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)
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');
25 const packetArray =
new Uint8Array(HEAPU8.buffer, packet_data, packet_len);
26 const packetCopy =
new Uint8Array(packetArray);
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));
32 Module.sendPacketCallback(packetCopy);
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>
64static asciichat_error_t wasm_transport_send(acip_transport_t *transport,
const void *data,
size_t len) {
65 WASM_LOG(
"wasm_transport_send called");
69 js_send_raw_packet((
const uint8_t *)data, len);
71 WASM_LOG(
"wasm_transport_send: packet sent to JS");
75static asciichat_error_t wasm_transport_recv(acip_transport_t *transport,
void **buffer,
size_t *out_len,
76 void **out_allocated_buffer) {
78 return SET_ERRNO(ERROR_NOT_SUPPORTED,
"recv not supported on WASM transport");
81static asciichat_error_t wasm_transport_close(acip_transport_t *transport) {
85static acip_transport_type_t wasm_transport_get_type(acip_transport_t *transport) {
86 return ACIP_TRANSPORT_WEBSOCKET;
89static socket_t wasm_transport_get_socket(acip_transport_t *transport) {
90 return INVALID_SOCKET_VALUE;
93static bool wasm_transport_is_connected(acip_transport_t *transport) {
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};
105static acip_transport_t g_wasm_transport = {.methods = &wasm_transport_methods, .crypto_ctx = NULL, .impl_data = NULL};
112static crypto_handshake_context_t g_crypto_handshake_ctx = {0};
113static bool g_initialized =
false;
114static bool g_handshake_complete =
false;
117static OpusEncoder *g_opus_encoder = NULL;
118static OpusDecoder *g_opus_decoder = NULL;
144 char *args_copy = strdup(args_json);
152 char *argv[64] = {NULL};
153 char *token = strtok(args_copy,
" ");
154 while (token != NULL && argc < 63) {
155 argv[argc++] = token;
156 token = strtok(NULL,
" ");
162 WASM_LOG(
"Calling options_init...");
165 WASM_LOG(
"client_init_with_args: START");
173 WASM_LOG(
"Calling platform_init...");
175 if (err != ASCIICHAT_OK) {
183 log_init(NULL, LOG_DEBUG,
true,
false);
185 log_info(
"WASM client initialized via logging system");
187 if (err != ASCIICHAT_OK) {
194 WASM_LOG(
"Calling ansi_fast_init...");
198 g_initialized =
true;
201 WASM_LOG(
"client_init_with_args: COMPLETE");
207 WASM_LOG(
"=== client_cleanup CALLED ===");
209 WASM_LOG_INT(
" g_connection_state", g_connection_state);
210 WASM_LOG_INT(
" g_crypto_handshake_ctx.state", g_crypto_handshake_ctx.state);
214 memset(&g_crypto_handshake_ctx, 0,
sizeof(g_crypto_handshake_ctx));
216 g_handshake_complete =
false;
218 g_initialized =
false;
222 WASM_LOG(
"=== client_cleanup COMPLETE ===");
235 WASM_LOG(
"=== client_generate_keypair CALLED ===");
237 WASM_LOG_INT(
" g_crypto_handshake_ctx.state BEFORE", g_crypto_handshake_ctx.state);
239 if (!g_initialized) {
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);
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;
256 WASM_LOG(
"Calling crypto_handshake_init...");
257 WASM_LOG_INT(
" State before init", g_crypto_handshake_ctx.state);
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);
265 WASM_LOG(
"Keypair generated successfully");
266 WASM_LOG_INT(
" g_crypto_handshake_ctx.state AFTER init", g_crypto_handshake_ctx.state);
279 if (!g_initialized) {
284 if (!server_host || server_port <= 0 || server_port > 65535) {
285 WASM_ERROR(
"Invalid server address parameters");
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;
303 if (g_crypto_handshake_ctx.state == CRYPTO_HANDSHAKE_DISABLED) {
304 WASM_ERROR(
"No crypto context (call client_generate_keypair first)");
309 const uint8_t *pubkey = g_crypto_handshake_ctx.crypto_ctx.public_key;
311 WASM_ERROR(
"Public key not available in crypto context");
316 char *hex_buffer = SAFE_MALLOC(65,
char *);
323 for (
int i = 0; i < 32; i++) {
324 snprintf(hex_buffer + (i * 2), 3,
"%02x", pubkey[i]);
326 hex_buffer[64] =
'\0';
340 WASM_LOG(
"=== client_handle_key_exchange_init CALLED ===");
342 WASM_LOG_INT(
" g_crypto_handshake_ctx.state BEFORE", g_crypto_handshake_ctx.state);
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);
351 if (g_crypto_handshake_ctx.state != CRYPTO_HANDSHAKE_DISABLED) {
354 memset(&g_crypto_handshake_ctx, 0,
sizeof(g_crypto_handshake_ctx));
358 if (init_result != ASCIICHAT_OK) {
359 WASM_ERROR(
"Failed to reinitialize crypto handshake context");
363 WASM_LOG(
"Crypto handshake context reinitialized");
366 if (!packet || packet_len == 0) {
372 if (packet_len <
sizeof(packet_header_t)) {
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);
388 uint8_t *payload = NULL;
389 if (payload_len > 0) {
392 WASM_ERROR(
"Failed to allocate payload buffer");
396 memcpy(payload, payload_src, payload_len);
400 WASM_LOG(
"Calling crypto_handshake_client_key_exchange...");
403 packet_type, payload, payload_len);
406 WASM_LOG_INT(
" g_crypto_handshake_ctx.state AFTER", g_crypto_handshake_ctx.state);
408 if (result != ASCIICHAT_OK) {
409 WASM_ERROR(
"Failed to process KEY_EXCHANGE_INIT");
415 WASM_LOG(
"=== KEY_EXCHANGE_INIT processed successfully ===");
427 WASM_LOG(
"=== client_handle_auth_challenge CALLED ===");
429 WASM_LOG_INT(
" g_crypto_handshake_ctx.state", g_crypto_handshake_ctx.state);
431 if (!packet || packet_len == 0) {
437 if (packet_len <
sizeof(packet_header_t)) {
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);
451 uint8_t *payload = NULL;
452 if (payload_len > 0) {
455 WASM_ERROR(
"Failed to allocate payload buffer");
459 memcpy(payload, payload_src, payload_len);
464 packet_type, payload, payload_len);
468 if (result != ASCIICHAT_OK) {
469 WASM_ERROR(
"Failed to process AUTH_CHALLENGE");
474 WASM_LOG(
"=== AUTH_CHALLENGE processed successfully ===");
486 WASM_LOG(
"=== client_handle_handshake_complete CALLED ===");
488 WASM_LOG_INT(
" g_crypto_handshake_ctx.state", g_crypto_handshake_ctx.state);
490 if (!packet || packet_len == 0) {
496 if (packet_len <
sizeof(packet_header_t)) {
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);
509 uint8_t *payload = NULL;
510 if (payload_len > 0) {
513 WASM_ERROR(
"Failed to allocate payload buffer");
517 memcpy(payload, payload_src, payload_len);
521 asciichat_error_t result =
526 if (result != ASCIICHAT_OK) {
532 g_handshake_complete =
true;
534 WASM_LOG(
"=== HANDSHAKE COMPLETE - session encrypted ===");
552int client_encrypt_packet(
const uint8_t *plaintext,
size_t plaintext_len, uint8_t *ciphertext,
size_t ciphertext_size,
554 if (!g_handshake_complete) {
555 WASM_ERROR(
"Encryption requires completed handshake");
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) {
568 *out_len = ciphertext_len;
582int client_decrypt_packet(
const uint8_t *ciphertext,
size_t ciphertext_len, uint8_t *plaintext,
size_t plaintext_size,
584 if (!g_handshake_complete) {
585 WASM_ERROR(
"Decryption requires completed handshake");
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) {
598 *out_len = plaintext_len;
610 if (!raw_packet || packet_len <
sizeof(packet_header_t)) {
616 const packet_header_t *header = (
const packet_header_t *)raw_packet;
619 uint64_t magic = NET_TO_HOST_U64(header->magic);
620 if (magic != PACKET_MAGIC) {
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);
632 char *json = SAFE_MALLOC(1024,
char *);
638 snprintf(json, 1024,
"{\"type\":%u,\"length\":%u,\"client_id\":%u,\"crc32\":%u}", type, length, client_id, crc32);
655 uint8_t *output,
size_t *out_len) {
656 if (!output || !out_len) {
662 uint32_t crc = payload && payload_len > 0 ?
asciichat_crc32_sw(payload, payload_len) : 0;
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)};
672 memcpy(output, &header,
sizeof(packet_header_t));
675 if (payload && payload_len > 0) {
676 memcpy(output +
sizeof(packet_header_t), payload, payload_len);
679 *out_len =
sizeof(packet_header_t) + payload_len;
692 if (!g_initialized) {
703 WASM_LOG(
"Video frame processing not yet implemented");
717 return (
int)g_connection_state;
742 if (g_opus_encoder) {
743 opus_encoder_destroy(g_opus_encoder);
744 g_opus_encoder = NULL;
748 g_opus_encoder = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_VOIP, &error);
749 if (error != OPUS_OK || !g_opus_encoder) {
755 opus_encoder_ctl(g_opus_encoder, OPUS_SET_BITRATE(bitrate));
757 WASM_LOG(
"Opus encoder initialized");
769 if (g_opus_decoder) {
770 opus_decoder_destroy(g_opus_decoder);
771 g_opus_decoder = NULL;
775 g_opus_decoder = opus_decoder_create(sample_rate, channels, &error);
776 if (error != OPUS_OK || !g_opus_decoder) {
781 WASM_LOG(
"Opus decoder initialized");
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) {
800 int encoded_bytes = opus_encode(g_opus_encoder, pcm_data, frame_size, opus_data, max_opus_bytes);
801 if (encoded_bytes < 0) {
806 return encoded_bytes;
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) {
825 int decoded_samples = opus_decode(g_opus_decoder, opus_data, opus_bytes, pcm_data, frame_size, decode_fec);
826 if (decoded_samples < 0) {
831 return decoded_samples;
839 if (g_opus_encoder) {
840 opus_encoder_destroy(g_opus_encoder);
841 g_opus_encoder = NULL;
842 WASM_LOG(
"Opus encoder cleaned up");
851 if (g_opus_decoder) {
852 opus_decoder_destroy(g_opus_decoder);
853 g_opus_decoder = NULL;
854 WASM_LOG(
"Opus decoder cleaned up");
867 if (!option_name || !option_name[0]) {
872 asciichat_mode_t mode_enum = (asciichat_mode_t)mode;
void ansi_fast_init(void)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
connection_state_t
Simple TCP connection state machine.
uint32_t asciichat_crc32_sw(const void *data, size_t len)
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.
void platform_destroy(void)
asciichat_error_t platform_init(void)
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)
#define WASM_LOG_INT(msg, val)
EMSCRIPTEN_KEEPALIVE void client_opus_decoder_cleanup(void)
EMSCRIPTEN_KEEPALIVE void client_free_string(char *ptr)
@ 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)