Server handles join packets nicely.

This commit is contained in:
2026-02-21 11:28:00 -05:00
parent e81b5e5b48
commit 226c4a8b52
7 changed files with 180 additions and 174 deletions

View File

@@ -29,13 +29,13 @@ import { renderChat } from "./render.js";
* @returns {{ socket: WebSocket, sendMessage: (content: string) => void }} * @returns {{ socket: WebSocket, sendMessage: (content: string) => void }}
*/ */
export function initWebSocket(username, chatEl, inputContainer, usersListEl) { export function initWebSocket(username, chatEl, inputContainer, usersListEl) {
const socket = new WebSocket( const socket = new WebSocket("ws://localhost:8080");
(location.protocol === "https:" ? "wss" : "ws") + /*(location.protocol === "https:" ? "wss" : "ws") +
"://" + "://" +
location.host + location.host +
"/ws", "/ws",
"coms", "coms",
); );*/
socket.onopen = () => { socket.onopen = () => {
// Announce join. // Announce join.

View File

@@ -27,7 +27,7 @@ Packet* packet_init(PacketType type, void* data) {
return packet; 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); PacketType t = packet_type_parse(type);
if (t == PACKET_TYPE_BAD) return NULL; if (t == PACKET_TYPE_BAD) return NULL;

View File

@@ -1,4 +1,5 @@
#include "include/chat.h" #include "include/chat.h"
#include "include/api.h"
#include "include/data.h" #include "include/data.h"
#include "include/session.h" #include "include/session.h"
@@ -41,16 +42,40 @@ MsgData** chat_history_nice(void) {
return msgs; 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. yyjson_val* root = yyjson_doc_get_root(doc);
#define HISTORY_SIZE 100 yyjson_val* jtype = yyjson_obj_get(root, "type");
static char* history[HISTORY_SIZE]; const char* type =
static size_t history_len[HISTORY_SIZE]; (jtype && yyjson_is_str(jtype)) ? yyjson_get_str(jtype) : NULL;
static size_t history_pos = 0;
static size_t history_count = 0;
#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. * cb_chat - libwebsockets protocol callback.
@@ -66,51 +91,32 @@ int cb_chat(
Session* head = session_get_head(); Session* head = session_get_head();
switch (reason) { switch (reason) {
case LWS_CALLBACK_ESTABLISHED: case LWS_CALLBACK_ESTABLISHED:
// New connection, create session. // New connection, create session.
if (ps_p) { *ps_p = session_create(wsi); } if (ps_p) { *ps_p = session_create(wsi); }
break; break;
case LWS_CALLBACK_RECEIVE: { case LWS_CALLBACK_RECEIVE: {
// Parse inc JSON packet. // Parse inc JSON packet.
if (len > CHAT_BUF_SIZE) { if (len > CHAT_PACKET_SIZE) {
lwsl_warn( lwsl_warn(
"Received JSON payload exceeds limit: %zu bytes\n", len "Received JSON payload exceeds limit: %zu bytes\n", len
); );
break; 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. Packet* packet = packet_parse(in, len);
if (type && strcmp(type, "join") == 0 && sess) {
const yyjson_val* data = if (!packet) {
yyjson_obj_get((yyjson_val*)root, "data"); lwsl_warn("Received bad packet: \"%s\".\n", (char*)in);
const yyjson_val* uval = break;
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. 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_doc* wdoc = yyjson_mut_doc_new(NULL);
yyjson_mut_val* wroot = yyjson_mut_obj(wdoc); yyjson_mut_val* wroot = yyjson_mut_obj(wdoc);
@@ -127,7 +133,7 @@ int cb_chat(
yyjson_mut_obj_add_val(wdoc, wdata, "users", wusers); yyjson_mut_obj_add_val(wdoc, wdata, "users", wusers);
for (Session* s = head; s; s = s->next) { for (Session* s = head; s; s = s->next) {
if (session_has_username(s)) { if (session_has_name(s)) {
yyjson_mut_val* uobj = yyjson_mut_obj(wdoc); yyjson_mut_val* uobj = yyjson_mut_obj(wdoc);
/* add each user once */ /* add each user once */
yyjson_mut_arr_add_val(wusers, uobj); yyjson_mut_arr_add_val(wusers, uobj);
@@ -135,7 +141,7 @@ int cb_chat(
wdoc, uobj, "id", session_get_id(s) wdoc, uobj, "id", session_get_id(s)
); );
yyjson_mut_obj_add_str( yyjson_mut_obj_add_str(
wdoc, uobj, "username", session_get_username(s) wdoc, uobj, "username", session_get_name(s)
); );
} }
} }
@@ -153,30 +159,16 @@ int cb_chat(
free(out); free(out);
yyjson_mut_doc_free(wdoc); 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. // #2. Introduce our new client to everybody else.
{ {
yyjson_mut_doc* jdoc = yyjson_mut_doc_new(NULL); yyjson_mut_doc* jdoc = yyjson_mut_doc_new(NULL);
yyjson_mut_val* jroot = yyjson_mut_obj(jdoc); yyjson_mut_val* jroot = yyjson_mut_obj(jdoc);
yyjson_mut_doc_set_root(jdoc, jroot); yyjson_mut_doc_set_root(jdoc, jroot);
yyjson_mut_obj_add_str(jdoc, jroot, "type", "join-event"); yyjson_mut_obj_add_str(
jdoc, jroot, "type", "join-event"
);
yyjson_mut_val* jdata = yyjson_mut_obj(jdoc); yyjson_mut_val* jdata = yyjson_mut_obj(jdoc);
yyjson_mut_obj_add_val(jdoc, jroot, "data", jdata); yyjson_mut_obj_add_val(jdoc, jroot, "data", jdata);
@@ -184,7 +176,7 @@ int cb_chat(
jdoc, jdata, "id", session_get_id(sess) jdoc, jdata, "id", session_get_id(sess)
); );
yyjson_mut_obj_add_str( yyjson_mut_obj_add_str(
jdoc, jdata, "username", session_get_username(sess) jdoc, jdata, "username", session_get_name(sess)
); );
size_t out_len; size_t out_len;
@@ -200,7 +192,15 @@ int cb_chat(
free(out); free(out);
yyjson_mut_doc_free(jdoc); 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. // NAME.
else if (type && strcmp(type, "name") == 0 && sess && else if (type && strcmp(type, "name") == 0 && sess &&
session_has_username(sess)) { session_has_username(sess)) {
@@ -226,12 +226,12 @@ int cb_chat(
newname_buf[_j] = '\0'; newname_buf[_j] = '\0';
} else { } else {
// Disallow empty names. // Disallow empty names.
strcpy(newname_buf, session_get_username(sess)); strcpy(newname_buf, session_get_name(sess));
} }
const char* newname = newname_buf; const char* newname = newname_buf;
// Buffer old name before updating. // Buffer old name before updating.
char oldname_buf[SESSION_USERNAME_MAX_LEN]; char oldname_buf[SESSION_USERNAME_MAX_LEN];
const char* current = session_get_username(sess); const char* current = session_get_name(sess);
if (current) { if (current) {
strncpy(oldname_buf, current, SESSION_USERNAME_MAX_LEN - 1); strncpy(oldname_buf, current, SESSION_USERNAME_MAX_LEN - 1);
oldname_buf[SESSION_USERNAME_MAX_LEN - 1] = '\0'; oldname_buf[SESSION_USERNAME_MAX_LEN - 1] = '\0';
@@ -239,7 +239,7 @@ int cb_chat(
oldname_buf[0] = '\0'; oldname_buf[0] = '\0';
} }
// Now update to new name. // Now update to new name.
session_set_username(sess, newname); session_set_name(sess, newname);
// Broadcast name-event to other clients. // Broadcast name-event to other clients.
{ {
@@ -340,7 +340,7 @@ int cb_chat(
else yyjson_mut_obj_add_null(mdoc, mdat, "parent"); else yyjson_mut_obj_add_null(mdoc, mdat, "parent");
yyjson_mut_obj_add_uint(mdoc, mdat, "ts", (uint64_t)now); yyjson_mut_obj_add_uint(mdoc, mdat, "ts", (uint64_t)now);
yyjson_mut_obj_add_str( 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); yyjson_mut_obj_add_str(mdoc, mdat, "content", msg);
@@ -371,6 +371,7 @@ int cb_chat(
yyjson_doc_free(doc); yyjson_doc_free(doc);
break; break;
} }
#endif
case LWS_CALLBACK_SERVER_WRITEABLE: case LWS_CALLBACK_SERVER_WRITEABLE:
if (sess && sess->buf_len > 0) { if (sess && sess->buf_len > 0) {

View File

@@ -25,7 +25,7 @@ typedef struct {
Packet* packet_init(PacketType type, void* data); Packet* packet_init(PacketType type, void* data);
// Create a packet from untrusted 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 { typedef struct {
Name name; // Your selected name. Name name; // Your selected name.

View File

@@ -1,5 +1,5 @@
#ifndef CHAT_H #ifndef CHAT__H
#define CHAT_H #define CHAT__H
#include "data.h" #include "data.h"
@@ -10,7 +10,7 @@
// Message history (ring). // Message history (ring).
#define CHAT_HISTORY_SZ 128 #define CHAT_HISTORY_SZ 128
extern MsgData* chat_history[CHAT_HISTORY_SZ]; 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. // Add message to history ring.
MsgData* chat_history_msg_add(MsgData* msg); 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. // Get a list of messages in order.
MsgData** chat_history_nice(void); MsgData** chat_history_nice(void);
/** /**
* cb_chat - libwebsockets protocol callback for COMS chat. * cb_chat - libwebsockets protocol callback for COMS chat.
* *
@@ -38,4 +37,6 @@ int cb_chat(
size_t len size_t len
); );
#endif // CHAT_H
#endif

View File

@@ -1,12 +1,13 @@
#ifndef SESSION_H #ifndef SESSION__H
#define SESSION_H #define SESSION__H
#include <libwebsockets.h> #include <libwebsockets.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "data.h"
// Includes terminating null. // Includes terminating null.
#define SESSION_USERNAME_MAX_LEN 32
#define SESSION_CHAT_BUF_SIZE 32768 #define SESSION_CHAT_BUF_SIZE 32768
/** /**
@@ -17,9 +18,10 @@ typedef struct SESSION {
struct lws* wsi; // Libwebsockets connection handle. struct lws* wsi; // Libwebsockets connection handle.
uint64_t id; // Unique session ID. uint64_t id; // Unique session ID.
struct SESSION* next; // Next session in the internal list. struct SESSION* next; // Next session in the internal list.
char name[SESSION_USERNAME_MAX_LEN]; // Stored username. Name name; // Stored name.
bool named; // True once username is set. bool named; // True once name is set.
unsigned char buf[LWS_PRE + SESSION_CHAT_BUF_SIZE]; // Outgoing buffer per session unsigned char
buf[LWS_PRE + SESSION_CHAT_BUF_SIZE]; // Outgoing buffer per session
size_t buf_len; // Length of data in buf size_t buf_len; // Length of data in buf
} Session; } Session;
@@ -58,31 +60,31 @@ Session* session_get_head(void);
void session_broadcast(void (*cb)(Session* sess, void* user), void* user); void session_broadcast(void (*cb)(Session* sess, void* user), void* user);
/** /**
* session_set_username * session_set_name
* Store a username in the given session. * Store a name with the given session.
* *
* @param sess The session to update. * @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 * session_get_name
* Fetch the username stored in the session. * Fetch the name stored in the session.
* *
* @param sess The session to query. * @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 * session_has_name
* Check whether a session has already set a username. * Check whether a session has already set a name.
* *
* @param sess The session to query. * @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 * session_get_id
@@ -93,4 +95,4 @@ bool session_has_username(const Session* sess);
*/ */
uint64_t session_get_id(const Session* sess); uint64_t session_get_id(const Session* sess);
#endif // SESSION_H #endif

View File

@@ -1,4 +1,6 @@
#include "include/session.h" #include "include/session.h"
#include "include/data.h"
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -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); } for (Session* iter = head; iter; iter = iter->next) { cb(iter, user); }
} }
void session_set_username(Session* sess, const char* username) { void session_set_name(Session* sess, const char* name) {
if (!sess || !username) { return; } if (!sess || !name) { return; }
strncpy(sess->name, username, SESSION_USERNAME_MAX_LEN - 1); strncpy(sess->name, name, NAME_MAX_LENGTH - 1);
sess->name[SESSION_USERNAME_MAX_LEN - 1] = '\0'; sess->name[NAME_MAX_LENGTH - 1] = '\0';
sess->named = true; sess->named = true;
} }
const char* session_get_username(const Session* sess) { const char* session_get_name(const Session* sess) {
if (!sess) { return NULL; } if (!sess) { return NULL; }
return sess->name; return sess->name;
} }
bool session_has_username(const Session* sess) { bool session_has_name(const Session* sess) {
return sess ? sess->named : false; return sess ? sess->named : false;
} }