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