Added code.
This commit is contained in:
14
server/.clang-format
Normal file
14
server/.clang-format
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: true
|
||||
AcrossComments: true
|
||||
IndentCaseLabels: true
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
IndentWidth: 4
|
||||
PointerAlignment: Left
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
2
server/.clangd
Normal file
2
server/.clangd
Normal file
@@ -0,0 +1,2 @@
|
||||
CompileFlags:
|
||||
Add: [-xc]
|
||||
33
server/Makefile
Normal file
33
server/Makefile
Normal file
@@ -0,0 +1,33 @@
|
||||
include config.mk
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
release: clean
|
||||
release: CFLAGS += -O2
|
||||
release: $(TARGET)
|
||||
|
||||
# Run the target.
|
||||
run: $(TARGET)
|
||||
./$(TARGET)
|
||||
|
||||
# Compile project source objects.
|
||||
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(INC_DIR)/%.h
|
||||
@ mkdir -p $(OBJ_DIR)
|
||||
@ $(PRINT) "$(WHITE_BOLD)Compiling source object $(WHITE)$@$(WHITE_BOLD)... $(RESETCOLOR)"
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# Link to final binary.
|
||||
$(TARGET): $(OBJ_FILES)
|
||||
@ $(PRINT) "$(WHITE_BOLD)Linking $(WHITE)$@$(WHITE_BOLD)...$(RESETCOLOR)"
|
||||
$(LINK) -o $(TARGET) $(OBJ_FILES) $(LDFLAGS)
|
||||
|
||||
# Clean out objects, binaries, and built artifacts.
|
||||
clean:
|
||||
@ $(PRINT) "$(WHITE_BOLD)Cleaning up...$(RESETCOLOR)"
|
||||
rm -rf $(OBJ_DIR)/*.o $(TARGET)
|
||||
|
||||
# Get LOC.
|
||||
lines:
|
||||
@ wc -l $(SRC_FILES) $(INC_FILES)
|
||||
|
||||
.PHONY: all clean test nocolor release run lines
|
||||
24
server/config.mk
Normal file
24
server/config.mk
Normal file
@@ -0,0 +1,24 @@
|
||||
NAME = coms
|
||||
|
||||
TARGET = $(NAME).out
|
||||
|
||||
SRC_DIR = src
|
||||
INC_DIR = $(SRC_DIR)/include
|
||||
BUILD_DIR = build
|
||||
OBJ_DIR = $(BUILD_DIR)/obj
|
||||
|
||||
CC = clang -std=c23
|
||||
LINK = clang
|
||||
CFLAGS = -Wall -DDBG -ggdb -fsanitize=leak -I$(INC_DIR) -I$(SRC_DIR)
|
||||
LDFLAGS = -lwebsockets
|
||||
PRINT = echo -e
|
||||
|
||||
SRC_FILES = $(wildcard $(SRC_DIR)/*.c)
|
||||
INC_FILES = $(wildcard $(INC_DIR)/*.h)
|
||||
OBJ_FILES = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_FILES))
|
||||
OBJ_FILES_NOMAIN = $(filter-out $(OBJ_DIR)/main.o, $(OBJ_FILES)) # Object files without main.c.
|
||||
|
||||
RESETCOLOR = \033[0m
|
||||
WHITE = $(RESETCOLOR)\033[0m
|
||||
WHITE_BOLD = $(RESETCOLOR)\033[0;1m
|
||||
RED_BOLD = $(RESETCOLOR)\033[31;1m
|
||||
331
server/src/chat.c
Normal file
331
server/src/chat.c
Normal file
@@ -0,0 +1,331 @@
|
||||
// TODO: Make types for allt he proto events. This is quite messy without that.
|
||||
|
||||
#include "include/chat.h"
|
||||
#include "include/session.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <libwebsockets.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <yyjson.h>
|
||||
|
||||
static size_t next_msg_id = 1;
|
||||
|
||||
#define CHAT_BUF_SIZE SESSION_CHAT_BUF_SIZE
|
||||
|
||||
/*
|
||||
* cb_chat - libwebsockets protocol callback.
|
||||
|
||||
* Handles connection lifecycle, incoming messages, and writable events.
|
||||
*/
|
||||
int cb_chat(
|
||||
struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in,
|
||||
size_t len
|
||||
) {
|
||||
Session** ps_p = (Session**)user;
|
||||
Session* sess = ps_p ? *ps_p : NULL;
|
||||
Session* head = session_get_head();
|
||||
|
||||
switch (reason) {
|
||||
|
||||
case LWS_CALLBACK_ESTABLISHED:
|
||||
// New connection, create session.
|
||||
if (ps_p) { *ps_p = session_create(wsi); }
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_RECEIVE: {
|
||||
// Parse inc JSON packet.
|
||||
if (len > CHAT_BUF_SIZE) {
|
||||
lwsl_warn(
|
||||
"Received JSON payload exceeds limit: %zu bytes\n", len
|
||||
);
|
||||
break;
|
||||
}
|
||||
yyjson_doc* doc = yyjson_read((const char*)in, len, 0);
|
||||
if (!doc) break;
|
||||
const yyjson_val* root = yyjson_doc_get_root(doc);
|
||||
const yyjson_val* tval = yyjson_obj_get((yyjson_val*)root, "type");
|
||||
const char* type = (tval && yyjson_is_str((yyjson_val*)tval))
|
||||
? yyjson_get_str((yyjson_val*)tval)
|
||||
: NULL;
|
||||
|
||||
// JOIN.
|
||||
if (type && strcmp(type, "join") == 0 && sess) {
|
||||
const yyjson_val* data =
|
||||
yyjson_obj_get((yyjson_val*)root, "data");
|
||||
const yyjson_val* uval =
|
||||
data ? yyjson_obj_get((yyjson_val*)data, "username") : NULL;
|
||||
// Enforce max username length.
|
||||
const char* raw_name =
|
||||
(uval && yyjson_is_str((yyjson_val*)uval))
|
||||
? yyjson_get_str((yyjson_val*)uval)
|
||||
: "Anonymous";
|
||||
if (raw_name[0] == '\0') { raw_name = "Anonymous"; }
|
||||
char name_buf[SESSION_USERNAME_MAX_LEN];
|
||||
size_t _i;
|
||||
for (_i = 0; raw_name[_i] && _i < SESSION_USERNAME_MAX_LEN - 1;
|
||||
++_i) {
|
||||
unsigned char c = (unsigned char)raw_name[_i];
|
||||
name_buf[_i] = isprint(c) ? c : '?';
|
||||
}
|
||||
name_buf[_i] = '\0';
|
||||
session_set_username(sess, name_buf);
|
||||
|
||||
// #1: Welcome our new client.
|
||||
{
|
||||
yyjson_mut_doc* wdoc = yyjson_mut_doc_new(NULL);
|
||||
yyjson_mut_val* wroot = yyjson_mut_obj(wdoc);
|
||||
yyjson_mut_doc_set_root(wdoc, wroot);
|
||||
yyjson_mut_obj_add_str(wdoc, wroot, "type", "welcome");
|
||||
|
||||
yyjson_mut_val* wdata = yyjson_mut_obj(wdoc);
|
||||
yyjson_mut_obj_add_val(wdoc, wroot, "data", wdata);
|
||||
yyjson_mut_obj_add_uint(
|
||||
wdoc, wdata, "you", session_get_id(sess)
|
||||
);
|
||||
|
||||
yyjson_mut_val* wusers = yyjson_mut_arr(wdoc);
|
||||
yyjson_mut_obj_add_val(wdoc, wdata, "users", wusers);
|
||||
|
||||
for (Session* s = head; s; s = s->next) {
|
||||
if (session_has_username(s)) {
|
||||
yyjson_mut_val* uobj = yyjson_mut_obj(wdoc);
|
||||
yyjson_mut_arr_add_val(wusers, uobj);
|
||||
yyjson_mut_obj_add_uint(
|
||||
wdoc, uobj, "id", session_get_id(s)
|
||||
);
|
||||
yyjson_mut_obj_add_str(
|
||||
wdoc, uobj, "username", session_get_username(s)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
size_t out_len;
|
||||
char* out = yyjson_mut_write(wdoc, 0, &out_len);
|
||||
size_t copy_len = out_len < SESSION_CHAT_BUF_SIZE
|
||||
? out_len
|
||||
: SESSION_CHAT_BUF_SIZE;
|
||||
sess->buf_len = copy_len;
|
||||
memcpy(&sess->buf[LWS_PRE], out, copy_len);
|
||||
lws_write(
|
||||
sess->wsi, &sess->buf[LWS_PRE], sess->buf_len,
|
||||
LWS_WRITE_TEXT
|
||||
);
|
||||
|
||||
free(out);
|
||||
yyjson_mut_doc_free(wdoc);
|
||||
}
|
||||
|
||||
// #2: Introduce our new client to everybody else.
|
||||
{
|
||||
yyjson_mut_doc* jdoc = yyjson_mut_doc_new(NULL);
|
||||
yyjson_mut_val* jroot = yyjson_mut_obj(jdoc);
|
||||
yyjson_mut_doc_set_root(jdoc, jroot);
|
||||
yyjson_mut_obj_add_str(jdoc, jroot, "type", "join-event");
|
||||
|
||||
yyjson_mut_val* jdata = yyjson_mut_obj(jdoc);
|
||||
yyjson_mut_obj_add_val(jdoc, jroot, "data", jdata);
|
||||
yyjson_mut_obj_add_uint(
|
||||
jdoc, jdata, "id", session_get_id(sess)
|
||||
);
|
||||
yyjson_mut_obj_add_str(
|
||||
jdoc, jdata, "username", session_get_username(sess)
|
||||
);
|
||||
|
||||
size_t out_len;
|
||||
char* out = yyjson_mut_write(jdoc, 0, &out_len);
|
||||
size_t copy_len = out_len < SESSION_CHAT_BUF_SIZE
|
||||
? out_len
|
||||
: SESSION_CHAT_BUF_SIZE;
|
||||
for (Session* s = head; s; s = s->next) {
|
||||
s->buf_len = copy_len;
|
||||
memcpy(&s->buf[LWS_PRE], out, copy_len);
|
||||
lws_callback_on_writable(s->wsi);
|
||||
}
|
||||
free(out);
|
||||
yyjson_mut_doc_free(jdoc);
|
||||
}
|
||||
}
|
||||
// NAME.
|
||||
else if (type && strcmp(type, "name") == 0 && sess &&
|
||||
session_has_username(sess)) {
|
||||
const yyjson_val* dataVal =
|
||||
root ? yyjson_obj_get((yyjson_val*)root, "data") : NULL;
|
||||
const yyjson_val* nval =
|
||||
dataVal ? yyjson_obj_get((yyjson_val*)dataVal, "username")
|
||||
: NULL;
|
||||
// Enforce max name length.
|
||||
const char* raw_newname =
|
||||
(nval && yyjson_is_str((yyjson_val*)nval))
|
||||
? yyjson_get_str((yyjson_val*)nval)
|
||||
: NULL;
|
||||
char newname_buf[SESSION_USERNAME_MAX_LEN];
|
||||
if (raw_newname && raw_newname[0] != '\0') {
|
||||
size_t _j;
|
||||
for (_j = 0;
|
||||
raw_newname[_j] && _j < SESSION_USERNAME_MAX_LEN - 1;
|
||||
++_j) {
|
||||
unsigned char c = (unsigned char)raw_newname[_j];
|
||||
newname_buf[_j] = isprint(c) ? c : '?';
|
||||
}
|
||||
newname_buf[_j] = '\0';
|
||||
} else {
|
||||
// Disallow empty names.
|
||||
strcpy(newname_buf, session_get_username(sess));
|
||||
}
|
||||
const char* newname = newname_buf;
|
||||
// Buffer old name before updating.
|
||||
char oldname_buf[SESSION_USERNAME_MAX_LEN];
|
||||
const char* current = session_get_username(sess);
|
||||
if (current) {
|
||||
strncpy(oldname_buf, current, SESSION_USERNAME_MAX_LEN - 1);
|
||||
oldname_buf[SESSION_USERNAME_MAX_LEN - 1] = '\0';
|
||||
} else {
|
||||
oldname_buf[0] = '\0';
|
||||
}
|
||||
// Now update to new name.
|
||||
session_set_username(sess, newname);
|
||||
|
||||
// Broadcast name-event to other clients.
|
||||
{
|
||||
yyjson_mut_doc* ndoc = yyjson_mut_doc_new(NULL);
|
||||
yyjson_mut_val* nroot = yyjson_mut_obj(ndoc);
|
||||
yyjson_mut_doc_set_root(ndoc, nroot);
|
||||
yyjson_mut_obj_add_str(ndoc, nroot, "type", "name-event");
|
||||
yyjson_mut_val* ndata = yyjson_mut_obj(ndoc);
|
||||
yyjson_mut_obj_add_val(ndoc, nroot, "data", ndata);
|
||||
yyjson_mut_obj_add_uint(
|
||||
ndoc, ndata, "id", session_get_id(sess)
|
||||
);
|
||||
yyjson_mut_obj_add_str(ndoc, ndata, "old", oldname_buf);
|
||||
yyjson_mut_obj_add_str(ndoc, ndata, "new", newname);
|
||||
|
||||
size_t out_len;
|
||||
char* out = yyjson_mut_write(ndoc, 0, &out_len);
|
||||
size_t copy_len = out_len < SESSION_CHAT_BUF_SIZE
|
||||
? out_len
|
||||
: SESSION_CHAT_BUF_SIZE;
|
||||
for (Session* s = head; s; s = s->next) {
|
||||
s->buf_len = copy_len;
|
||||
memcpy(&s->buf[LWS_PRE], out, copy_len);
|
||||
lws_callback_on_writable(s->wsi);
|
||||
}
|
||||
free(out);
|
||||
yyjson_mut_doc_free(ndoc);
|
||||
}
|
||||
}
|
||||
// MSG.
|
||||
else if (type && strcmp(type, "msg") == 0 && sess &&
|
||||
session_has_username(sess)) {
|
||||
const yyjson_val* data =
|
||||
yyjson_obj_get((yyjson_val*)root, "data");
|
||||
const yyjson_val* cval =
|
||||
data ? yyjson_obj_get((yyjson_val*)data, "content") : NULL;
|
||||
// Enforce maximum message content length
|
||||
const char* raw_msg = (cval && yyjson_is_str((yyjson_val*)cval))
|
||||
? yyjson_get_str((yyjson_val*)cval)
|
||||
: "";
|
||||
char msg_buf[CHAT_BUF_SIZE];
|
||||
strncpy(msg_buf, raw_msg, CHAT_BUF_SIZE - 1);
|
||||
msg_buf[CHAT_BUF_SIZE - 1] = '\0';
|
||||
// Sanitize message to printable characters.
|
||||
for (size_t _k = 0; msg_buf[_k]; ++_k) {
|
||||
unsigned char c = (unsigned char)msg_buf[_k];
|
||||
if (!isprint(c)) { msg_buf[_k] = '?'; }
|
||||
}
|
||||
const char* msg = msg_buf;
|
||||
|
||||
// Build msg-event JSON with ID, parent and timestamp.
|
||||
// Get parent ID if present.
|
||||
const yyjson_val* dataVal =
|
||||
yyjson_obj_get((yyjson_val*)root, "data");
|
||||
const yyjson_val* pval =
|
||||
dataVal ? yyjson_obj_get((yyjson_val*)dataVal, "parent")
|
||||
: NULL;
|
||||
uint64_t parent_id = (pval && yyjson_is_uint((yyjson_val*)pval))
|
||||
? yyjson_get_uint((yyjson_val*)pval)
|
||||
: 0;
|
||||
time_t now = time(NULL);
|
||||
size_t msg_id = next_msg_id++;
|
||||
// Send ack with the new message ID.
|
||||
{
|
||||
yyjson_mut_doc* ackdoc = yyjson_mut_doc_new(NULL);
|
||||
yyjson_mut_val* ackroot = yyjson_mut_obj(ackdoc);
|
||||
yyjson_mut_doc_set_root(ackdoc, ackroot);
|
||||
yyjson_mut_obj_add_str(ackdoc, ackroot, "type", "msg-ack");
|
||||
yyjson_mut_val* ackdat = yyjson_mut_obj(ackdoc);
|
||||
yyjson_mut_obj_add_val(ackdoc, ackroot, "data", ackdat);
|
||||
yyjson_mut_obj_add_uint(ackdoc, ackdat, "id", msg_id);
|
||||
size_t ack_len;
|
||||
char* ack_out = yyjson_mut_write(ackdoc, 0, &ack_len);
|
||||
size_t copy_ack = ack_len < SESSION_CHAT_BUF_SIZE
|
||||
? ack_len
|
||||
: SESSION_CHAT_BUF_SIZE;
|
||||
sess->buf_len = copy_ack;
|
||||
memcpy(&sess->buf[LWS_PRE], ack_out, copy_ack);
|
||||
lws_write(
|
||||
sess->wsi, &sess->buf[LWS_PRE], sess->buf_len,
|
||||
LWS_WRITE_TEXT
|
||||
);
|
||||
free(ack_out);
|
||||
yyjson_mut_doc_free(ackdoc);
|
||||
}
|
||||
|
||||
yyjson_mut_doc* mdoc = yyjson_mut_doc_new(NULL);
|
||||
yyjson_mut_val* mroot = yyjson_mut_obj(mdoc);
|
||||
yyjson_mut_doc_set_root(mdoc, mroot);
|
||||
yyjson_mut_obj_add_str(mdoc, mroot, "type", "msg-event");
|
||||
|
||||
yyjson_mut_val* mdat = yyjson_mut_obj(mdoc);
|
||||
yyjson_mut_obj_add_val(mdoc, mroot, "data", mdat);
|
||||
yyjson_mut_obj_add_uint(mdoc, mdat, "id", msg_id);
|
||||
if (parent_id)
|
||||
yyjson_mut_obj_add_uint(mdoc, mdat, "parent", parent_id);
|
||||
else yyjson_mut_obj_add_null(mdoc, mdat, "parent");
|
||||
yyjson_mut_obj_add_uint(mdoc, mdat, "ts", (uint64_t)now);
|
||||
yyjson_mut_obj_add_str(
|
||||
mdoc, mdat, "username", session_get_username(sess)
|
||||
);
|
||||
yyjson_mut_obj_add_str(mdoc, mdat, "content", msg);
|
||||
|
||||
size_t out_len;
|
||||
char* out = yyjson_mut_write(mdoc, 0, &out_len);
|
||||
size_t copy_len = out_len < SESSION_CHAT_BUF_SIZE
|
||||
? out_len
|
||||
: SESSION_CHAT_BUF_SIZE;
|
||||
for (Session* s = head; s; s = s->next) {
|
||||
s->buf_len = copy_len;
|
||||
memcpy(&s->buf[LWS_PRE], out, copy_len);
|
||||
lws_callback_on_writable(s->wsi);
|
||||
}
|
||||
free(out);
|
||||
yyjson_mut_doc_free(mdoc);
|
||||
// Writable events already scheduled for each session above.
|
||||
}
|
||||
|
||||
yyjson_doc_free(doc);
|
||||
break;
|
||||
}
|
||||
|
||||
case LWS_CALLBACK_SERVER_WRITEABLE:
|
||||
if (sess && sess->buf_len > 0) {
|
||||
lws_write(
|
||||
sess->wsi, &sess->buf[LWS_PRE], sess->buf_len,
|
||||
LWS_WRITE_TEXT
|
||||
);
|
||||
sess->buf_len = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CLOSED:
|
||||
// Goodbye.
|
||||
// TODO: Add leave event to proto.
|
||||
if (sess) { session_destroy(sess); }
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
25
server/src/include/chat.h
Normal file
25
server/src/include/chat.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef CHAT_H
|
||||
#define CHAT_H
|
||||
|
||||
#include <libwebsockets.h>
|
||||
|
||||
/**
|
||||
* cb_chat - libwebsockets protocol callback for COMS chat.
|
||||
*
|
||||
* This function is registered in the protocols array passed to the
|
||||
* lws_context and handles connection, receive, writable, and close events.
|
||||
*
|
||||
* @wsi: The WebSocket instance pointer
|
||||
* @reason: One of the LWS_CALLBACK_* reasons
|
||||
* @user: Pointer to a session_t* for this connection
|
||||
* @in: Pointer to incoming data (for receive events)
|
||||
* @len: Length of the incoming data buffer
|
||||
*
|
||||
* Return: 0 on success, non-zero on error.
|
||||
*/
|
||||
int cb_chat(
|
||||
struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in,
|
||||
size_t len
|
||||
);
|
||||
|
||||
#endif // CHAT_H
|
||||
4
server/src/include/main.h
Normal file
4
server/src/include/main.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#endif // MAIN_H
|
||||
96
server/src/include/session.h
Normal file
96
server/src/include/session.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#ifndef SESSION_H
|
||||
#define SESSION_H
|
||||
|
||||
#include <libwebsockets.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Includes terminating null.
|
||||
#define SESSION_USERNAME_MAX_LEN 32
|
||||
#define SESSION_CHAT_BUF_SIZE 1024
|
||||
|
||||
/**
|
||||
* session_t
|
||||
* Represents a single WebSocket client session in the chat server.
|
||||
*/
|
||||
typedef struct SESSION {
|
||||
struct lws* wsi; // Libwebsockets connection handle.
|
||||
uint64_t id; // Unique session ID.
|
||||
struct SESSION* next; // Next session in the internal list.
|
||||
char name[SESSION_USERNAME_MAX_LEN]; // Stored username.
|
||||
bool named; // True once username is set.
|
||||
unsigned char buf[LWS_PRE + SESSION_CHAT_BUF_SIZE]; // Outgoing buffer per session
|
||||
size_t buf_len; // Length of data in buf
|
||||
} Session;
|
||||
|
||||
/**
|
||||
* session_create
|
||||
* Allocate and initialize a new session, adding it to the internal list.
|
||||
*
|
||||
* @param wsi The libwebsockets connection handle.
|
||||
* @return Pointer to the new session, or NULL on failure.
|
||||
*/
|
||||
Session* session_create(struct lws* wsi);
|
||||
|
||||
/**
|
||||
* session_destroy
|
||||
* Remove the given session from the internal list and free its memory.
|
||||
*
|
||||
* @param sess The session to destroy.
|
||||
*/
|
||||
void session_destroy(Session* sess);
|
||||
|
||||
/**
|
||||
* session_get_head
|
||||
* Retrieve the head of the internal session linked list.
|
||||
*
|
||||
* @return The first session in the list, or NULL if none.
|
||||
*/
|
||||
Session* session_get_head(void);
|
||||
|
||||
/**
|
||||
* session_broadcast
|
||||
* Iterate every session in the internal list and invoke the callback.
|
||||
*
|
||||
* @param cb Function to call for each session.
|
||||
* @param user Arbitrary user data passed through to each callback.
|
||||
*/
|
||||
void session_broadcast(void (*cb)(Session* sess, void* user), void* user);
|
||||
|
||||
/**
|
||||
* session_set_username
|
||||
* Store a username in the given session.
|
||||
*
|
||||
* @param sess The session to update.
|
||||
* @param username Null-terminated string to copy into the session.
|
||||
*/
|
||||
void session_set_username(Session* sess, const char* username);
|
||||
|
||||
/**
|
||||
* session_get_username
|
||||
* Fetch the username stored in the session.
|
||||
*
|
||||
* @param sess The session to query.
|
||||
* @return Pointer to the stored username (readonly).
|
||||
*/
|
||||
const char* session_get_username(const Session* sess);
|
||||
|
||||
/**
|
||||
* session_has_username
|
||||
* Check whether a session has already set a username.
|
||||
*
|
||||
* @param sess The session to query.
|
||||
* @return True if a username is set, false otherwise.
|
||||
*/
|
||||
bool session_has_username(const Session* sess);
|
||||
|
||||
/**
|
||||
* session_get_id
|
||||
* Fetch the session ID.
|
||||
*
|
||||
* @param sess The session to query.
|
||||
* @return The session's unique ID.
|
||||
*/
|
||||
uint64_t session_get_id(const Session* sess);
|
||||
|
||||
#endif // SESSION_H
|
||||
8332
server/src/include/yyjson.h
Normal file
8332
server/src/include/yyjson.h
Normal file
File diff suppressed because it is too large
Load Diff
52
server/src/main.c
Normal file
52
server/src/main.c
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "include/chat.h"
|
||||
#include "include/session.h"
|
||||
|
||||
#include <libwebsockets.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static volatile sig_atomic_t interrupted = 0;
|
||||
|
||||
static void handle_sigint(int sig) {
|
||||
(void)sig;
|
||||
interrupted = 1;
|
||||
}
|
||||
|
||||
static struct lws_protocols protocols[] = {
|
||||
{
|
||||
"coms",
|
||||
cb_chat,
|
||||
sizeof(Session*),
|
||||
1024,
|
||||
},
|
||||
{NULL, NULL, 0, 0}
|
||||
};
|
||||
|
||||
int main(void) {
|
||||
signal(SIGINT, handle_sigint);
|
||||
|
||||
// Create libws context.
|
||||
struct lws_context_creation_info info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.port = 8080;
|
||||
info.protocols = protocols;
|
||||
info.gid = -1;
|
||||
info.uid = -1;
|
||||
|
||||
struct lws_context* context = lws_create_context(&info);
|
||||
if (!context) {
|
||||
fprintf(stderr, "Failed to create LWS context\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("COMS server started on port %d\n", info.port);
|
||||
|
||||
// Service loop.
|
||||
while (!interrupted) { lws_service(context, 1000); }
|
||||
|
||||
// Cleanse.
|
||||
lws_context_destroy(context);
|
||||
printf("Server shutting down.\n");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
58
server/src/session.c
Normal file
58
server/src/session.c
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "include/session.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint64_t next_session_id = 1;
|
||||
|
||||
// Head of session linked list.
|
||||
static Session* head = NULL;
|
||||
|
||||
Session* session_create(struct lws* wsi) {
|
||||
Session* sess = malloc(sizeof(Session));
|
||||
if (!sess) { return NULL; }
|
||||
sess->wsi = wsi;
|
||||
sess->id = next_session_id++;
|
||||
sess->named = false;
|
||||
sess->name[0] = '\0';
|
||||
sess->next = head;
|
||||
head = sess;
|
||||
return sess;
|
||||
}
|
||||
|
||||
void session_destroy(Session* sess) {
|
||||
if (!sess) { return; }
|
||||
Session** ptr = &head;
|
||||
while (*ptr) {
|
||||
if (*ptr == sess) {
|
||||
*ptr = sess->next;
|
||||
free(sess);
|
||||
return;
|
||||
}
|
||||
ptr = &(*ptr)->next;
|
||||
}
|
||||
}
|
||||
|
||||
Session* session_get_head(void) { return head; }
|
||||
|
||||
void session_broadcast(void (*cb)(Session* s, void* user), void* user) {
|
||||
for (Session* iter = head; iter; iter = iter->next) { cb(iter, user); }
|
||||
}
|
||||
|
||||
void session_set_username(Session* sess, const char* username) {
|
||||
if (!sess || !username) { return; }
|
||||
strncpy(sess->name, username, SESSION_USERNAME_MAX_LEN - 1);
|
||||
sess->name[SESSION_USERNAME_MAX_LEN - 1] = '\0';
|
||||
sess->named = true;
|
||||
}
|
||||
|
||||
const char* session_get_username(const Session* sess) {
|
||||
if (!sess) { return NULL; }
|
||||
return sess->name;
|
||||
}
|
||||
|
||||
bool session_has_username(const Session* sess) {
|
||||
return sess ? sess->named : false;
|
||||
}
|
||||
|
||||
uint64_t session_get_id(const Session* sess) { return sess ? sess->id : 0; }
|
||||
11210
server/src/yyjson.c
Normal file
11210
server/src/yyjson.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user