comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:aa723e3948a4
1 /*
2 * wcc -- a shitty sockchat client
3 *
4 * Copyright (c) 2025 Paper
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25 #include "flashii.h"
26
27 #include <libwebsockets.h>
28 #include <assert.h>
29
30 #define HEARTBEAT_SECONDS (30)
31
32 /* internal linked list of messages to send */
33 struct flashii_internal_msgbuf {
34 struct flashii_internal_msgbuf *next;
35
36 char msg[]; /* meat */
37 };
38
39 struct flashii {
40 struct lws_context *ctx;
41 struct lws *lws;
42
43 struct flashii_internal_msgbuf *msgbuf;
44
45 flashii_msg_recv_spec msg_recv;
46 flashii_user_recv_spec user_recv;
47
48 /* user ID, retrieved after auth success */
49 char *id;
50 /* username */
51 char *username;
52 /* color */
53 /* char *color; -- don't care */
54
55 /* permissions
56 * odd, they can be separated by \f or ' ',
57 * so if we want to save them we'll have to write mempbrk */
58
59 /* maybe save channel name too? */
60
61 /* maximum message length in unicode characters */
62 int32_t max_msg_length;
63
64 time_t last_heartbeat;
65 int need_heartbeat;
66 };
67
68 static char *memduptostr(const char *in, size_t len)
69 {
70 char *q = malloc(len + 1);
71 if (!q)
72 return NULL;
73
74 memcpy(q, in, len);
75 q[len] = 0;
76
77 return q;
78 }
79
80 /* converts a string-like thing to i64 */
81 static int memtoi64(const char *mem, size_t len, int64_t *pr)
82 {
83 size_t i;
84 int64_t r;
85 int neg;
86
87 if (len <= 0)
88 return -1;
89
90 if (!(mem[0] == '-' || (mem[0] >= '0' && mem[0] <= '9')))
91 return -1;
92
93 r = 0;
94
95 neg = (mem[0] == '-');
96
97 for (i = (neg ? 1 : 0); i < len; i++) {
98 /* FIXME this does absolutely no checking for overflow etc */
99 if (!(mem[i] >= '0' && mem[i] <= '9'))
100 return -1;
101
102 r *= 10;
103 r += (mem[i] - '0');
104 }
105
106 if (mem[0] == '-')
107 r = -r;
108
109 *pr = r;
110
111 return 0;
112 }
113
114 /* converts a string-like thing to i32 */
115 static int memtoi32(const char *mem, size_t len, int32_t *pr)
116 {
117 int64_t r;
118 if (memtoi64(mem, len, &r) < 0)
119 return -1;
120
121 if (r > INT32_MAX || r < INT32_MIN)
122 return -1;
123
124 *pr = r;
125 return 0;
126 }
127
128 /* converts a binary string to u32
129 *
130 * e.x.:
131 * '10010' -> 0x09 */
132 static int memtou32flags(const char *mem, size_t len, uint32_t *pflags)
133 {
134 size_t i;
135 uint32_t u;
136
137 if (len >= 32)
138 return -1; /* totally funtastic! */
139
140 u = 0;
141
142 for (i = 0; i < len; i++) {
143 switch (mem[i]) {
144 case '0':
145 break;
146 case '1':
147 u |= (UINT32_C(1) << i);
148 break;
149 default:
150 /* invalid input */
151 return -1;
152 }
153 }
154
155 *pflags = u;
156
157 return 0;
158 }
159
160 /* ------------------------------------------------------------------------ */
161 /* packet receiving and parsing.
162 *
163 * packets are generally received as
164 * [type] (optional subtype) (data)
165 * hence I've put together a simple filter thing to be able to handle
166 * types and subtypes without duplicating a shit ton of code */
167
168 struct flashii_receive_packet_filter {
169 const char *prefix;
170 size_t len;
171 int (*func)(struct flashii *fls, const char *in, size_t len);
172 };
173
174 #define FLASHII_RECEIVE_PACKET_FILTER(prefix, func) \
175 { (prefix "\t"), sizeof(prefix), (func) }
176
177 static int flashii_receive_packet_filter(struct flashii *fls, const char *in,
178 size_t len, struct flashii_receive_packet_filter *filters)
179 {
180 struct flashii_receive_packet_filter *f;
181
182 for (f = filters; f->prefix; f++) {
183 if (f->len > len || memcmp(in, f->prefix, f->len))
184 continue; /* nope */
185
186 return f->func(fls, in + f->len, len - f->len);
187 }
188
189 return -1;
190 }
191
192 /* ------------------------------------------------------------------------ */
193 /* enumerate over parts of a packet
194 * note that whatever returned to the callback is not guaranteed to be
195 * NUL terminated; you should treat it simply as a memory buffer */
196
197 /* XXX needs to take in delim */
198 static int flashii_receive_packet_enum(struct flashii *fls, const char *in,
199 size_t len,
200 int (*cb)(struct flashii *fls,
201 const char *in, size_t len, size_t index, void *userdata),
202 void *userdata)
203 {
204 size_t index;
205
206 for (index = 0; /* none */; index++) {
207 size_t ilen;
208
209 {
210 const char *x;
211 x = memchr(in, '\t', len);
212 ilen = x ? (x - in) : len;
213 }
214
215 //printf("%d: %.*s\n", (int)index, (int)ilen, in);
216
217 if (cb(fls, in, ilen, index, userdata) < 0)
218 return -1;
219
220 /* hopefully this is right */
221 if (ilen + 1 >= len)
222 break;
223
224 in = in + ilen + 1;
225 len -= ilen + 1;
226 }
227
228 return index;
229 }
230
231 /* ------------------------------------------------------------------------ */
232 /* 0: pong */
233
234 static int flashii_receive_packet_pong(struct flashii *fls, const char *in,
235 size_t len)
236 {
237 /* nothing to do */
238 return 0;
239 }
240
241 /* ------------------------------------------------------------------------ */
242 /* 1: auth */
243
244 static int flashii_receive_auth_cb(struct flashii *fls, const char *in,
245 size_t len, size_t index, void *userdata)
246 {
247 switch (index) {
248 case 1:
249 case 2:
250 case 3:
251 case 4:
252 return 0; /* nothing */
253 case 0:
254 fls->id = memduptostr(in, len);
255 /* user ID */
256 return 0;
257 case 5:
258 if (memtoi32(in, len, &fls->max_msg_length) < 0)
259 return -1;
260
261 return 0;
262 }
263
264 return -1;
265
266 (void)userdata;
267 }
268
269 static int flashii_receive_packet_auth_success(struct flashii *fls,
270 const char *in, size_t len)
271 {
272 return (flashii_receive_packet_enum(fls, in, len, flashii_receive_auth_cb, NULL) != 5) ? -1 : 0;
273 }
274
275 static int flashii_receive_packet_auth(struct flashii *fls, const char *in,
276 size_t len)
277 {
278 struct flashii_receive_packet_filter f[] = {
279 FLASHII_RECEIVE_PACKET_FILTER("y", flashii_receive_packet_auth_success),
280 /* FLASHII_RECEIVE_PACKET_FILTER("n", flashii_receive_packet_auth_failure), */
281 /* FLASHII_RECEIVE_PACKET_FILTER("#", flashii_receive_packet_user_add), */
282 /* terminate */
283 { 0 }
284 };
285
286 return flashii_receive_packet_filter(fls, in, len, f);
287 }
288
289 /* ------------------------------------------------------------------------ */
290 /* 2: message add */
291
292 static int flashii_receive_packet_msg_cb(struct flashii *fls, const char *in,
293 size_t len, size_t index, void *userdata)
294 {
295 struct flashii_msg *msg = userdata;
296
297 switch (index) {
298 case 0:
299 /* UNIX timestamp */
300 if (memtoi64(in, len, &msg->timestamp) < 0)
301 return -1;
302 return 0;
303 case 1:
304 msg->id = memduptostr(in, len);
305 return 0;
306 case 2:
307 msg->body = memduptostr(in, len);
308 return 0;
309 case 3:
310 msg->msg_id = memduptostr(in, len);
311 return 0;
312 case 4:
313 if (memtou32flags(in, len, &msg->flags) < 0)
314 return -1;
315 return 0;
316 }
317
318 return -1;
319 }
320
321 static int flashii_receive_packet_msg(struct flashii *fls, const char *in,
322 size_t len)
323 {
324 struct flashii_msg msg;
325
326 if (flashii_receive_packet_enum(fls, in, len, flashii_receive_packet_msg_cb, &msg) != 4)
327 return -1;
328
329 fls->msg_recv(fls, &msg);
330 return 0;
331 }
332
333 /* ------------------------------------------------------------------------ */
334 /* 7: context */
335
336 static int flashii_receive_packet_context_user_list_cb(struct flashii *fls,
337 const char *in, size_t len, size_t index, void *userdata)
338 {
339 struct flashii_user *user = userdata;
340
341 if (!index)
342 return 0; /* ignored */
343
344 switch ((index - 1) % 5) {
345 case 0:
346 /* user ID */
347 user->id = memduptostr(in, len);
348 return 0;
349 case 1:
350 /* user name */
351 user->name = memduptostr(in, len);
352 return 0;
353 case 2:
354 /* color */
355 return 0;
356 case 3:
357 /* perms */
358 return 0;
359 case 4:
360 /* visiblility */
361 if (memtoi32(in, len, &user->visible) < 0)
362 return -1;
363 fls->user_recv(fls, user);
364 return 0;
365 }
366
367 return -1;
368 }
369
370 static int flashii_receive_packet_context_user_list(struct flashii *fls,
371 const char *in, size_t len)
372 {
373 struct flashii_user user;
374
375 if (flashii_receive_packet_enum(fls, in, len, flashii_receive_packet_context_user_list_cb, &user) < 0)
376 return -1;
377
378 return 0;
379 }
380
381 static int flashii_receive_packet_context_msg_add_cb(struct flashii *fls,
382 const char *in, size_t len, size_t index, void *userdata)
383 {
384 struct flashii_msg *msg = userdata;
385
386 switch (index) {
387 case 0:
388 /* UNIX timestamp */
389 if (memtoi64(in, len, &msg->timestamp) < 0)
390 return -1;
391 return 0;
392 case 1:
393 msg->id = memduptostr(in, len);
394 return 0;
395 case 2:
396 /* "user name" wtf */
397 return 0;
398 case 3:
399 /* color */
400 return 0;
401 case 4:
402 /* user permissions */
403 return 0;
404 case 5:
405 msg->body = memduptostr(in, len);
406 return 0;
407 case 6:
408 msg->msg_id = memduptostr(in, len);
409 return 0;
410 case 7:
411 /* notifications */
412 return 0;
413 case 8:
414 #if 0
415 if (memtou32flags(in, len, &msg->flags) < 0)
416 return -1;
417 #endif
418 return 0;
419 }
420
421 return -1;
422 }
423
424 static int flashii_receive_packet_context_message_add(struct flashii *fls,
425 const char *in, size_t len)
426 {
427 struct flashii_msg msg;
428
429 if (flashii_receive_packet_enum(fls, in, len, flashii_receive_packet_context_msg_add_cb, &msg) < 0)
430 return -1;
431
432 fls->msg_recv(fls, &msg);
433 return 0;
434 }
435
436 static int flashii_receive_packet_context(struct flashii *fls, const char *in,
437 size_t len)
438 {
439 struct flashii_receive_packet_filter f[] = {
440 FLASHII_RECEIVE_PACKET_FILTER("0", flashii_receive_packet_context_user_list),
441 FLASHII_RECEIVE_PACKET_FILTER("1", flashii_receive_packet_context_message_add),
442 /* FLASHII_RECEIVE_PACKET_FILTER("2", ...), */
443 /* terminate */
444 { 0 }
445 };
446
447 return flashii_receive_packet_filter(fls, in, len, f);
448 }
449
450 /* ------------------------------------------------------------------------ */
451
452 static int flashii_receive_packet(struct flashii *fls, const char *in,
453 size_t len)
454 {
455 struct flashii_receive_packet_filter f[] = {
456 FLASHII_RECEIVE_PACKET_FILTER("0", flashii_receive_packet_pong),
457 FLASHII_RECEIVE_PACKET_FILTER("1", flashii_receive_packet_auth),
458 FLASHII_RECEIVE_PACKET_FILTER("2", flashii_receive_packet_msg),
459 FLASHII_RECEIVE_PACKET_FILTER("7", flashii_receive_packet_context),
460 /* terminate */
461 { 0 }
462 };
463
464 return flashii_receive_packet_filter(fls, in, len, f);
465 }
466
467 /* ------------------------------------------------------------------------ */
468
469 static int flashii_send_vpacket(struct flashii *fls, const char *fmt, va_list ap)
470 {
471 /* this uses vsnprintf to assemble the packet and sends it off */
472 int r;
473 char *s;
474 va_list ap2;
475
476 va_copy(ap2, ap);
477
478 r = vsnprintf(NULL, 0, fmt, ap);
479
480 if (r < 0) {
481 va_end(ap2);
482 return -1;
483 }
484
485 s = malloc(LWS_PRE + r + 1);
486 if (!s) {
487 va_end(ap2);
488 return -1;
489 }
490
491 r = vsnprintf(s + LWS_PRE, r + 1, fmt, ap2);
492
493 va_end(ap2);
494
495 if (r < 0)
496 return -1;
497
498 lws_write(fls->lws, s + LWS_PRE, r, LWS_WRITE_TEXT);
499 free(s);
500
501 return 0;
502 }
503
504 static int flashii_send_packet(struct flashii *fls, const char *fmt, ...)
505 {
506 va_list ap;
507 int r;
508
509 va_start(ap, fmt);
510 r = flashii_send_vpacket(fls, fmt, ap);
511 va_end(ap);
512
513 return r;
514 }
515
516 static int flashii_send_ping(struct flashii *fls, const char *id)
517 {
518 assert(id);
519 return flashii_send_packet(fls, "0\t%s", id);
520 }
521
522 static int flashii_send_auth(struct flashii *fls, const char *scheme, const char *args)
523 {
524 assert(scheme);
525 assert(args);
526 return flashii_send_packet(fls, "1\t%s\t%s", scheme, args);
527 }
528
529 static int flashii_send_msg(struct flashii *fls, const char *id, const char *msg)
530 {
531 assert(id);
532 assert(msg);
533 return flashii_send_packet(fls, "2\t%s\t%s", id, msg);
534 }
535
536 static int flashii_process_msgbuf(struct flashii *fls)
537 {
538 while (fls->msgbuf) {
539 struct flashii_internal_msgbuf *tmp;
540
541 tmp = fls->msgbuf;
542 flashii_send_msg(fls, fls->id, tmp->msg);
543 fls->msgbuf = tmp->next;
544 free(tmp);
545 }
546 }
547
548 /* libwebsockets calls us back here */
549 static int flashii_cb(struct lws *wsi, enum lws_callback_reasons reason,
550 void *userdata, void *in, size_t len)
551 {
552 struct flashii *fls = (struct flashii *)userdata;
553 static const unsigned char token[] = {
554 #embed "token.txt"
555 , 0
556 };
557
558 switch (reason) {
559 case LWS_CALLBACK_CLIENT_ESTABLISHED:
560 lws_callback_on_writable(wsi);
561 break;
562 case LWS_CALLBACK_CLIENT_RECEIVE:
563 /* incoming message -- print it out
564 * we should handle it based on the type */
565 flashii_receive_packet(fls, in, len);
566 break;
567 case LWS_CALLBACK_CLIENT_WRITEABLE:
568 if (fls->need_heartbeat) {
569 flashii_send_ping(fls, fls->id);
570 fls->need_heartbeat = 0;
571 }
572
573 /* process the buffer of messages, if any */
574 flashii_process_msgbuf(fls);
575
576 flashii_send_auth(fls, "Misuzu", token);
577 /* send message here using lws_write */
578 break;
579 case LWS_CALLBACK_CLIENT_CLOSED:
580 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
581 break;
582 #if 0
583 default:
584 printf("UNKNOWN REASON: %d\n", reason);
585 break;
586 #endif
587 }
588
589 return lws_callback_http_dummy(wsi, reason, userdata, in, len);
590 }
591
592 static const struct lws_protocols protocols[] = {
593 {
594 "sockchat",
595 flashii_cb,
596 sizeof(struct flashii),
597 0,
598 },
599 { 0 } /* terminator */
600 };
601
602 /* ------------------------------------------------------------------------ */
603
604 struct flashii *flashii_init(const char *protocol, const char *address,
605 uint16_t port, flashii_msg_recv_spec msg_recv,
606 flashii_user_recv_spec user_recv)
607 {
608 struct lws_context_creation_info info;
609 struct lws_client_connect_info ccinfo;
610 struct flashii *fls;
611
612 fls = calloc(1, sizeof(*fls));
613 if (!fls)
614 return NULL;
615
616 fls->msg_recv = msg_recv;
617 fls->user_recv = user_recv;
618
619 lws_context_info_defaults(&info, NULL);
620 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
621 info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
622 info.protocols = protocols;
623
624 fls->ctx = lws_create_context(&info);
625 if (!fls->ctx) {
626 lwsl_err("lws init failed\n");
627 free(fls);
628 return NULL;
629 }
630
631 /* konekuto */
632 memset(&ccinfo, 0, sizeof(ccinfo));
633
634 ccinfo.context = fls->ctx;
635 ccinfo.address = address;
636 ccinfo.port = port;
637 ccinfo.path = "/";
638 ccinfo.host = address;
639 ccinfo.origin = address;
640 ccinfo.protocol = "sockchat";
641 ccinfo.ssl_connection = LCCSCF_USE_SSL;
642 ccinfo.userdata = fls;
643
644 fls->lws = lws_client_connect_via_info(&ccinfo);
645 if (!fls->lws) {
646 lwsl_cx_err(fls->ctx, "lws connect failed\n");
647 lws_context_destroy(fls->ctx);
648 free(fls);
649 return NULL;
650 }
651
652 return fls;
653 }
654
655 /* worker
656 *
657 * this should be called in an event loop, on the same thread that
658 * initialized the structure.
659 *
660 * `noblock` is a boolean saying... whether we should block :) */
661 void flashii_work(struct flashii *fls, int noblock)
662 {
663 time_t thetime;
664
665 time(&thetime);
666
667 if (fls->id) {
668 if (difftime(thetime, fls->last_heartbeat) >= 30.0) {
669 fls->need_heartbeat = 1;
670 fls->last_heartbeat = thetime;
671 lws_callback_on_writable(fls->lws);
672 }
673 }
674
675 lws_service(fls->ctx, (noblock) ? -1 : 0);
676 }
677
678 int flashii_send_message(struct flashii *fls, const char *msg)
679 {
680 /* append to the internal linked list of messages */
681 size_t len = strlen(msg);
682
683 struct flashii_internal_msgbuf *msgbuf = malloc(sizeof(*msgbuf) + len + 1);
684 if (!msgbuf)
685 return -1; /* WHAT */
686
687 msgbuf->next = NULL;
688
689 /* copy it allll in (includes NUL terminator) */
690 memcpy(msgbuf->msg, msg, len + 1);
691
692 if (fls->msgbuf) {
693 struct flashii_internal_msgbuf *tail = fls->msgbuf;
694 while (tail->next)
695 tail = tail->next;
696 tail->next = msgbuf;
697 } else {
698 fls->msgbuf = msgbuf;
699 }
700
701 lws_callback_on_writable(fls->lws);
702
703 return 0; /* yay */
704 }