Mercurial > wcc
view flashii.c @ 0:aa723e3948a4 default tip
*: initial commit
awesome
| author | Paper <paper@tflc.us> |
|---|---|
| date | Tue, 09 Sep 2025 00:29:57 -0400 |
| parents | |
| children |
line wrap: on
line source
/* * wcc -- a shitty sockchat client * * Copyright (c) 2025 Paper * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "flashii.h" #include <libwebsockets.h> #include <assert.h> #define HEARTBEAT_SECONDS (30) /* internal linked list of messages to send */ struct flashii_internal_msgbuf { struct flashii_internal_msgbuf *next; char msg[]; /* meat */ }; struct flashii { struct lws_context *ctx; struct lws *lws; struct flashii_internal_msgbuf *msgbuf; flashii_msg_recv_spec msg_recv; flashii_user_recv_spec user_recv; /* user ID, retrieved after auth success */ char *id; /* username */ char *username; /* color */ /* char *color; -- don't care */ /* permissions * odd, they can be separated by \f or ' ', * so if we want to save them we'll have to write mempbrk */ /* maybe save channel name too? */ /* maximum message length in unicode characters */ int32_t max_msg_length; time_t last_heartbeat; int need_heartbeat; }; static char *memduptostr(const char *in, size_t len) { char *q = malloc(len + 1); if (!q) return NULL; memcpy(q, in, len); q[len] = 0; return q; } /* converts a string-like thing to i64 */ static int memtoi64(const char *mem, size_t len, int64_t *pr) { size_t i; int64_t r; int neg; if (len <= 0) return -1; if (!(mem[0] == '-' || (mem[0] >= '0' && mem[0] <= '9'))) return -1; r = 0; neg = (mem[0] == '-'); for (i = (neg ? 1 : 0); i < len; i++) { /* FIXME this does absolutely no checking for overflow etc */ if (!(mem[i] >= '0' && mem[i] <= '9')) return -1; r *= 10; r += (mem[i] - '0'); } if (mem[0] == '-') r = -r; *pr = r; return 0; } /* converts a string-like thing to i32 */ static int memtoi32(const char *mem, size_t len, int32_t *pr) { int64_t r; if (memtoi64(mem, len, &r) < 0) return -1; if (r > INT32_MAX || r < INT32_MIN) return -1; *pr = r; return 0; } /* converts a binary string to u32 * * e.x.: * '10010' -> 0x09 */ static int memtou32flags(const char *mem, size_t len, uint32_t *pflags) { size_t i; uint32_t u; if (len >= 32) return -1; /* totally funtastic! */ u = 0; for (i = 0; i < len; i++) { switch (mem[i]) { case '0': break; case '1': u |= (UINT32_C(1) << i); break; default: /* invalid input */ return -1; } } *pflags = u; return 0; } /* ------------------------------------------------------------------------ */ /* packet receiving and parsing. * * packets are generally received as * [type] (optional subtype) (data) * hence I've put together a simple filter thing to be able to handle * types and subtypes without duplicating a shit ton of code */ struct flashii_receive_packet_filter { const char *prefix; size_t len; int (*func)(struct flashii *fls, const char *in, size_t len); }; #define FLASHII_RECEIVE_PACKET_FILTER(prefix, func) \ { (prefix "\t"), sizeof(prefix), (func) } static int flashii_receive_packet_filter(struct flashii *fls, const char *in, size_t len, struct flashii_receive_packet_filter *filters) { struct flashii_receive_packet_filter *f; for (f = filters; f->prefix; f++) { if (f->len > len || memcmp(in, f->prefix, f->len)) continue; /* nope */ return f->func(fls, in + f->len, len - f->len); } return -1; } /* ------------------------------------------------------------------------ */ /* enumerate over parts of a packet * note that whatever returned to the callback is not guaranteed to be * NUL terminated; you should treat it simply as a memory buffer */ /* XXX needs to take in delim */ static int flashii_receive_packet_enum(struct flashii *fls, const char *in, size_t len, int (*cb)(struct flashii *fls, const char *in, size_t len, size_t index, void *userdata), void *userdata) { size_t index; for (index = 0; /* none */; index++) { size_t ilen; { const char *x; x = memchr(in, '\t', len); ilen = x ? (x - in) : len; } //printf("%d: %.*s\n", (int)index, (int)ilen, in); if (cb(fls, in, ilen, index, userdata) < 0) return -1; /* hopefully this is right */ if (ilen + 1 >= len) break; in = in + ilen + 1; len -= ilen + 1; } return index; } /* ------------------------------------------------------------------------ */ /* 0: pong */ static int flashii_receive_packet_pong(struct flashii *fls, const char *in, size_t len) { /* nothing to do */ return 0; } /* ------------------------------------------------------------------------ */ /* 1: auth */ static int flashii_receive_auth_cb(struct flashii *fls, const char *in, size_t len, size_t index, void *userdata) { switch (index) { case 1: case 2: case 3: case 4: return 0; /* nothing */ case 0: fls->id = memduptostr(in, len); /* user ID */ return 0; case 5: if (memtoi32(in, len, &fls->max_msg_length) < 0) return -1; return 0; } return -1; (void)userdata; } static int flashii_receive_packet_auth_success(struct flashii *fls, const char *in, size_t len) { return (flashii_receive_packet_enum(fls, in, len, flashii_receive_auth_cb, NULL) != 5) ? -1 : 0; } static int flashii_receive_packet_auth(struct flashii *fls, const char *in, size_t len) { struct flashii_receive_packet_filter f[] = { FLASHII_RECEIVE_PACKET_FILTER("y", flashii_receive_packet_auth_success), /* FLASHII_RECEIVE_PACKET_FILTER("n", flashii_receive_packet_auth_failure), */ /* FLASHII_RECEIVE_PACKET_FILTER("#", flashii_receive_packet_user_add), */ /* terminate */ { 0 } }; return flashii_receive_packet_filter(fls, in, len, f); } /* ------------------------------------------------------------------------ */ /* 2: message add */ static int flashii_receive_packet_msg_cb(struct flashii *fls, const char *in, size_t len, size_t index, void *userdata) { struct flashii_msg *msg = userdata; switch (index) { case 0: /* UNIX timestamp */ if (memtoi64(in, len, &msg->timestamp) < 0) return -1; return 0; case 1: msg->id = memduptostr(in, len); return 0; case 2: msg->body = memduptostr(in, len); return 0; case 3: msg->msg_id = memduptostr(in, len); return 0; case 4: if (memtou32flags(in, len, &msg->flags) < 0) return -1; return 0; } return -1; } static int flashii_receive_packet_msg(struct flashii *fls, const char *in, size_t len) { struct flashii_msg msg; if (flashii_receive_packet_enum(fls, in, len, flashii_receive_packet_msg_cb, &msg) != 4) return -1; fls->msg_recv(fls, &msg); return 0; } /* ------------------------------------------------------------------------ */ /* 7: context */ static int flashii_receive_packet_context_user_list_cb(struct flashii *fls, const char *in, size_t len, size_t index, void *userdata) { struct flashii_user *user = userdata; if (!index) return 0; /* ignored */ switch ((index - 1) % 5) { case 0: /* user ID */ user->id = memduptostr(in, len); return 0; case 1: /* user name */ user->name = memduptostr(in, len); return 0; case 2: /* color */ return 0; case 3: /* perms */ return 0; case 4: /* visiblility */ if (memtoi32(in, len, &user->visible) < 0) return -1; fls->user_recv(fls, user); return 0; } return -1; } static int flashii_receive_packet_context_user_list(struct flashii *fls, const char *in, size_t len) { struct flashii_user user; if (flashii_receive_packet_enum(fls, in, len, flashii_receive_packet_context_user_list_cb, &user) < 0) return -1; return 0; } static int flashii_receive_packet_context_msg_add_cb(struct flashii *fls, const char *in, size_t len, size_t index, void *userdata) { struct flashii_msg *msg = userdata; switch (index) { case 0: /* UNIX timestamp */ if (memtoi64(in, len, &msg->timestamp) < 0) return -1; return 0; case 1: msg->id = memduptostr(in, len); return 0; case 2: /* "user name" wtf */ return 0; case 3: /* color */ return 0; case 4: /* user permissions */ return 0; case 5: msg->body = memduptostr(in, len); return 0; case 6: msg->msg_id = memduptostr(in, len); return 0; case 7: /* notifications */ return 0; case 8: #if 0 if (memtou32flags(in, len, &msg->flags) < 0) return -1; #endif return 0; } return -1; } static int flashii_receive_packet_context_message_add(struct flashii *fls, const char *in, size_t len) { struct flashii_msg msg; if (flashii_receive_packet_enum(fls, in, len, flashii_receive_packet_context_msg_add_cb, &msg) < 0) return -1; fls->msg_recv(fls, &msg); return 0; } static int flashii_receive_packet_context(struct flashii *fls, const char *in, size_t len) { struct flashii_receive_packet_filter f[] = { FLASHII_RECEIVE_PACKET_FILTER("0", flashii_receive_packet_context_user_list), FLASHII_RECEIVE_PACKET_FILTER("1", flashii_receive_packet_context_message_add), /* FLASHII_RECEIVE_PACKET_FILTER("2", ...), */ /* terminate */ { 0 } }; return flashii_receive_packet_filter(fls, in, len, f); } /* ------------------------------------------------------------------------ */ static int flashii_receive_packet(struct flashii *fls, const char *in, size_t len) { struct flashii_receive_packet_filter f[] = { FLASHII_RECEIVE_PACKET_FILTER("0", flashii_receive_packet_pong), FLASHII_RECEIVE_PACKET_FILTER("1", flashii_receive_packet_auth), FLASHII_RECEIVE_PACKET_FILTER("2", flashii_receive_packet_msg), FLASHII_RECEIVE_PACKET_FILTER("7", flashii_receive_packet_context), /* terminate */ { 0 } }; return flashii_receive_packet_filter(fls, in, len, f); } /* ------------------------------------------------------------------------ */ static int flashii_send_vpacket(struct flashii *fls, const char *fmt, va_list ap) { /* this uses vsnprintf to assemble the packet and sends it off */ int r; char *s; va_list ap2; va_copy(ap2, ap); r = vsnprintf(NULL, 0, fmt, ap); if (r < 0) { va_end(ap2); return -1; } s = malloc(LWS_PRE + r + 1); if (!s) { va_end(ap2); return -1; } r = vsnprintf(s + LWS_PRE, r + 1, fmt, ap2); va_end(ap2); if (r < 0) return -1; lws_write(fls->lws, s + LWS_PRE, r, LWS_WRITE_TEXT); free(s); return 0; } static int flashii_send_packet(struct flashii *fls, const char *fmt, ...) { va_list ap; int r; va_start(ap, fmt); r = flashii_send_vpacket(fls, fmt, ap); va_end(ap); return r; } static int flashii_send_ping(struct flashii *fls, const char *id) { assert(id); return flashii_send_packet(fls, "0\t%s", id); } static int flashii_send_auth(struct flashii *fls, const char *scheme, const char *args) { assert(scheme); assert(args); return flashii_send_packet(fls, "1\t%s\t%s", scheme, args); } static int flashii_send_msg(struct flashii *fls, const char *id, const char *msg) { assert(id); assert(msg); return flashii_send_packet(fls, "2\t%s\t%s", id, msg); } static int flashii_process_msgbuf(struct flashii *fls) { while (fls->msgbuf) { struct flashii_internal_msgbuf *tmp; tmp = fls->msgbuf; flashii_send_msg(fls, fls->id, tmp->msg); fls->msgbuf = tmp->next; free(tmp); } } /* libwebsockets calls us back here */ static int flashii_cb(struct lws *wsi, enum lws_callback_reasons reason, void *userdata, void *in, size_t len) { struct flashii *fls = (struct flashii *)userdata; static const unsigned char token[] = { #embed "token.txt" , 0 }; switch (reason) { case LWS_CALLBACK_CLIENT_ESTABLISHED: lws_callback_on_writable(wsi); break; case LWS_CALLBACK_CLIENT_RECEIVE: /* incoming message -- print it out * we should handle it based on the type */ flashii_receive_packet(fls, in, len); break; case LWS_CALLBACK_CLIENT_WRITEABLE: if (fls->need_heartbeat) { flashii_send_ping(fls, fls->id); fls->need_heartbeat = 0; } /* process the buffer of messages, if any */ flashii_process_msgbuf(fls); flashii_send_auth(fls, "Misuzu", token); /* send message here using lws_write */ break; case LWS_CALLBACK_CLIENT_CLOSED: case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: break; #if 0 default: printf("UNKNOWN REASON: %d\n", reason); break; #endif } return lws_callback_http_dummy(wsi, reason, userdata, in, len); } static const struct lws_protocols protocols[] = { { "sockchat", flashii_cb, sizeof(struct flashii), 0, }, { 0 } /* terminator */ }; /* ------------------------------------------------------------------------ */ struct flashii *flashii_init(const char *protocol, const char *address, uint16_t port, flashii_msg_recv_spec msg_recv, flashii_user_recv_spec user_recv) { struct lws_context_creation_info info; struct lws_client_connect_info ccinfo; struct flashii *fls; fls = calloc(1, sizeof(*fls)); if (!fls) return NULL; fls->msg_recv = msg_recv; fls->user_recv = user_recv; lws_context_info_defaults(&info, NULL); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; fls->ctx = lws_create_context(&info); if (!fls->ctx) { lwsl_err("lws init failed\n"); free(fls); return NULL; } /* konekuto */ memset(&ccinfo, 0, sizeof(ccinfo)); ccinfo.context = fls->ctx; ccinfo.address = address; ccinfo.port = port; ccinfo.path = "/"; ccinfo.host = address; ccinfo.origin = address; ccinfo.protocol = "sockchat"; ccinfo.ssl_connection = LCCSCF_USE_SSL; ccinfo.userdata = fls; fls->lws = lws_client_connect_via_info(&ccinfo); if (!fls->lws) { lwsl_cx_err(fls->ctx, "lws connect failed\n"); lws_context_destroy(fls->ctx); free(fls); return NULL; } return fls; } /* worker * * this should be called in an event loop, on the same thread that * initialized the structure. * * `noblock` is a boolean saying... whether we should block :) */ void flashii_work(struct flashii *fls, int noblock) { time_t thetime; time(&thetime); if (fls->id) { if (difftime(thetime, fls->last_heartbeat) >= 30.0) { fls->need_heartbeat = 1; fls->last_heartbeat = thetime; lws_callback_on_writable(fls->lws); } } lws_service(fls->ctx, (noblock) ? -1 : 0); } int flashii_send_message(struct flashii *fls, const char *msg) { /* append to the internal linked list of messages */ size_t len = strlen(msg); struct flashii_internal_msgbuf *msgbuf = malloc(sizeof(*msgbuf) + len + 1); if (!msgbuf) return -1; /* WHAT */ msgbuf->next = NULL; /* copy it allll in (includes NUL terminator) */ memcpy(msgbuf->msg, msg, len + 1); if (fls->msgbuf) { struct flashii_internal_msgbuf *tail = fls->msgbuf; while (tail->next) tail = tail->next; tail->next = msgbuf; } else { fls->msgbuf = msgbuf; } lws_callback_on_writable(fls->lws); return 0; /* yay */ }
