Server handles join packets nicely.
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user