ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
src/web/client.c
Go to the documentation of this file.
1
6#include <emscripten.h>
7#include <stdlib.h>
8#include <string.h>
9#include <stdint.h>
10
11// Logging macros for debug - use console.error so playwright-cli captures it
12#define WASM_LOG(msg) EM_ASM({ console.error('[C] ' + UTF8ToString($0)); }, msg)
13#define WASM_LOG_INT(msg, val) EM_ASM({ console.error('[C] ' + UTF8ToString($0) + ': ' + $1); }, msg, val)
14#define WASM_ERROR(msg) EM_ASM({ console.error('[C] ERROR: ' + UTF8ToString($0)); }, msg)
15
16// JavaScript callback for sending complete ACIP packets from WASM to WebSocket
17// This will be called by the WASM transport to send complete packets (header + payload)
18EM_JS(void, js_send_raw_packet, (const uint8_t *packet_data, size_t packet_len), {
19 if (!Module.sendPacketCallback) {
20 console.error('[WASM] sendPacketCallback not registered - cannot send packet');
21 return;
22 }
23
24 // Copy complete packet from WASM memory to JavaScript
25 const packetArray = new Uint8Array(HEAPU8.buffer, packet_data, packet_len);
26 const packetCopy = new Uint8Array(packetArray);
27
28 var pktType = packetCopy.length >= 10 ? ((packetCopy[8] << 8) | packetCopy[9]) : -1;
29 console.error('[WASM->JS] Sending raw packet:', packetCopy.length, 'bytes, type=0x' + pktType.toString(16));
30
31 // Send as raw binary packet via WebSocket
32 Module.sendPacketCallback(packetCopy);
33});
34
35#include <ascii-chat/options/options.h>
36#include <ascii-chat/options/rcu.h>
37#include <ascii-chat/platform/init.h>
38#include <ascii-chat/asciichat_errno.h>
39#include <ascii-chat/log/logging.h>
40#include <ascii-chat/crypto/crypto.h>
41#include <ascii-chat/crypto/handshake/client.h>
42#include <ascii-chat/crypto/handshake/common.h>
43#include <ascii-chat/network/packet.h>
44#include <ascii-chat/network/packet_parsing.h>
45#include <ascii-chat/network/crc32.h>
46#include <ascii-chat/network/compression.h>
47#include <ascii-chat/network/acip/send.h>
48#include <ascii-chat/network/acip/transport.h>
49#include <ascii-chat/video/ascii.h>
50#include <ascii-chat/video/ansi_fast.h>
51#include <ascii-chat/common.h>
52#include <ascii-chat/buffer_pool.h>
53#include <ascii-chat/util/format.h>
54#include <ascii-chat/util/magic.h>
55#include <opus.h>
56
57// ============================================================================
58// WASM Transport Implementation
59// ============================================================================
60
64static asciichat_error_t wasm_transport_send(acip_transport_t *transport, const void *data, size_t len) {
65 WASM_LOG("wasm_transport_send called");
66 WASM_LOG_INT(" packet length", (int)len);
67
68 // Forward complete packet (header + payload) to JavaScript WebSocket bridge
69 js_send_raw_packet((const uint8_t *)data, len);
70
71 WASM_LOG("wasm_transport_send: packet sent to JS");
72 return ASCIICHAT_OK;
73}
74
75static asciichat_error_t wasm_transport_recv(acip_transport_t *transport, void **buffer, size_t *out_len,
76 void **out_allocated_buffer) {
77 // Not used - packets arrive via JavaScript callbacks
78 return SET_ERRNO(ERROR_NOT_SUPPORTED, "recv not supported on WASM transport");
79}
80
81static asciichat_error_t wasm_transport_close(acip_transport_t *transport) {
82 return ASCIICHAT_OK; // Nothing to close
83}
84
85static acip_transport_type_t wasm_transport_get_type(acip_transport_t *transport) {
86 return ACIP_TRANSPORT_WEBSOCKET; // Closest match
87}
88
89static socket_t wasm_transport_get_socket(acip_transport_t *transport) {
90 return INVALID_SOCKET_VALUE;
91}
92
93static bool wasm_transport_is_connected(acip_transport_t *transport) {
94 return true; // Always "connected" from WASM perspective
95}
96
97static const acip_transport_methods_t wasm_transport_methods = {.send = wasm_transport_send,
98 .recv = wasm_transport_recv,
99 .close = wasm_transport_close,
100 .get_type = wasm_transport_get_type,
101 .get_socket = wasm_transport_get_socket,
102 .is_connected = wasm_transport_is_connected,
103 .destroy_impl = NULL};
104
105static acip_transport_t g_wasm_transport = {.methods = &wasm_transport_methods, .crypto_ctx = NULL, .impl_data = NULL};
106
107// ============================================================================
108// Global State
109// ============================================================================
110
111// Global state for client session
112static crypto_handshake_context_t g_crypto_handshake_ctx = {0};
113static bool g_initialized = false;
114static bool g_handshake_complete = false;
115
116// Opus codec state
117static OpusEncoder *g_opus_encoder = NULL;
118static OpusDecoder *g_opus_decoder = NULL;
119
120// Connection state enum (exposed to JS)
128
129static connection_state_t g_connection_state = CONNECTION_STATE_DISCONNECTED;
130
131// ============================================================================
132// Initialization
133// ============================================================================
134
140EMSCRIPTEN_KEEPALIVE
141int client_init_with_args(const char *args_json) {
142 // Parse arguments
143 WASM_LOG("Parsing arguments...");
144 char *args_copy = strdup(args_json);
145 if (!args_copy) {
146 WASM_ERROR("strdup FAILED");
147 return -1;
148 }
149
150 // Count arguments
151 int argc = 0;
152 char *argv[64] = {NULL}; // Max 64 arguments
153 char *token = strtok(args_copy, " ");
154 while (token != NULL && argc < 63) {
155 argv[argc++] = token;
156 token = strtok(NULL, " ");
157 }
158 argv[argc] = NULL;
159 WASM_LOG_INT("Parsed arguments, argc", argc);
160
161 // Initialize options (sets up RCU, defaults, etc.)
162 WASM_LOG("Calling options_init...");
163 asciichat_error_t err = options_init(argc, argv);
164 free(args_copy);
165 WASM_LOG("client_init_with_args: START");
166
167 if (g_initialized) {
168 WASM_ERROR("Client already initialized");
169 return -1;
170 }
171
172 // Initialize platform layer
173 WASM_LOG("Calling platform_init...");
174 err = platform_init();
175 if (err != ASCIICHAT_OK) {
176 WASM_ERROR("platform_init FAILED");
177 return -1;
178 }
179 WASM_LOG("platform_init OK");
180
181 // Initialize logging to stderr (console.error in browser)
182 WASM_LOG("Calling log_init...");
183 log_init(NULL, LOG_DEBUG, true, false);
184 WASM_LOG("log_init OK");
185 log_info("WASM client initialized via logging system");
186
187 if (err != ASCIICHAT_OK) {
188 WASM_LOG_INT("options_init FAILED", err);
189 return -1;
190 }
191 WASM_LOG("options_init OK");
192
193 // Initialize ANSI color code generation
194 WASM_LOG("Calling ansi_fast_init...");
196 WASM_LOG("ansi_fast_init OK");
197
198 g_initialized = true;
199 g_connection_state = CONNECTION_STATE_DISCONNECTED;
200
201 WASM_LOG("client_init_with_args: COMPLETE");
202 return 0;
203}
204
205EMSCRIPTEN_KEEPALIVE
206void client_cleanup(void) {
207 WASM_LOG("=== client_cleanup CALLED ===");
208 WASM_LOG_INT(" g_initialized", g_initialized);
209 WASM_LOG_INT(" g_connection_state", g_connection_state);
210 WASM_LOG_INT(" g_crypto_handshake_ctx.state", g_crypto_handshake_ctx.state);
211
212 // Clean up crypto handshake context
213 crypto_handshake_destroy(&g_crypto_handshake_ctx);
214 memset(&g_crypto_handshake_ctx, 0, sizeof(g_crypto_handshake_ctx));
215
216 g_handshake_complete = false;
217 g_connection_state = CONNECTION_STATE_DISCONNECTED;
218 g_initialized = false;
221
222 WASM_LOG("=== client_cleanup COMPLETE ===");
223}
224
225// ============================================================================
226// Cryptography API
227// ============================================================================
228
233EMSCRIPTEN_KEEPALIVE
235 WASM_LOG("=== client_generate_keypair CALLED ===");
236 WASM_LOG_INT(" g_initialized", g_initialized);
237 WASM_LOG_INT(" g_crypto_handshake_ctx.state BEFORE", g_crypto_handshake_ctx.state);
238
239 if (!g_initialized) {
240 WASM_ERROR("Client not initialized");
241 return -1;
242 }
243
244 // Reset handshake context if previously used (reconnection / React Strict Mode remount)
245 if (g_crypto_handshake_ctx.state != CRYPTO_HANDSHAKE_DISABLED) {
246 WASM_LOG("Destroying previous handshake context before re-init");
247 WASM_LOG_INT(" State before destroy", g_crypto_handshake_ctx.state);
248 crypto_handshake_destroy(&g_crypto_handshake_ctx);
249 WASM_LOG_INT(" State after destroy", g_crypto_handshake_ctx.state);
250 memset(&g_crypto_handshake_ctx, 0, sizeof(g_crypto_handshake_ctx));
251 WASM_LOG_INT(" State after memset", g_crypto_handshake_ctx.state);
252 g_handshake_complete = false;
253 }
254
255 // Initialize crypto handshake context
256 WASM_LOG("Calling crypto_handshake_init...");
257 WASM_LOG_INT(" State before init", g_crypto_handshake_ctx.state);
258 asciichat_error_t result = crypto_handshake_init(&g_crypto_handshake_ctx, false /* is_server */);
259 if (result != ASCIICHAT_OK) {
260 WASM_LOG_INT("crypto_handshake_init FAILED, result", result);
261 WASM_LOG_INT(" State after failed init", g_crypto_handshake_ctx.state);
262 return -1;
263 }
264
265 WASM_LOG("Keypair generated successfully");
266 WASM_LOG_INT(" g_crypto_handshake_ctx.state AFTER init", g_crypto_handshake_ctx.state);
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 WASM_ERROR("Client not initialized");
281 return -1;
282 }
283
284 if (!server_host || server_port <= 0 || server_port > 65535) {
285 WASM_ERROR("Invalid server address parameters");
286 return -1;
287 }
288
289 // Set server IP and port in handshake context
290 SAFE_STRNCPY(g_crypto_handshake_ctx.server_ip, server_host, sizeof(g_crypto_handshake_ctx.server_ip));
291 g_crypto_handshake_ctx.server_port = server_port;
292
293 WASM_LOG("Server address set");
294 return 0;
295}
296
301EMSCRIPTEN_KEEPALIVE
303 if (g_crypto_handshake_ctx.state == CRYPTO_HANDSHAKE_DISABLED) {
304 WASM_ERROR("No crypto context (call client_generate_keypair first)");
305 return NULL;
306 }
307
308 // Get public key from handshake context
309 const uint8_t *pubkey = g_crypto_handshake_ctx.crypto_ctx.public_key;
310 if (!pubkey) {
311 WASM_ERROR("Public key not available in crypto context");
312 return NULL;
313 }
314
315 // Allocate buffer for hex string (32 bytes = 64 hex chars + null terminator)
316 char *hex_buffer = SAFE_MALLOC(65, char *);
317 if (!hex_buffer) {
318 WASM_ERROR("Failed to allocate hex buffer");
319 return NULL;
320 }
321
322 // Convert public key to hex
323 for (int i = 0; i < 32; i++) {
324 snprintf(hex_buffer + (i * 2), 3, "%02x", pubkey[i]);
325 }
326 hex_buffer[64] = '\0';
327
328 return hex_buffer;
329}
330
338EMSCRIPTEN_KEEPALIVE
339int client_handle_key_exchange_init(const uint8_t *packet, size_t packet_len) {
340 WASM_LOG("=== client_handle_key_exchange_init CALLED ===");
341 WASM_LOG_INT(" packet_len", (int)packet_len);
342 WASM_LOG_INT(" g_crypto_handshake_ctx.state BEFORE", g_crypto_handshake_ctx.state);
343
344 // Safety check: if handshake context is not in INIT state, reinitialize it
345 // This handles cases where previous handshakes weren't properly cleaned up
346 if (g_crypto_handshake_ctx.state != CRYPTO_HANDSHAKE_INIT) {
347 WASM_LOG("Handshake context not in INIT state, reinitializing...");
348 WASM_LOG_INT(" Previous state", g_crypto_handshake_ctx.state);
349
350 // Destroy and reset
351 if (g_crypto_handshake_ctx.state != CRYPTO_HANDSHAKE_DISABLED) {
352 crypto_handshake_destroy(&g_crypto_handshake_ctx);
353 }
354 memset(&g_crypto_handshake_ctx, 0, sizeof(g_crypto_handshake_ctx));
355
356 // Reinitialize to INIT state
357 asciichat_error_t init_result = crypto_handshake_init(&g_crypto_handshake_ctx, false);
358 if (init_result != ASCIICHAT_OK) {
359 WASM_ERROR("Failed to reinitialize crypto handshake context");
360 WASM_LOG_INT(" init result", init_result);
361 return -1;
362 }
363 WASM_LOG("Crypto handshake context reinitialized");
364 }
365
366 if (!packet || packet_len == 0) {
367 WASM_ERROR("Invalid packet data");
368 return -1;
369 }
370
371 // Extract packet type and payload
372 if (packet_len < sizeof(packet_header_t)) {
373 WASM_ERROR("Packet too small for header");
374 return -1;
375 }
376
377 const packet_header_t *header = (const packet_header_t *)packet;
378 packet_type_t packet_type = ntohs(header->type);
379 const uint8_t *payload_src = packet + sizeof(packet_header_t);
380 size_t payload_len = packet_len - sizeof(packet_header_t);
381
382 WASM_LOG_INT(" packet_type", packet_type);
383 WASM_LOG_INT(" payload_len", (int)payload_len);
384
385 // Allocate payload copy from buffer pool (crypto function takes ownership and frees it)
386 // The raw packet pointer from JS cannot be passed directly because the crypto
387 // handshake function calls buffer_pool_free() on the payload when done.
388 uint8_t *payload = NULL;
389 if (payload_len > 0) {
390 payload = buffer_pool_alloc(NULL, payload_len);
391 if (!payload) {
392 WASM_ERROR("Failed to allocate payload buffer");
393 g_connection_state = CONNECTION_STATE_ERROR;
394 return -1;
395 }
396 memcpy(payload, payload_src, payload_len);
397 }
398
399 // Process key exchange using transport-abstracted handshake
400 WASM_LOG("Calling crypto_handshake_client_key_exchange...");
401
402 asciichat_error_t result = crypto_handshake_client_key_exchange(&g_crypto_handshake_ctx, &g_wasm_transport,
403 packet_type, payload, payload_len);
404
405 WASM_LOG_INT(" handshake result", result);
406 WASM_LOG_INT(" g_crypto_handshake_ctx.state AFTER", g_crypto_handshake_ctx.state);
407
408 if (result != ASCIICHAT_OK) {
409 WASM_ERROR("Failed to process KEY_EXCHANGE_INIT");
410 g_connection_state = CONNECTION_STATE_ERROR;
411 return -1;
412 }
413
414 g_connection_state = CONNECTION_STATE_HANDSHAKE;
415 WASM_LOG("=== KEY_EXCHANGE_INIT processed successfully ===");
416 return 0;
417}
418
425EMSCRIPTEN_KEEPALIVE
426int client_handle_auth_challenge(const uint8_t *packet, size_t packet_len) {
427 WASM_LOG("=== client_handle_auth_challenge CALLED ===");
428 WASM_LOG_INT(" packet_len", (int)packet_len);
429 WASM_LOG_INT(" g_crypto_handshake_ctx.state", g_crypto_handshake_ctx.state);
430
431 if (!packet || packet_len == 0) {
432 WASM_ERROR("Invalid packet data");
433 return -1;
434 }
435
436 // Extract packet type and payload
437 if (packet_len < sizeof(packet_header_t)) {
438 WASM_ERROR("Packet too small for header");
439 return -1;
440 }
441
442 const packet_header_t *header = (const packet_header_t *)packet;
443 packet_type_t packet_type = ntohs(header->type);
444 const uint8_t *payload_src = packet + sizeof(packet_header_t);
445 size_t payload_len = packet_len - sizeof(packet_header_t);
446
447 WASM_LOG_INT(" packet_type", packet_type);
448 WASM_LOG_INT(" payload_len", (int)payload_len);
449
450 // Allocate payload copy from buffer pool (crypto function takes ownership and frees it)
451 uint8_t *payload = NULL;
452 if (payload_len > 0) {
453 payload = buffer_pool_alloc(NULL, payload_len);
454 if (!payload) {
455 WASM_ERROR("Failed to allocate payload buffer");
456 g_connection_state = CONNECTION_STATE_ERROR;
457 return -1;
458 }
459 memcpy(payload, payload_src, payload_len);
460 }
461
462 // Process auth challenge
463 asciichat_error_t result = crypto_handshake_client_auth_response(&g_crypto_handshake_ctx, &g_wasm_transport,
464 packet_type, payload, payload_len);
465
466 WASM_LOG_INT(" auth_response result", result);
467
468 if (result != ASCIICHAT_OK) {
469 WASM_ERROR("Failed to process AUTH_CHALLENGE");
470 g_connection_state = CONNECTION_STATE_ERROR;
471 return -1;
472 }
473
474 WASM_LOG("=== AUTH_CHALLENGE processed successfully ===");
475 return 0;
476}
477
484EMSCRIPTEN_KEEPALIVE
485int client_handle_handshake_complete(const uint8_t *packet, size_t packet_len) {
486 WASM_LOG("=== client_handle_handshake_complete CALLED ===");
487 WASM_LOG_INT(" packet_len", (int)packet_len);
488 WASM_LOG_INT(" g_crypto_handshake_ctx.state", g_crypto_handshake_ctx.state);
489
490 if (!packet || packet_len == 0) {
491 WASM_ERROR("Invalid packet data");
492 return -1;
493 }
494
495 // Extract packet type and payload
496 if (packet_len < sizeof(packet_header_t)) {
497 WASM_ERROR("Packet too small for header");
498 return -1;
499 }
500
501 const packet_header_t *header = (const packet_header_t *)packet;
502 packet_type_t packet_type = ntohs(header->type);
503 const uint8_t *payload_src = packet + sizeof(packet_header_t);
504 size_t payload_len = packet_len - sizeof(packet_header_t);
505
506 WASM_LOG_INT(" packet_type", packet_type);
507
508 // Allocate payload copy from buffer pool (crypto function takes ownership and frees it)
509 uint8_t *payload = NULL;
510 if (payload_len > 0) {
511 payload = buffer_pool_alloc(NULL, payload_len);
512 if (!payload) {
513 WASM_ERROR("Failed to allocate payload buffer");
514 g_connection_state = CONNECTION_STATE_ERROR;
515 return -1;
516 }
517 memcpy(payload, payload_src, payload_len);
518 }
519
520 // Complete handshake (takes ownership of payload and will free it)
521 asciichat_error_t result =
522 crypto_handshake_client_complete(&g_crypto_handshake_ctx, &g_wasm_transport, packet_type, payload, payload_len);
523
524 WASM_LOG_INT(" handshake_complete result", result);
525
526 if (result != ASCIICHAT_OK) {
527 WASM_ERROR("Failed to complete handshake");
528 g_connection_state = CONNECTION_STATE_ERROR;
529 return -1;
530 }
531
532 g_handshake_complete = true;
533 g_connection_state = CONNECTION_STATE_CONNECTED;
534 WASM_LOG("=== HANDSHAKE COMPLETE - session encrypted ===");
535 return 0;
536}
537
538// ============================================================================
539// Packet Processing API
540// ============================================================================
541
551EMSCRIPTEN_KEEPALIVE
552int client_encrypt_packet(const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t ciphertext_size,
553 size_t *out_len) {
554 if (!g_handshake_complete) {
555 WASM_ERROR("Encryption requires completed handshake");
556 return -1;
557 }
558
559 // Encrypt the packet using handshake context's crypto context
560 size_t ciphertext_len = 0;
561 crypto_result_t result = crypto_encrypt(&g_crypto_handshake_ctx.crypto_ctx, plaintext, plaintext_len, ciphertext,
562 ciphertext_size, &ciphertext_len);
563 if (result != CRYPTO_OK) {
564 WASM_ERROR("Encryption failed");
565 return -1;
566 }
567
568 *out_len = ciphertext_len;
569 return 0;
570}
571
581EMSCRIPTEN_KEEPALIVE
582int client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size,
583 size_t *out_len) {
584 if (!g_handshake_complete) {
585 WASM_ERROR("Decryption requires completed handshake");
586 return -1;
587 }
588
589 // Decrypt the packet using handshake context's crypto context
590 size_t plaintext_len = 0;
591 crypto_result_t result = crypto_decrypt(&g_crypto_handshake_ctx.crypto_ctx, ciphertext, ciphertext_len, plaintext,
592 plaintext_size, &plaintext_len);
593 if (result != CRYPTO_OK) {
594 WASM_ERROR("Decryption failed");
595 return -1;
596 }
597
598 *out_len = plaintext_len;
599 return 0;
600}
601
608EMSCRIPTEN_KEEPALIVE
609char *client_parse_packet(const uint8_t *raw_packet, size_t packet_len) {
610 if (!raw_packet || packet_len < sizeof(packet_header_t)) {
611 WASM_ERROR("Invalid packet data");
612 return NULL;
613 }
614
615 // Parse packet header directly from bytes
616 const packet_header_t *header = (const packet_header_t *)raw_packet;
617
618 // Validate magic number (convert from network byte order)
619 uint64_t magic = NET_TO_HOST_U64(header->magic);
620 if (magic != PACKET_MAGIC) {
621 WASM_ERROR("Invalid packet magic number");
622 return NULL;
623 }
624
625 // Convert header fields from network byte order
626 uint16_t type = NET_TO_HOST_U16(header->type);
627 uint32_t length = NET_TO_HOST_U32(header->length);
628 uint32_t client_id = NET_TO_HOST_U32(header->client_id);
629 uint32_t crc32 = NET_TO_HOST_U32(header->crc32);
630
631 // Build JSON response with packet metadata
632 char *json = SAFE_MALLOC(1024, char *);
633 if (!json) {
634 WASM_ERROR("Failed to allocate JSON buffer");
635 return NULL;
636 }
637
638 snprintf(json, 1024, "{\"type\":%u,\"length\":%u,\"client_id\":%u,\"crc32\":%u}", type, length, client_id, crc32);
639
640 return json;
641}
642
653EMSCRIPTEN_KEEPALIVE
654int client_serialize_packet(uint16_t packet_type, const uint8_t *payload, size_t payload_len, uint32_t client_id,
655 uint8_t *output, size_t *out_len) {
656 if (!output || !out_len) {
657 WASM_ERROR("Invalid output parameters");
658 return -1;
659 }
660
661 // Calculate CRC32 of payload (use software version for WASM)
662 uint32_t crc = payload && payload_len > 0 ? asciichat_crc32_sw(payload, payload_len) : 0;
663
664 // Build packet header (convert to network byte order)
665 packet_header_t header = {.magic = HOST_TO_NET_U64(PACKET_MAGIC),
666 .type = HOST_TO_NET_U16(packet_type),
667 .length = HOST_TO_NET_U32((uint32_t)payload_len),
668 .crc32 = HOST_TO_NET_U32(crc),
669 .client_id = HOST_TO_NET_U32(client_id)};
670
671 // Copy header to output
672 memcpy(output, &header, sizeof(packet_header_t));
673
674 // Copy payload to output
675 if (payload && payload_len > 0) {
676 memcpy(output + sizeof(packet_header_t), payload, payload_len);
677 }
678
679 *out_len = sizeof(packet_header_t) + payload_len;
680 return 0;
681}
682
690EMSCRIPTEN_KEEPALIVE
691int client_send_video_frame(const uint8_t *rgba_data, int width, int height) {
692 if (!g_initialized) {
693 WASM_ERROR("Client not initialized");
694 return -1;
695 }
696
697 // This is a placeholder - actual implementation would:
698 // 1. Convert RGBA to target format (e.g., compress with libjpeg-turbo)
699 // 2. Build frame packet
700 // 3. Encrypt if handshake complete
701 // 4. Return serialized packet via callback to JS
702
703 WASM_LOG("Video frame processing not yet implemented");
704 return 0;
705}
706
707// ============================================================================
708// Connection State API
709// ============================================================================
710
715EMSCRIPTEN_KEEPALIVE
717 return (int)g_connection_state;
718}
719
720// ============================================================================
721// Memory Management
722// ============================================================================
723
724EMSCRIPTEN_KEEPALIVE
725void client_free_string(char *ptr) {
726 SAFE_FREE(ptr);
727}
728
729// ============================================================================
730// Opus Audio Codec API
731// ============================================================================
732
740EMSCRIPTEN_KEEPALIVE
741int client_opus_encoder_init(int sample_rate, int channels, int bitrate) {
742 if (g_opus_encoder) {
743 opus_encoder_destroy(g_opus_encoder);
744 g_opus_encoder = NULL;
745 }
746
747 int error;
748 g_opus_encoder = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_VOIP, &error);
749 if (error != OPUS_OK || !g_opus_encoder) {
750 WASM_ERROR("Failed to create Opus encoder");
751 return -1;
752 }
753
754 // Set bitrate
755 opus_encoder_ctl(g_opus_encoder, OPUS_SET_BITRATE(bitrate));
756
757 WASM_LOG("Opus encoder initialized");
758 return 0;
759}
760
767EMSCRIPTEN_KEEPALIVE
768int client_opus_decoder_init(int sample_rate, int channels) {
769 if (g_opus_decoder) {
770 opus_decoder_destroy(g_opus_decoder);
771 g_opus_decoder = NULL;
772 }
773
774 int error;
775 g_opus_decoder = opus_decoder_create(sample_rate, channels, &error);
776 if (error != OPUS_OK || !g_opus_decoder) {
777 WASM_ERROR("Failed to create Opus decoder");
778 return -1;
779 }
780
781 WASM_LOG("Opus decoder initialized");
782 return 0;
783}
784
793EMSCRIPTEN_KEEPALIVE
794int client_opus_encode(const int16_t *pcm_data, int frame_size, uint8_t *opus_data, int max_opus_bytes) {
795 if (!g_opus_encoder) {
796 WASM_ERROR("Opus encoder not initialized");
797 return -1;
798 }
799
800 int encoded_bytes = opus_encode(g_opus_encoder, pcm_data, frame_size, opus_data, max_opus_bytes);
801 if (encoded_bytes < 0) {
802 WASM_ERROR("Opus encoding failed");
803 return -1;
804 }
805
806 return encoded_bytes;
807}
808
818EMSCRIPTEN_KEEPALIVE
819int client_opus_decode(const uint8_t *opus_data, int opus_bytes, int16_t *pcm_data, int frame_size, int decode_fec) {
820 if (!g_opus_decoder) {
821 WASM_ERROR("Opus decoder not initialized");
822 return -1;
823 }
824
825 int decoded_samples = opus_decode(g_opus_decoder, opus_data, opus_bytes, pcm_data, frame_size, decode_fec);
826 if (decoded_samples < 0) {
827 WASM_ERROR("Opus decoding failed");
828 return -1;
829 }
830
831 return decoded_samples;
832}
833
837EMSCRIPTEN_KEEPALIVE
839 if (g_opus_encoder) {
840 opus_encoder_destroy(g_opus_encoder);
841 g_opus_encoder = NULL;
842 WASM_LOG("Opus encoder cleaned up");
843 }
844}
845
849EMSCRIPTEN_KEEPALIVE
851 if (g_opus_decoder) {
852 opus_decoder_destroy(g_opus_decoder);
853 g_opus_decoder = NULL;
854 WASM_LOG("Opus decoder cleaned up");
855 }
856}
857
865EMSCRIPTEN_KEEPALIVE
866const char *get_help_text(int mode, const char *option_name) {
867 if (!option_name || !option_name[0]) {
868 return NULL;
869 }
870
871 // Convert int mode to asciichat_mode_t
872 asciichat_mode_t mode_enum = (asciichat_mode_t)mode;
873
874 // Call the C API function
875 return options_get_help_text(mode_enum, option_name);
876}
void ansi_fast_init(void)
Definition ansi_fast.c:198
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Definition buffer_pool.c:99
connection_state_t
Simple TCP connection state machine.
uint32_t asciichat_crc32_sw(const void *data, size_t len)
Definition crc32.c:171
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
asciichat_error_t crypto_handshake_init(crypto_handshake_context_t *ctx, bool is_server)
const char * options_get_help_text(asciichat_mode_t mode, const char *option_name)
Get help text for an option in a specific mode.
Definition help_api.c:27
int socket_t
void platform_destroy(void)
Definition init.c:15
asciichat_error_t platform_init(void)
Definition init.c:10
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)
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)
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)
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)
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)
asciichat_error_t options_init(int argc, char **argv)
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
void options_state_destroy(void)
Definition rcu.c:309
#define WASM_LOG(msg)
#define WASM_LOG_INT(msg, val)
EMSCRIPTEN_KEEPALIVE void client_opus_decoder_cleanup(void)
#define WASM_ERROR(msg)
EMSCRIPTEN_KEEPALIVE void client_free_string(char *ptr)
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)
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 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 const char * get_help_text(int mode, const char *option_name)
EMSCRIPTEN_KEEPALIVE int client_get_connection_state(void)