diff --git a/client/public/wsModule.js b/client/public/wsModule.js index e26c64e..ca89443 100644 --- a/client/public/wsModule.js +++ b/client/public/wsModule.js @@ -29,13 +29,13 @@ import { renderChat } from "./render.js"; * @returns {{ socket: WebSocket, sendMessage: (content: string) => void }} */ export function initWebSocket(username, chatEl, inputContainer, usersListEl) { - const socket = new WebSocket( - (location.protocol === "https:" ? "wss" : "ws") + + const socket = new WebSocket("ws://localhost:8080"); + /*(location.protocol === "https:" ? "wss" : "ws") + "://" + location.host + "/ws", "coms", - ); + );*/ socket.onopen = () => { // Announce join. diff --git a/server/src/api.c b/server/src/api.c index 6a6a00b..cd78c01 100644 --- a/server/src/api.c +++ b/server/src/api.c @@ -27,7 +27,7 @@ Packet* packet_init(PacketType type, void* data) { return packet; } -Packet* packet_init_safe(char* type, void* data) { +Packet* packet_init_safe(const char* type, const void* data) { PacketType t = packet_type_parse(type); if (t == PACKET_TYPE_BAD) return NULL; diff --git a/server/src/chat.c b/server/src/chat.c index ce74fff..71ed5e7 100644 --- a/server/src/chat.c +++ b/server/src/chat.c @@ -1,4 +1,5 @@ #include "include/chat.h" +#include "include/api.h" #include "include/data.h" #include "include/session.h" @@ -41,16 +42,40 @@ MsgData** chat_history_nice(void) { return msgs; } -static size_t next_msg_id = 1; +// Parse a raw packet. +Packet* packet_parse(const char* in, size_t len) { + yyjson_doc* doc = yyjson_read((const char*)in, len, 0); + if (!doc) return NULL; -// History buffer for last 100 messages. -#define HISTORY_SIZE 100 -static char* history[HISTORY_SIZE]; -static size_t history_len[HISTORY_SIZE]; -static size_t history_pos = 0; -static size_t history_count = 0; + yyjson_val* root = yyjson_doc_get_root(doc); + yyjson_val* jtype = yyjson_obj_get(root, "type"); + const char* type = + (jtype && yyjson_is_str(jtype)) ? yyjson_get_str(jtype) : NULL; -#define CHAT_BUF_SIZE SESSION_CHAT_BUF_SIZE + yyjson_val* rdata = yyjson_obj_get((yyjson_val*)root, "data"); + const yyjson_val* data = ((rdata && yyjson_is_obj(rdata)) ? rdata : NULL); + + return packet_init_safe(type, data); +} + +// Do a join packet. +void do_join(Session* sess, Packet* packet) { + yyjson_val* data = packet->data; + yyjson_val* jname = data ? yyjson_obj_get(data, "name") : NULL; + const char* rname = + (jname && yyjson_is_str(jname)) ? yyjson_get_str(jname) : "Anon"; + + Name name = {'\0'}; + for (size_t i = 0; rname[i] && i < NAME_MAX_LENGTH - 1; i++) + name[i] = rname[i]; + + session_set_name(sess, name); +} + +// Do a msg packet. +void do_msg(Session* sess, Packet* packet) {} + +#define CHAT_PACKET_SIZE SESSION_CHAT_BUF_SIZE /* * cb_chat - libwebsockets protocol callback. @@ -66,142 +91,117 @@ int cb_chat( 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) { + if (len > CHAT_PACKET_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); + Packet* packet = packet_parse(in, len); - // #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); - /* add each user once */ - 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) - ); - } - } - // History will be streamed individually after welcome. - - 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); - // Schedule the welcome frame for writable callback. - lws_callback_on_writable(sess->wsi); - - free(out); - yyjson_mut_doc_free(wdoc); - - // #1a: Stream history messages to new client. - for (size_t i = 0; i < history_count; ++i) { - size_t idx = - (history_pos + HISTORY_SIZE - history_count + i) % - HISTORY_SIZE; - size_t hist_len = history_len[idx]; - size_t send_len = hist_len < SESSION_CHAT_BUF_SIZE - ? hist_len - : SESSION_CHAT_BUF_SIZE; - memcpy(&sess->buf[LWS_PRE], history[idx], send_len); - lws_write( - sess->wsi, &sess->buf[LWS_PRE], send_len, - LWS_WRITE_TEXT - ); - } - } - - // #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); - } + if (!packet) { + lwsl_warn("Received bad packet: \"%s\".\n", (char*)in); + break; } - // NAME. + + switch (packet->type) { + case PACKET_TYPE_JOIN: + // #1. Handle join packet, add new session. + do_join(sess, packet); + // + // #2. Welcome the 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_name(s)) { + yyjson_mut_val* uobj = yyjson_mut_obj(wdoc); + /* add each user once */ + 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_name(s) + ); + } + } + // History will be streamed individually after welcome. + + 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); + // Schedule the welcome frame for writable callback. + lws_callback_on_writable(sess->wsi); + + 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_name(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); + } + break; + case PACKET_TYPE_MSG: do_msg(sess, packet); break; + default: + lwsl_warn( + "Received client-only packet: \"%s\".\n", (char*)in + ); + } + } +#if 0 + // NAME. else if (type && strcmp(type, "name") == 0 && sess && session_has_username(sess)) { const yyjson_val* dataVal = @@ -226,12 +226,12 @@ int cb_chat( newname_buf[_j] = '\0'; } else { // Disallow empty names. - strcpy(newname_buf, session_get_username(sess)); + strcpy(newname_buf, session_get_name(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); + const char* current = session_get_name(sess); if (current) { strncpy(oldname_buf, current, SESSION_USERNAME_MAX_LEN - 1); oldname_buf[SESSION_USERNAME_MAX_LEN - 1] = '\0'; @@ -239,7 +239,7 @@ int cb_chat( oldname_buf[0] = '\0'; } // Now update to new name. - session_set_username(sess, newname); + session_set_name(sess, newname); // Broadcast name-event to other clients. { @@ -340,7 +340,7 @@ int cb_chat( 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) + mdoc, mdat, "username", session_get_name(sess) ); yyjson_mut_obj_add_str(mdoc, mdat, "content", msg); @@ -370,7 +370,8 @@ int cb_chat( yyjson_doc_free(doc); break; - } + } +#endif case LWS_CALLBACK_SERVER_WRITEABLE: if (sess && sess->buf_len > 0) { diff --git a/server/src/include/api.h b/server/src/include/api.h index 3a1bd83..282ec93 100644 --- a/server/src/include/api.h +++ b/server/src/include/api.h @@ -25,7 +25,7 @@ typedef struct { Packet* packet_init(PacketType type, void* data); // Create a packet from untrusted data. -Packet* packet_init_safe(char* type, void* data); +Packet* packet_init_safe(const char* type, const void* data); typedef struct { Name name; // Your selected name. diff --git a/server/src/include/chat.h b/server/src/include/chat.h index 4af0f49..409a48f 100644 --- a/server/src/include/chat.h +++ b/server/src/include/chat.h @@ -1,5 +1,5 @@ -#ifndef CHAT_H -#define CHAT_H +#ifndef CHAT__H +#define CHAT__H #include "data.h" @@ -10,7 +10,7 @@ // Message history (ring). #define CHAT_HISTORY_SZ 128 extern MsgData* chat_history[CHAT_HISTORY_SZ]; -extern size_t chat_history_head; // Points to the oldest message, or NULL. +extern size_t chat_history_head; // Points to the oldest message, or is NULL. // Add message to history ring. MsgData* chat_history_msg_add(MsgData* msg); @@ -18,7 +18,6 @@ MsgData* chat_history_msg_add(MsgData* msg); // Get a list of messages in order. MsgData** chat_history_nice(void); - /** * cb_chat - libwebsockets protocol callback for COMS chat. * @@ -38,4 +37,6 @@ int cb_chat( size_t len ); -#endif // CHAT_H + + +#endif diff --git a/server/src/include/session.h b/server/src/include/session.h index e94f6a7..e29bc66 100644 --- a/server/src/include/session.h +++ b/server/src/include/session.h @@ -1,12 +1,13 @@ -#ifndef SESSION_H -#define SESSION_H +#ifndef SESSION__H +#define SESSION__H #include #include #include +#include "data.h" + // Includes terminating null. -#define SESSION_USERNAME_MAX_LEN 32 #define SESSION_CHAT_BUF_SIZE 32768 /** @@ -14,13 +15,14 @@ * 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 + struct lws* wsi; // Libwebsockets connection handle. + uint64_t id; // Unique session ID. + struct SESSION* next; // Next session in the internal list. + Name name; // Stored name. + bool named; // True once name 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; /** @@ -58,31 +60,31 @@ Session* session_get_head(void); void session_broadcast(void (*cb)(Session* sess, void* user), void* user); /** - * session_set_username - * Store a username in the given session. + * session_set_name + * Store a name with the given session. * * @param sess The session to update. - * @param username Null-terminated string to copy into the session. + * @param name Null-terminated string to copy into the session. */ -void session_set_username(Session* sess, const char* username); +void session_set_name(Session* sess, const char* name); /** - * session_get_username - * Fetch the username stored in the session. + * session_get_name + * Fetch the name stored in the session. * * @param sess The session to query. - * @return Pointer to the stored username (readonly). + * @return Pointer to the stored name (readonly). */ -const char* session_get_username(const Session* sess); +const char* session_get_name(const Session* sess); /** - * session_has_username - * Check whether a session has already set a username. + * session_has_name + * Check whether a session has already set a name. * * @param sess The session to query. - * @return True if a username is set, false otherwise. + * @return True if a name is set, false otherwise. */ -bool session_has_username(const Session* sess); +bool session_has_name(const Session* sess); /** * session_get_id @@ -93,4 +95,4 @@ bool session_has_username(const Session* sess); */ uint64_t session_get_id(const Session* sess); -#endif // SESSION_H +#endif diff --git a/server/src/session.c b/server/src/session.c index 41a1506..3a681ca 100644 --- a/server/src/session.c +++ b/server/src/session.c @@ -1,4 +1,6 @@ #include "include/session.h" +#include "include/data.h" + #include #include #include @@ -39,19 +41,19 @@ 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'; +void session_set_name(Session* sess, const char* name) { + if (!sess || !name) { return; } + strncpy(sess->name, name, NAME_MAX_LENGTH - 1); + sess->name[NAME_MAX_LENGTH - 1] = '\0'; sess->named = true; } -const char* session_get_username(const Session* sess) { +const char* session_get_name(const Session* sess) { if (!sess) { return NULL; } return sess->name; } -bool session_has_username(const Session* sess) { +bool session_has_name(const Session* sess) { return sess ? sess->named : false; }