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 }}
*/
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.

View File

@@ -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;

View File

@@ -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,51 +91,32 @@ 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 : '?';
Packet* packet = packet_parse(in, len);
if (!packet) {
lwsl_warn("Received bad packet: \"%s\".\n", (char*)in);
break;
}
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_val* wroot = yyjson_mut_obj(wdoc);
@@ -127,7 +133,7 @@ int cb_chat(
yyjson_mut_obj_add_val(wdoc, wdata, "users", wusers);
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);
/* add each user once */
yyjson_mut_arr_add_val(wusers, uobj);
@@ -135,7 +141,7 @@ int cb_chat(
wdoc, uobj, "id", session_get_id(s)
);
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);
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_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_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);
@@ -184,7 +176,7 @@ int cb_chat(
jdoc, jdata, "id", session_get_id(sess)
);
yyjson_mut_obj_add_str(
jdoc, jdata, "username", session_get_username(sess)
jdoc, jdata, "username", session_get_name(sess)
);
size_t out_len;
@@ -200,7 +192,15 @@ int cb_chat(
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)) {
@@ -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);
@@ -371,6 +371,7 @@ int cb_chat(
yyjson_doc_free(doc);
break;
}
#endif
case LWS_CALLBACK_SERVER_WRITEABLE:
if (sess && sess->buf_len > 0) {

View File

@@ -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.

View File

@@ -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

View File

@@ -1,12 +1,13 @@
#ifndef SESSION_H
#define SESSION_H
#ifndef SESSION__H
#define SESSION__H
#include <libwebsockets.h>
#include <stdbool.h>
#include <stdint.h>
#include "data.h"
// Includes terminating null.
#define SESSION_USERNAME_MAX_LEN 32
#define SESSION_CHAT_BUF_SIZE 32768
/**
@@ -17,9 +18,10 @@ 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
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

View File

@@ -1,4 +1,6 @@
#include "include/session.h"
#include "include/data.h"
#include <stdint.h>
#include <stdlib.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); }
}
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;
}