ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
src/web/client.c
Go to the documentation of this file.
1
6#include <emscripten.h>
7#include <emscripten/html5.h>
8#include <stdlib.h>
9#include <string.h>
10#include <stdint.h>
11
12#include "common/init.h"
13
14// JavaScript callback for sending complete ACIP packets from WASM to WebSocket
15// This will be called by the WASM transport to send complete packets (header + payload)
16// clang-format off
17EM_JS(void, js_send_raw_packet, (const uint8_t *packet_data, size_t packet_len), {
18 if (!Module.sendPacketCallback) {
19 console.error('[WASM] sendPacketCallback not registered - cannot send packet');
20 return;
21 }
22
23 // Copy complete packet from WASM memory to JavaScript
24 const packetArray = new Uint8Array(HEAPU8.buffer, packet_data, packet_len);
25 const packetCopy = new Uint8Array(packetArray);
26
27 var pktType = packetCopy.length >= 10 ? ((packetCopy[8] << 8) | packetCopy[9]) : -1;
28 console.error('[WASM->JS] Sending raw packet:', packetCopy.length, 'bytes, type=0x' + pktType.toString(16));
29
30 // Send as raw binary packet via WebSocket
31 Module.sendPacketCallback(packetCopy);
32});
33// clang-format on
34
39#include <ascii-chat/log/log.h>
51#include <ascii-chat/font.h>
52
53/* Browser terminal rendering reads the embedded font directly from WASM memory. */
54const unsigned char *get_font_default_ptr(void) {
55 return g_font_default;
56}
57
58unsigned int get_font_default_size(void) {
59 return (unsigned int)g_font_default_size;
60}
61#include <ascii-chat/common.h>
65#include <opus.h>
66#include <sodium.h>
67
68// ============================================================================
69// WASM Transport Implementation
70// ============================================================================
71
75static asciichat_error_t wasm_transport_send(acip_transport_t *transport, const void *data, size_t len) {
76
77 // Forward complete packet (header + payload) to JavaScript WebSocket bridge
78 js_send_raw_packet((const uint8_t *)data, len);
79
80 return ASCIICHAT_OK;
81}
82
83static asciichat_error_t wasm_transport_recv(acip_transport_t *transport, void **buffer, size_t *out_len,
84 void **out_allocated_buffer) {
85 // Not used - packets arrive via JavaScript callbacks
86 return SET_ERRNO(ERROR_NOT_SUPPORTED, "recv not supported on WASM transport");
87}
88
89static asciichat_error_t wasm_transport_close(acip_transport_t *transport) {
90 return ASCIICHAT_OK; // Nothing to close
91}
92
93static acip_transport_type_t wasm_transport_get_type(acip_transport_t *transport) {
94 return ACIP_TRANSPORT_WEBSOCKET; // Closest match
95}
96
97static socket_t wasm_transport_get_socket(acip_transport_t *transport) {
99}
100
101static bool wasm_transport_is_connected(acip_transport_t *transport) {
102 return true; // Always "connected" from WASM perspective
103}
104
105static const acip_transport_methods_t wasm_transport_methods = {.send = wasm_transport_send,
106 .recv = wasm_transport_recv,
107 .close = wasm_transport_close,
108 .get_type = wasm_transport_get_type,
109 .get_socket = wasm_transport_get_socket,
110 .is_connected = wasm_transport_is_connected,
111 .destroy_impl = NULL};
112
113static acip_transport_t g_wasm_transport = {.methods = &wasm_transport_methods, .crypto_ctx = NULL, .impl_data = NULL};
114
115// ============================================================================
116// Global State
117// ============================================================================
118
119// Global state for client session
120static crypto_handshake_context_t g_crypto_handshake_ctx = {0};
121static bool g_initialized = false;
122static bool g_handshake_complete = false;
123
124// Opus codec state
125static OpusEncoder *g_opus_encoder = NULL;
126static OpusDecoder *g_opus_decoder = NULL;
127
128// Connection state enum (exposed to JS)
136
137static connection_state_t g_connection_state = CONNECTION_STATE_DISCONNECTED;
138
139// ============================================================================
140// Initialization
141// ============================================================================
142
148EMSCRIPTEN_KEEPALIVE
149int client_init_with_args(const char *args_json) {
150
151 // Parse space-separated arguments
152 char *args_copy = NULL;
153 char *argv[64] = {NULL};
154 int argc = wasm_parse_args(args_json, argv, 64, &args_copy);
155 if (argc < 0) {
156 return -1;
157 }
158
159 // Initialize options (sets up RCU, defaults, etc.)
160 asciichat_error_t err = options_init(argc, argv);
161 free(args_copy);
162
163 if (g_initialized) {
164 return -1;
165 }
166
167 // Initialize platform layer
168 err = platform_init();
169 if (err != ASCIICHAT_OK) {
170 return -1;
171 }
172
173 // Initialize logging to stderr (console.error in browser)
174 log_init(NULL, LOG_DEBUG, true, false);
175 // Use debug-style format in WASM so browser console shows full log headers
177 log_info("WASM client initialized via logging system");
178
179 if (err != ASCIICHAT_OK) {
180 return -1;
181 }
182
183 // Initialize ANSI color code generation
185
186 g_initialized = true;
187 g_connection_state = CONNECTION_STATE_DISCONNECTED;
188
189 return 0;
190}
191
192EMSCRIPTEN_KEEPALIVE
193void client_cleanup(void) {
194
195 // Clean up crypto handshake context
196 crypto_handshake_destroy(&g_crypto_handshake_ctx);
197 memset(&g_crypto_handshake_ctx, 0, sizeof(g_crypto_handshake_ctx));
198
199 g_handshake_complete = false;
200 g_connection_state = CONNECTION_STATE_DISCONNECTED;
201 g_initialized = false;
204}
205
206// ============================================================================
207// Cryptography API
208// ============================================================================
209
211EMSCRIPTEN_KEEPALIVE
212asciichat_error_t client_acds_join_request(const char *session, const char *password, uint8_t *output) {
213 if (!session || !password || !output || strlen(session) == 0 || strlen(session) > 47 || strlen(password) > 127) {
214 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid discovery session or password");
215 }
216 uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
217 uint8_t secret_key[crypto_sign_SECRETKEYBYTES];
218 if (crypto_sign_keypair(public_key, secret_key) != 0) {
219 return SET_ERRNO(ERROR_CRYPTO, "Failed to create discovery identity");
220 }
221 memset(output, 0, 282);
222 size_t session_len = strlen(session);
223 output[0] = (uint8_t)session_len;
224 memcpy(output + 1, session, session_len);
225 memcpy(output + 49, public_key, sizeof(public_key));
226 uint64_t timestamp = (uint64_t)emscripten_date_now();
227 // Packed ACDS structs use host (little-endian) fields; signatures use network order.
228 memcpy(output + 145, &timestamp, sizeof(timestamp));
229 uint8_t message[9 + 48];
230 message[0] = (uint8_t)6004;
231 for (size_t i = 0; i < 8; ++i)
232 message[1 + i] = (uint8_t)(timestamp >> (56 - i * 8));
233 memcpy(message + 9, session, session_len);
234 int result = crypto_sign_detached(output + 81, NULL, message, 9 + session_len, secret_key);
235 sodium_memzero(secret_key, sizeof(secret_key));
236 if (result != 0)
237 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign discovery join");
238 output[153] = password[0] != '\0';
239 memcpy(output + 154, password, strlen(password));
240 return ASCIICHAT_OK;
241}
242
247EMSCRIPTEN_KEEPALIVE
249
250 if (!g_initialized) {
251 return -1;
252 }
253
254 // Reset handshake context if previously used (reconnection / React Strict Mode remount)
255 if (g_crypto_handshake_ctx.state != CRYPTO_HANDSHAKE_DISABLED) {
256 crypto_handshake_destroy(&g_crypto_handshake_ctx);
257 memset(&g_crypto_handshake_ctx, 0, sizeof(g_crypto_handshake_ctx));
258 g_handshake_complete = false;
259 }
260
261 // Initialize crypto handshake context
262 asciichat_error_t result = crypto_handshake_init("wasm-client", &g_crypto_handshake_ctx, false /* is_server */);
263 if (result != ASCIICHAT_OK) {
264 return -1;
265 }
266
267 g_connection_state = CONNECTION_STATE_DISCONNECTED;
268 return 0;
269}
270
277EMSCRIPTEN_KEEPALIVE
278int client_set_server_address(const char *server_host, int server_port) {
279 if (!g_initialized) {
280 return -1;
281 }
282
283 if (!server_host || server_port <= 0 || server_port > 65535) {
284 return -1;
285 }
286
287 // Set server IP and port in handshake context
288 SAFE_STRNCPY(g_crypto_handshake_ctx.server_ip, server_host, sizeof(g_crypto_handshake_ctx.server_ip));
289 g_crypto_handshake_ctx.server_port = server_port;
290
291 return 0;
292}
293
298EMSCRIPTEN_KEEPALIVE
300 if (g_crypto_handshake_ctx.state == CRYPTO_HANDSHAKE_DISABLED) {
301 return NULL;
302 }
303
304 // Get public key from handshake context
305 const uint8_t *pubkey = g_crypto_handshake_ctx.crypto_ctx.public_key;
306 if (!pubkey) {
307 return NULL;
308 }
309
310 // Allocate buffer for hex string (32 bytes = 64 hex chars + null terminator)
311 char *hex_buffer = SAFE_MALLOC(65, char *);
312 if (!hex_buffer) {
313 return NULL;
314 }
315
316 // Convert public key to hex
317 for (int i = 0; i < 32; i++) {
318 snprintf(hex_buffer + (i * 2), 3, "%02x", pubkey[i]);
319 }
320 hex_buffer[64] = '\0';
321
322 return hex_buffer;
323}
324
332EMSCRIPTEN_KEEPALIVE
333int client_handle_key_exchange_init(const uint8_t *packet, size_t packet_len) {
334
335 // Safety check: if handshake context is not in INIT state, reinitialize it
336 // This handles cases where previous handshakes weren't properly cleaned up
337 if (g_crypto_handshake_ctx.state != CRYPTO_HANDSHAKE_INIT) {
338
339 // Destroy and reset
340 if (g_crypto_handshake_ctx.state != CRYPTO_HANDSHAKE_DISABLED) {
341 crypto_handshake_destroy(&g_crypto_handshake_ctx);
342 }
343 memset(&g_crypto_handshake_ctx, 0, sizeof(g_crypto_handshake_ctx));
344
345 // Reinitialize to INIT state
346 asciichat_error_t init_result = crypto_handshake_init("wasm-client", &g_crypto_handshake_ctx, false);
347 if (init_result != ASCIICHAT_OK) {
348 return -1;
349 }
350 }
351
352 if (!packet || packet_len == 0) {
353 return -1;
354 }
355
356 // Extract packet type and payload
357 if (packet_len < sizeof(packet_header_t)) {
358 return -1;
359 }
360
361 const packet_header_t *header = (const packet_header_t *)packet;
362 packet_type_t packet_type = ntohs(header->type);
363 const uint8_t *payload_src = packet + sizeof(packet_header_t);
364 size_t payload_len = packet_len - sizeof(packet_header_t);
365
366 // Allocate payload copy from buffer pool (crypto function takes ownership and frees it)
367 // The raw packet pointer from JS cannot be passed directly because the crypto
368 // handshake function calls buffer_pool_free() on the payload when done.
369 uint8_t *payload = NULL;
370 if (payload_len > 0) {
371 payload = buffer_pool_alloc(NULL, payload_len);
372 if (!payload) {
373 g_connection_state = CONNECTION_STATE_ERROR;
374 return -1;
375 }
376 memcpy(payload, payload_src, payload_len);
377 }
378
379 // Process key exchange using transport-abstracted handshake
380
381 asciichat_error_t result = crypto_handshake_client_key_exchange(&g_crypto_handshake_ctx, &g_wasm_transport,
382 packet_type, payload, payload_len);
383
384 if (result != ASCIICHAT_OK) {
385 g_connection_state = CONNECTION_STATE_ERROR;
386 return -1;
387 }
388
389 g_connection_state = CONNECTION_STATE_HANDSHAKE;
390 return 0;
391}
392
400EMSCRIPTEN_KEEPALIVE
401int client_handle_crypto_parameters(const uint8_t *packet, size_t packet_len) {
402
403 if (!packet || packet_len == 0) {
404 return -1;
405 }
406
407 // Discovery sends CRYPTO_PARAMETERS before KEY_EXCHANGE_INIT. Initialize
408 // here so the negotiated values survive the subsequent key exchange packet.
409 if (g_crypto_handshake_ctx.state != CRYPTO_HANDSHAKE_INIT) {
410 if (g_crypto_handshake_ctx.state != CRYPTO_HANDSHAKE_DISABLED) {
411 crypto_handshake_destroy(&g_crypto_handshake_ctx);
412 }
413 memset(&g_crypto_handshake_ctx, 0, sizeof(g_crypto_handshake_ctx));
414 if (crypto_handshake_init("wasm-client", &g_crypto_handshake_ctx, false) != ASCIICHAT_OK) {
415 return -1;
416 }
417 }
418
419 // Extract packet type and payload
420 if (packet_len < sizeof(packet_header_t)) {
421 return -1;
422 }
423
424 const packet_header_t *header = (const packet_header_t *)packet;
425 packet_type_t packet_type = ntohs(header->type);
426 const uint8_t *payload_src = packet + sizeof(packet_header_t);
427 size_t payload_len = packet_len - sizeof(packet_header_t);
428
429 // Allocate payload copy from buffer pool
430 uint8_t *payload = NULL;
431 if (payload_len > 0) {
432 payload = buffer_pool_alloc(NULL, payload_len);
433 if (!payload) {
434 g_connection_state = CONNECTION_STATE_ERROR;
435 return -1;
436 }
437 memcpy(payload, payload_src, payload_len);
438 }
439
440 // Process crypto parameters
441 asciichat_error_t result =
442 crypto_handshake_set_parameters(&g_crypto_handshake_ctx, (const crypto_parameters_packet_t *)payload);
443
444 // Free payload
445 if (payload) {
446 buffer_pool_free(NULL, payload, payload_len);
447 }
448
449 if (result != ASCIICHAT_OK) {
450 g_connection_state = CONNECTION_STATE_ERROR;
451 return -1;
452 }
453
454 return 0;
455}
456
463EMSCRIPTEN_KEEPALIVE
464int client_handle_auth_challenge(const uint8_t *packet, size_t packet_len) {
465
466 if (!packet || packet_len == 0) {
467 return -1;
468 }
469
470 // Extract packet type and payload
471 if (packet_len < sizeof(packet_header_t)) {
472 return -1;
473 }
474
475 const packet_header_t *header = (const packet_header_t *)packet;
476 packet_type_t packet_type = ntohs(header->type);
477 const uint8_t *payload_src = packet + sizeof(packet_header_t);
478 size_t payload_len = packet_len - sizeof(packet_header_t);
479
480 // Allocate payload copy from buffer pool (crypto function takes ownership and frees it)
481 uint8_t *payload = NULL;
482 if (payload_len > 0) {
483 payload = buffer_pool_alloc(NULL, payload_len);
484 if (!payload) {
485 g_connection_state = CONNECTION_STATE_ERROR;
486 return -1;
487 }
488 memcpy(payload, payload_src, payload_len);
489 }
490
491 // Process auth challenge
492 asciichat_error_t result = crypto_handshake_client_auth_response(&g_crypto_handshake_ctx, &g_wasm_transport,
493 packet_type, payload, payload_len);
494
495 if (result != ASCIICHAT_OK) {
496 g_connection_state = CONNECTION_STATE_ERROR;
497 return -1;
498 }
499
500 return 0;
501}
502
509EMSCRIPTEN_KEEPALIVE
510int client_handle_handshake_complete(const uint8_t *packet, size_t packet_len) {
511
512 if (!packet || packet_len == 0) {
513 return -1;
514 }
515
516 // Extract packet type and payload
517 if (packet_len < sizeof(packet_header_t)) {
518 return -1;
519 }
520
521 const packet_header_t *header = (const packet_header_t *)packet;
522 packet_type_t packet_type = ntohs(header->type);
523 const uint8_t *payload_src = packet + sizeof(packet_header_t);
524 size_t payload_len = packet_len - sizeof(packet_header_t);
525
526 // Allocate payload copy from buffer pool (crypto function takes ownership and frees it)
527 uint8_t *payload = NULL;
528 if (payload_len > 0) {
529 payload = buffer_pool_alloc(NULL, payload_len);
530 if (!payload) {
531 g_connection_state = CONNECTION_STATE_ERROR;
532 return -1;
533 }
534 memcpy(payload, payload_src, payload_len);
535 }
536
537 // Complete handshake (takes ownership of payload and will free it)
538 asciichat_error_t result =
539 crypto_handshake_client_complete(&g_crypto_handshake_ctx, &g_wasm_transport, packet_type, payload, payload_len);
540
541 if (result != ASCIICHAT_OK) {
542 g_connection_state = CONNECTION_STATE_ERROR;
543 return -1;
544 }
545
546 g_handshake_complete = true;
547 g_connection_state = CONNECTION_STATE_CONNECTED;
548
549 // Update transport with the crypto context so ACIP can encrypt/decrypt packets
550 g_wasm_transport.crypto_ctx = &g_crypto_handshake_ctx.crypto_ctx;
551
552 return 0;
553}
554
555// ============================================================================
556// Packet Processing API
557// ============================================================================
558
568EMSCRIPTEN_KEEPALIVE
569int client_encrypt_packet(const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t ciphertext_size,
570 size_t *out_len) {
571 if (!g_handshake_complete) {
572 return -1;
573 }
574
575 // Encrypt the packet using handshake context's crypto context
576 size_t ciphertext_len = 0;
577 crypto_result_t result = crypto_encrypt(&g_crypto_handshake_ctx.crypto_ctx, plaintext, plaintext_len, ciphertext,
578 ciphertext_size, &ciphertext_len);
579 if (result != CRYPTO_OK) {
580 return -1;
581 }
582
583 *out_len = ciphertext_len;
584 return 0;
585}
586
596EMSCRIPTEN_KEEPALIVE
597int client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size,
598 size_t *out_len) {
599 if (!g_handshake_complete) {
600 return -1;
601 }
602
603 // Decrypt the packet using handshake context's crypto context
604 size_t plaintext_len = 0;
605 crypto_result_t result = crypto_decrypt(&g_crypto_handshake_ctx.crypto_ctx, ciphertext, ciphertext_len, plaintext,
606 plaintext_size, &plaintext_len);
607 if (result != CRYPTO_OK) {
608 return -1;
609 }
610
611 *out_len = plaintext_len;
612 return 0;
613}
614
621EMSCRIPTEN_KEEPALIVE
622char *client_parse_packet(const uint8_t *raw_packet, size_t packet_len) {
623 if (!raw_packet || packet_len < sizeof(packet_header_t)) {
624 return NULL;
625 }
626
627 // Parse packet header directly from bytes
628 const packet_header_t *header = (const packet_header_t *)raw_packet;
629
630 // Validate magic number (convert from network byte order)
631 uint64_t magic = NET_TO_HOST_U64(header->magic);
632 if (magic != PACKET_MAGIC) {
633 return NULL;
634 }
635
636 // Convert header fields from network byte order
637 uint16_t type = NET_TO_HOST_U16(header->type);
638 uint32_t length = NET_TO_HOST_U32(header->length);
639 uint32_t client_id = NET_TO_HOST_U32(header->client_id);
640 uint32_t crc32 = NET_TO_HOST_U32(header->crc32);
641
642 if (length != packet_len - sizeof(packet_header_t) ||
643 (length > 0 && asciichat_crc32_sw(raw_packet + sizeof(packet_header_t), length) != crc32)) {
644 return NULL;
645 }
646
647 // Build JSON response with packet metadata
648 char *json = SAFE_MALLOC(1024, char *);
649 if (!json) {
650 return NULL;
651 }
652
653 snprintf(json, 1024, "{\"type\":%u,\"length\":%u,\"client_id\":%u,\"crc32\":%u}", type, length, client_id, crc32);
654
655 return json;
656}
657
668EMSCRIPTEN_KEEPALIVE
669int client_serialize_packet(uint16_t packet_type, const uint8_t *payload, size_t payload_len, uint32_t client_id,
670 uint8_t *output, size_t *out_len) {
671 if (!output || !out_len) {
672 return -1;
673 }
674
675 // Calculate CRC32 of payload (use software version for WASM)
676 uint32_t crc = payload && payload_len > 0 ? asciichat_crc32_sw(payload, payload_len) : 0;
677
678 // Build packet header (convert to network byte order)
680 .type = HOST_TO_NET_U16(packet_type),
681 .length = HOST_TO_NET_U32((uint32_t)payload_len),
682 .crc32 = HOST_TO_NET_U32(crc),
683 .client_id = HOST_TO_NET_U32(client_id)};
684
685 // Copy header to output
686 memcpy(output, &header, sizeof(packet_header_t));
687
688 // Copy payload to output
689 if (payload && payload_len > 0) {
690 memcpy(output + sizeof(packet_header_t), payload, payload_len);
691 }
692
693 *out_len = sizeof(packet_header_t) + payload_len;
694 return 0;
695}
696
704EMSCRIPTEN_KEEPALIVE
705int client_send_video_frame(const uint8_t *rgba_data, int width, int height) {
706 if (!g_initialized) {
707 return -1;
708 }
709
710 // This is a placeholder - actual implementation would:
711 // 1. Convert RGBA to target format (e.g., compress with libjpeg-turbo)
712 // 2. Build frame packet
713 // 3. Encrypt if handshake complete
714 // 4. Return serialized packet via callback to JS
715
716 return 0;
717}
718
719// ============================================================================
720// Connection State API
721// ============================================================================
722
727EMSCRIPTEN_KEEPALIVE
729 return (int)g_connection_state;
730}
731
732// ============================================================================
733// Memory Management
734// ============================================================================
735
736EMSCRIPTEN_KEEPALIVE
737void client_free_string(char *ptr) {
738 SAFE_FREE(ptr);
739}
740
741// ============================================================================
742// Opus Audio Codec API
743// ============================================================================
744
752EMSCRIPTEN_KEEPALIVE
753int client_opus_encoder_init(int sample_rate, int channels, int bitrate) {
754 if (g_opus_encoder) {
755 opus_encoder_destroy(g_opus_encoder);
756 g_opus_encoder = NULL;
757 }
758
759 int error;
760 g_opus_encoder = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_VOIP, &error);
761 if (error != OPUS_OK || !g_opus_encoder) {
762 return -1;
763 }
764
765 // Set bitrate
766 opus_encoder_ctl(g_opus_encoder, OPUS_SET_BITRATE(bitrate));
767
768 return 0;
769}
770
777EMSCRIPTEN_KEEPALIVE
778int client_opus_decoder_init(int sample_rate, int channels) {
779 if (g_opus_decoder) {
780 opus_decoder_destroy(g_opus_decoder);
781 g_opus_decoder = NULL;
782 }
783
784 int error;
785 g_opus_decoder = opus_decoder_create(sample_rate, channels, &error);
786 if (error != OPUS_OK || !g_opus_decoder) {
787 return -1;
788 }
789
790 return 0;
791}
792
801EMSCRIPTEN_KEEPALIVE
802int client_opus_encode(const int16_t *pcm_data, int frame_size, uint8_t *opus_data, int max_opus_bytes) {
803 if (!g_opus_encoder) {
804 return -1;
805 }
806
807 int encoded_bytes = opus_encode(g_opus_encoder, pcm_data, frame_size, opus_data, max_opus_bytes);
808 if (encoded_bytes < 0) {
809 return -1;
810 }
811
812 return encoded_bytes;
813}
814
824EMSCRIPTEN_KEEPALIVE
825int client_opus_decode(const uint8_t *opus_data, int opus_bytes, int16_t *pcm_data, int frame_size, int decode_fec) {
826 if (!g_opus_decoder) {
827 return -1;
828 }
829
830 int decoded_samples = opus_decode(g_opus_decoder, opus_data, opus_bytes, pcm_data, frame_size, decode_fec);
831 if (decoded_samples < 0) {
832 return -1;
833 }
834
835 return decoded_samples;
836}
837
841EMSCRIPTEN_KEEPALIVE
843 if (g_opus_encoder) {
844 opus_encoder_destroy(g_opus_encoder);
845 g_opus_encoder = NULL;
846 }
847}
848
852EMSCRIPTEN_KEEPALIVE
854 if (g_opus_decoder) {
855 opus_decoder_destroy(g_opus_decoder);
856 g_opus_decoder = NULL;
857 }
858}
ANSI escape sequence utilities and fast color code generation.
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
struct OpusDecoder OpusDecoder
struct OpusEncoder OpusEncoder
⚙️ Common definitions, error codes, macros, and types shared throughout the application
📦 Network Packet Compression Utilities
Hardware-Accelerated CRC32 Checksum Computation.
asciichat_error_t crypto_handshake_init(const char *name, crypto_handshake_context_t *ctx, bool is_server)
Initialize named crypto handshake context.
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
asciichat_error_t crypto_handshake_set_parameters(crypto_handshake_context_t *ctx, const crypto_parameters_packet_t *params)
Set crypto parameters from crypto_parameters_packet_t.
Common declarations and data structures for cryptographic handshake.
#define HOST_TO_NET_U16(val)
Definition endian.h:96
#define NET_TO_HOST_U64(val)
Definition endian.h:228
#define HOST_TO_NET_U32(val)
Definition endian.h:66
#define HOST_TO_NET_U64(val)
Definition endian.h:213
#define NET_TO_HOST_U16(val)
Definition endian.h:111
#define NET_TO_HOST_U32(val)
Definition endian.h:81
Font resolution and bundled font data.
void ansi_fast_init(void)
Definition ansi.c:319
@ OPUS_APPLICATION_VOIP
Voice over IP (optimized for speech)
Definition opus.h:77
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
crypto_result_t crypto_encrypt(crypto_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext_out, size_t ciphertext_out_size, size_t *ciphertext_len_out)
Encrypt data using XSalsa20-Poly1305.
crypto_result_t
Cryptographic operation result codes.
crypto_result_t crypto_decrypt(crypto_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext_out, size_t plaintext_out_size, size_t *plaintext_len_out)
Decrypt data using XSalsa20-Poly1305.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_CRYPTO
Definition error_codes.h:96
@ ERROR_INVALID_PARAM
@ ERROR_NOT_SUPPORTED
Definition error_codes.h:74
@ CRYPTO_HANDSHAKE_INIT
@ CRYPTO_HANDSHAKE_DISABLED
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
Initialize the logging system.
Definition log/log.c:533
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
asciichat_error_t log_set_format(const char *format_str, bool console_only)
Set a custom log format string.
Definition log/log.c:744
asciichat_error_t options_init(int argc, char **argv)
Initialize options by parsing command-line arguments with unified mode detection.
#define OPT_LOG_TEMPLATE_DEFAULT_DEBUG
Default log format string - debug mode (verbose with thread name, file relative path,...
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
#define PACKET_MAGIC
Packet magic number (alias for MAGIC_PACKET_VALID)
Definition packet.h:255
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
void platform_destroy(void)
Cleanup platform-specific subsystems.
asciichat_error_t platform_init(void)
Initialize platform-specific subsystems.
uint32_t asciichat_crc32_sw(const void *data, size_t len)
Compute CRC32 checksum using software implementation only.
Definition crc32.c:171
int socket_t
Client-side handshake functions.
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
Platform initialization and static synchronization helpers.
asciichat_error_t crypto_handshake_client_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Client: Wait for handshake complete confirmation.
asciichat_error_t crypto_handshake_client_key_exchange(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Client: Process server's public key and send our public key.
asciichat_error_t crypto_handshake_client_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Client: Process auth challenge and send response.
📝 Logging API with multiple log levels and terminal output control
Magic number validation constants and macros.
const size_t g_font_default_size
const unsigned char g_font_default[]
Opus audio codec wrapper for real-time encoding/decoding.
void options_state_destroy(void)
Shutdown RCU options system.
Definition rcu.c:461
ACIP shared/bidirectional packet sending functions.
EMSCRIPTEN_KEEPALIVE void client_opus_decoder_cleanup(void)
EMSCRIPTEN_KEEPALIVE void client_free_string(char *ptr)
const unsigned char * get_font_default_ptr(void)
connection_state_t
@ CONNECTION_STATE_ERROR
@ CONNECTION_STATE_DISCONNECTED
@ CONNECTION_STATE_CONNECTED
@ CONNECTION_STATE_HANDSHAKE
@ CONNECTION_STATE_CONNECTING
EMSCRIPTEN_KEEPALIVE int client_generate_keypair(void)
EMSCRIPTEN_KEEPALIVE int client_set_server_address(const char *server_host, int server_port)
EMSCRIPTEN_KEEPALIVE int client_handle_auth_challenge(const uint8_t *packet, size_t packet_len)
EMSCRIPTEN_KEEPALIVE int client_handle_key_exchange_init(const uint8_t *packet, size_t packet_len)
EMSCRIPTEN_KEEPALIVE int client_init_with_args(const char *args_json)
unsigned int get_font_default_size(void)
EMSCRIPTEN_KEEPALIVE int client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *out_len)
EMSCRIPTEN_KEEPALIVE int client_opus_encoder_init(int sample_rate, int channels, int bitrate)
EMSCRIPTEN_KEEPALIVE int client_handle_crypto_parameters(const uint8_t *packet, size_t packet_len)
EMSCRIPTEN_KEEPALIVE char * client_parse_packet(const uint8_t *raw_packet, size_t packet_len)
EMSCRIPTEN_KEEPALIVE int client_handle_handshake_complete(const uint8_t *packet, size_t packet_len)
EMSCRIPTEN_KEEPALIVE char * client_get_public_key_hex(void)
EMSCRIPTEN_KEEPALIVE void client_cleanup(void)
EMSCRIPTEN_KEEPALIVE int client_opus_encode(const int16_t *pcm_data, int frame_size, uint8_t *opus_data, int max_opus_bytes)
EMSCRIPTEN_KEEPALIVE int client_send_video_frame(const uint8_t *rgba_data, int width, int height)
EM_JS(void, js_send_raw_packet,(const uint8_t *packet_data, size_t packet_len), { if(!Module.sendPacketCallback) { console.error('[WASM] sendPacketCallback not registered - cannot send packet');return;} const packetArray=new Uint8Array(HEAPU8.buffer, packet_data, packet_len);const packetCopy=new Uint8Array(packetArray);var pktType=packetCopy.length >=10 ?((packetCopy[8]<< 8)|packetCopy[9]) :-1;console.error('[WASM->JS] Sending raw packet:', packetCopy.length, 'bytes, type=0x'+pktType.toString(16));Module.sendPacketCallback(packetCopy);})
EMSCRIPTEN_KEEPALIVE int client_opus_decode(const uint8_t *opus_data, int opus_bytes, int16_t *pcm_data, int frame_size, int decode_fec)
EMSCRIPTEN_KEEPALIVE int client_serialize_packet(uint16_t packet_type, const uint8_t *payload, size_t payload_len, uint32_t client_id, uint8_t *output, size_t *out_len)
EMSCRIPTEN_KEEPALIVE int client_opus_decoder_init(int sample_rate, int channels)
EMSCRIPTEN_KEEPALIVE void client_opus_encoder_cleanup(void)
EMSCRIPTEN_KEEPALIVE int client_encrypt_packet(const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t ciphertext_size, size_t *out_len)
EMSCRIPTEN_KEEPALIVE int client_get_connection_state(void)
EMSCRIPTEN_KEEPALIVE asciichat_error_t client_acds_join_request(const char *session, const char *password, uint8_t *output)
int wasm_parse_args(const char *args_str, char **argv, int max_args, char **out_args_copy)
WASM initialization helpers.
Transport method table (virtual function table)
Definition transport.h:115
asciichat_error_t(* send)(acip_transport_t *transport, const void *data, size_t len)
Send data through this transport.
Definition transport.h:129
Transport instance structure.
Definition transport.h:214
const acip_transport_methods_t * methods
Method table (virtual functions)
Definition transport.h:215
crypto_context_t * crypto_ctx
Optional encryption context.
Definition transport.h:216
Cryptographic handshake context structure.
crypto_handshake_state_t state
Crypto parameters packet structure (Packet Type 15)
Definition packet.h:981
Network packet header structure.
Definition packet.h:598
uint32_t client_id
Client ID (0 = server, >0 = client identifier)
Definition packet.h:608
uint32_t length
Payload data length in bytes (0 for header-only packets)
Definition packet.h:604
uint64_t magic
Magic number (PACKET_MAGIC = 0xA5C11C4A1 "ASCIICHAT" in hex) for packet validation.
Definition packet.h:600
uint16_t type
Packet type (packet_type_t enumeration)
Definition packet.h:602
uint32_t crc32
CRC32 checksum of payload data (0 if length == 0)
Definition packet.h:606
Transport abstraction layer for ACIP protocol.
acip_transport_type_t
Transport type enumeration.
Definition transport.h:93
@ ACIP_TRANSPORT_WEBSOCKET
WebSocket over TCP.
Definition transport.h:95
#define LOG_DEBUG
Definition types.h:39
📊 String Formatting Utilities