61static asciichat_error_t handle_client_ascii_frame(
const void *payload,
size_t payload_len,
63static asciichat_error_t handle_client_audio_batch(
const void *payload,
size_t payload_len,
65static asciichat_error_t handle_client_audio_opus(
const void *payload,
size_t payload_len,
67static asciichat_error_t handle_client_audio_opus_batch(
const void *payload,
size_t payload_len,
69static asciichat_error_t handle_client_server_state(
const void *payload,
size_t payload_len,
71static asciichat_error_t handle_client_error_message(
const void *payload,
size_t payload_len,
73static asciichat_error_t handle_client_remote_log(
const void *payload,
size_t payload_len,
75static asciichat_error_t handle_client_ping(
const void *payload,
size_t payload_len,
77static asciichat_error_t handle_client_pong(
const void *payload,
size_t payload_len,
79static asciichat_error_t handle_client_audio(
const void *payload,
size_t payload_len,
81static asciichat_error_t handle_client_clear_console(
const void *payload,
size_t payload_len,
83static asciichat_error_t handle_client_crypto_rekey_request(
const void *payload,
size_t payload_len,
85static asciichat_error_t handle_client_crypto_rekey_response(
const void *payload,
size_t payload_len,
87static asciichat_error_t handle_client_webrtc_sdp(
const void *payload,
size_t payload_len,
89static asciichat_error_t handle_client_webrtc_ice(
const void *payload,
size_t payload_len,
91static asciichat_error_t handle_client_session_joined(
const void *payload,
size_t payload_len,
121 if (!transport || !callbacks) {
130 log_warn(
"Invalid client packet type: %d (out of range)", type);
137 log_warn(
"Unhandled client packet type: %d", type);
142 return handler(payload, payload_len, callbacks);
149static asciichat_error_t handle_client_ascii_frame(
const void *payload,
size_t payload_len,
161 memcpy(&header, payload,
sizeof(header));
185 if (header.
width > MAX_WIDTH || header.
height > MAX_HEIGHT) {
187 MAX_WIDTH, MAX_HEIGHT);
194static asciichat_error_t handle_client_audio_batch(
const void *payload,
size_t payload_len,
213 if (sample_rate == 0 || (sample_rate < 8000 || sample_rate > 192000) ||
214 (sample_rate != 8000 && sample_rate != 16000 && sample_rate != 24000 && sample_rate != 32000 &&
215 sample_rate != 44100 && sample_rate != 48000 && sample_rate != 96000 && sample_rate != 192000)) {
218 "Invalid audio sample rate: %u Hz (expected: 8000, 16000, 24000, 32000, 44100, 48000, 96000, or 192000)",
223 if (channels == 0 || channels > 8) {
232 if (payload_len != expected_size) {
240 float *samples =
SAFE_MALLOC(total_samples *
sizeof(
float),
float *);
264static asciichat_error_t handle_client_audio_opus(
const void *payload,
size_t payload_len,
275static asciichat_error_t handle_client_audio_opus_batch(
const void *payload,
size_t payload_len,
286static asciichat_error_t handle_client_server_state(
const void *payload,
size_t payload_len,
300static asciichat_error_t handle_client_error_message(
const void *payload,
size_t payload_len,
311 const char *message = (
const char *)payload +
sizeof(
error_packet_t);
317 memcpy(msg_buffer, message, copy_len);
318 msg_buffer[copy_len] =
'\0';
324static asciichat_error_t handle_client_remote_log(
const void *payload,
size_t payload_len,
339 char msg_buffer[512];
340 size_t copy_len = msg_len <
sizeof(msg_buffer) - 1 ? msg_len : sizeof(msg_buffer) - 1;
341 memcpy(msg_buffer, message, copy_len);
342 msg_buffer[copy_len] =
'\0';
348static asciichat_error_t handle_client_ping(
const void *payload,
size_t payload_len,
359static asciichat_error_t handle_client_pong(
const void *payload,
size_t payload_len,
370static asciichat_error_t handle_client_audio(
const void *payload,
size_t payload_len,
381static asciichat_error_t handle_client_clear_console(
const void *payload,
size_t payload_len,
392static asciichat_error_t handle_client_crypto_rekey_request(
const void *payload,
size_t payload_len,
400static asciichat_error_t handle_client_crypto_rekey_response(
const void *payload,
size_t payload_len,
408static asciichat_error_t handle_client_webrtc_sdp(
const void *payload,
size_t payload_len,
423static asciichat_error_t handle_client_webrtc_ice(
const void *payload,
size_t payload_len,
438static asciichat_error_t handle_client_session_joined(
const void *payload,
size_t payload_len,
453 if (joined->success) {
454 log_debug(
"Session join succeeded: session_id=%.16s, participant_id=%.16s, server=%s:%u, type=%s",
455 (
const char *)joined->session_id, (
const char *)joined->participant_id, joined->server_address,
456 joined->server_port, joined->session_type == 1 ?
"WebRTC" :
"DirectTCP");
458 log_warn(
"Session join failed: error %d: %s", joined->error_code, joined->error_message);
475static asciichat_error_t handle_server_image_frame(
const void *payload,
size_t payload_len,
void *client_ctx,
477static asciichat_error_t handle_server_audio_batch(
const void *payload,
size_t payload_len,
void *client_ctx,
479static asciichat_error_t handle_server_audio_opus(
const void *payload,
size_t payload_len,
void *client_ctx,
481static asciichat_error_t handle_server_audio_opus_batch(
const void *payload,
size_t payload_len,
void *client_ctx,
483static asciichat_error_t handle_server_client_join(
const void *payload,
size_t payload_len,
void *client_ctx,
485static asciichat_error_t handle_server_client_leave(
const void *payload,
size_t payload_len,
void *client_ctx,
487static asciichat_error_t handle_server_stream_start(
const void *payload,
size_t payload_len,
void *client_ctx,
489static asciichat_error_t handle_server_stream_stop(
const void *payload,
size_t payload_len,
void *client_ctx,
491static asciichat_error_t handle_server_capabilities(
const void *payload,
size_t payload_len,
void *client_ctx,
493static asciichat_error_t handle_server_ping(
const void *payload,
size_t payload_len,
void *client_ctx,
495static asciichat_error_t handle_server_remote_log(
const void *payload,
size_t payload_len,
void *client_ctx,
497static asciichat_error_t handle_server_protocol_version(
const void *payload,
size_t payload_len,
void *client_ctx,
499static asciichat_error_t handle_server_audio(
const void *payload,
size_t payload_len,
void *client_ctx,
501static asciichat_error_t handle_server_pong(
const void *payload,
size_t payload_len,
void *client_ctx,
503static asciichat_error_t handle_server_error_message(
const void *payload,
size_t payload_len,
void *client_ctx,
505static asciichat_error_t handle_server_crypto_rekey_request(
const void *payload,
size_t payload_len,
void *client_ctx,
507static asciichat_error_t handle_server_crypto_rekey_response(
const void *payload,
size_t payload_len,
void *client_ctx,
509static asciichat_error_t handle_server_crypto_rekey_complete(
const void *payload,
size_t payload_len,
void *client_ctx,
535 size_t payload_len,
void *client_ctx,
537 if (!transport || !callbacks) {
546 log_warn(
"Invalid server packet type: %d (out of range)", type);
553 log_warn(
"Unhandled server packet type: %d", type);
558 return handler(payload, payload_len, client_ctx, callbacks);
565static asciichat_error_t handle_server_image_frame(
const void *payload,
size_t payload_len,
void *client_ctx,
577 memcpy(&header, payload,
sizeof(header));
601 if (header.
width > MAX_WIDTH || header.
height > MAX_HEIGHT) {
603 MAX_WIDTH, MAX_HEIGHT);
616static asciichat_error_t handle_server_audio_batch(
const void *payload,
size_t payload_len,
void *client_ctx,
635 if (payload_len != expected_size) {
643 float *samples =
SAFE_MALLOC(total_samples *
sizeof(
float),
float *);
667static asciichat_error_t handle_server_audio_opus(
const void *payload,
size_t payload_len,
void *client_ctx,
677static asciichat_error_t handle_server_audio_opus_batch(
const void *payload,
size_t payload_len,
void *client_ctx,
687static asciichat_error_t handle_server_client_join(
const void *payload,
size_t payload_len,
void *client_ctx,
697static asciichat_error_t handle_server_client_leave(
const void *payload,
size_t payload_len,
void *client_ctx,
710static asciichat_error_t handle_server_stream_start(
const void *payload,
size_t payload_len,
void *client_ctx,
716 if (payload_len <
sizeof(
uint32_t)) {
723 memcpy(&stream_types_net, payload,
sizeof(
uint32_t));
730static asciichat_error_t handle_server_stream_stop(
const void *payload,
size_t payload_len,
void *client_ctx,
736 if (payload_len <
sizeof(
uint32_t)) {
743 memcpy(&stream_types_net, payload,
sizeof(
uint32_t));
750static asciichat_error_t handle_server_capabilities(
const void *payload,
size_t payload_len,
void *client_ctx,
760static asciichat_error_t handle_server_ping(
const void *payload,
size_t payload_len,
void *client_ctx,
771static asciichat_error_t handle_server_remote_log(
const void *payload,
size_t payload_len,
void *client_ctx,
786 char msg_buffer[512];
787 size_t copy_len = msg_len <
sizeof(msg_buffer) - 1 ? msg_len : sizeof(msg_buffer) - 1;
788 memcpy(msg_buffer, message, copy_len);
789 msg_buffer[copy_len] =
'\0';
795static asciichat_error_t handle_server_protocol_version(
const void *payload,
size_t payload_len,
void *client_ctx,
809static asciichat_error_t handle_server_audio(
const void *payload,
size_t payload_len,
void *client_ctx,
816 callbacks->
on_audio(payload, payload_len, client_ctx, callbacks->
app_ctx);
820static asciichat_error_t handle_server_pong(
const void *payload,
size_t payload_len,
void *client_ctx,
831static asciichat_error_t handle_server_error_message(
const void *payload,
size_t payload_len,
void *client_ctx,
842 const char *message = (
const char *)payload +
sizeof(
error_packet_t);
848 memcpy(msg_buffer, message, copy_len);
849 msg_buffer[copy_len] =
'\0';
851 callbacks->
on_error(header, msg_buffer, client_ctx, callbacks->
app_ctx);
855static asciichat_error_t handle_server_crypto_rekey_request(
const void *payload,
size_t payload_len,
void *client_ctx,
863static asciichat_error_t handle_server_crypto_rekey_response(
const void *payload,
size_t payload_len,
void *client_ctx,
871static asciichat_error_t handle_server_crypto_rekey_complete(
const void *payload,
size_t payload_len,
void *client_ctx,
⚠️‼️ Error and/or exit() when things go bad.
🔄 Network byte order conversion helpers
#define NET_TO_HOST_U32(val)
asciichat_error_t audio_dequantize_samples(const uint8_t *samples_ptr, uint32_t total_samples, float *out_samples)
Dequantize network audio samples from int32 to float.
#define SAFE_MALLOC(size, cast)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define log_warn(...)
Log a WARN message.
#define log_debug(...)
Log a DEBUG message.
uint32_t pixel_format
Pixel format enum (0=RGB24, 1=RGBA32, 2=BGR24, etc.)
uint32_t timestamp
Timestamp when frame was captured (milliseconds since epoch)
uint32_t width
Terminal width in characters.
uint32_t checksum
CRC32 checksum of original ASCII data.
uint32_t original_size
Size of original uncompressed ASCII data in bytes.
uint32_t height
Terminal height in characters.
uint32_t compressed_size
Size of compressed data (0 = not compressed)
uint32_t compressed_size
Compressed data size (0 = not compressed, >0 = compressed)
uint32_t checksum
CRC32 checksum of pixel data.
uint32_t channels
Number of audio channels (1=mono, 2=stereo)
uint32_t height
Image height in pixels.
uint32_t batch_count
Number of audio chunks in this batch (usually AUDIO_BATCH_COUNT = 32)
uint32_t width
Image width in pixels.
uint32_t flags
Frame flags bitmask (HAS_COLOR, IS_COMPRESSED, etc.)
uint32_t sample_rate
Sample rate in Hz (e.g., 44100, 48000)
uint32_t total_samples
Total audio samples across all chunks (typically 8192)
packet_type_t
Network protocol packet type enumeration.
#define MAX_ERROR_MESSAGE_LENGTH
Maximum error message length (512 bytes)
@ PACKET_TYPE_ACIP_WEBRTC_ICE
WebRTC ICE candidate (bidirectional)
@ PACKET_TYPE_AUDIO_OPUS_BATCH
Batched Opus-encoded audio frames.
@ PACKET_TYPE_AUDIO_OPUS
Opus-encoded single audio frame.
@ PACKET_TYPE_CLIENT_LEAVE
Clean disconnect notification.
@ PACKET_TYPE_IMAGE_FRAME
Complete RGB image with dimensions.
@ PACKET_TYPE_PONG
Keepalive pong response.
@ PACKET_TYPE_STREAM_START
Client requests to start sending video/audio.
@ PACKET_TYPE_AUDIO
Single audio packet (legacy)
@ PACKET_TYPE_CRYPTO_REKEY_COMPLETE
Initiator -> Responder: Empty (encrypted with NEW key, but still handshake)
@ PACKET_TYPE_SERVER_STATE
Server sends current state to clients.
@ PACKET_TYPE_CRYPTO_REKEY_REQUEST
Initiator -> Responder: {new_ephemeral_pk[32]} (UNENCRYPTED during rekey)
@ PACKET_TYPE_REMOTE_LOG
Bidirectional remote logging packet.
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
@ PACKET_TYPE_CLEAR_CONSOLE
Server tells client to clear console.
@ PACKET_TYPE_ASCII_FRAME
Complete ASCII frame with all metadata.
@ PACKET_TYPE_ACIP_WEBRTC_SDP
WebRTC SDP offer/answer (bidirectional)
@ PACKET_TYPE_CLIENT_JOIN
Client announces capability to send media.
@ PACKET_TYPE_CLIENT_CAPABILITIES
Client reports terminal capabilities.
@ PACKET_TYPE_ERROR_MESSAGE
Error packet with asciichat_error_t code and human-readable message.
@ PACKET_TYPE_PING
Keepalive ping packet.
@ PACKET_TYPE_AUDIO_BATCH
Batched audio packets for efficiency.
@ PACKET_TYPE_CRYPTO_REKEY_RESPONSE
Responder -> Initiator: {new_ephemeral_pk[32]} (UNENCRYPTED during rekey)
@ PACKET_TYPE_ACIP_SESSION_JOINED
Session joined response (Discovery Server -> Client)
@ PACKET_TYPE_STREAM_STOP
Client stops sending media.
asciichat_error_t acip_handle_server_packet(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, void *client_ctx, const acip_server_callbacks_t *callbacks)
Handle incoming packet on server side.
asciichat_error_t acip_handle_client_packet(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, const acip_client_callbacks_t *callbacks)
Handle incoming packet on client side.
asciichat_error_t(* acip_client_handler_func_t)(const void *payload, size_t payload_len, const acip_client_callbacks_t *callbacks)
Client-side packet handler function pointer type.
asciichat_error_t(* acip_server_handler_func_t)(const void *payload, size_t payload_len, void *client_ctx, const acip_server_callbacks_t *callbacks)
Server-side packet handler function pointer type.
ACIP protocol packet handlers (transport-agnostic)
🔊 Audio Capture and Playback Interface for ascii-chat
📝 Logging API with multiple log levels and terminal output control
ACIP protocol message structures and helpers.
ASCII-Chat Discovery Service (ACDS) Protocol Message Formats.
Packet protocol implementation with encryption and compression support.
Shared packet parsing utilities to eliminate duplication between server and client handlers.
Client-side packet handler callbacks.
void(* on_clear_console)(void *ctx)
Called when server requests console clear.
void(* on_session_joined)(const acip_session_joined_t *joined, void *ctx)
Called when ACDS session join response received (Phase 3 WebRTC integration)
void(* on_webrtc_sdp)(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx)
Called when WebRTC SDP offer/answer received.
void(* on_error)(const error_packet_t *header, const char *message, void *ctx)
Called when error message received.
void(* on_audio_opus_batch)(const void *batch_data, size_t batch_len, void *ctx)
Called when Opus batch received from server.
void(* on_ping)(void *ctx)
Called when ping received (should send pong)
void(* on_pong)(void *ctx)
Called when pong received.
void(* on_audio_batch)(const audio_batch_packet_t *header, const float *samples, size_t num_samples, void *ctx)
Called when audio batch received from server.
void(* on_crypto_rekey_request)(const void *payload, size_t payload_len, void *ctx)
Called when server sends crypto rekey request.
void(* on_webrtc_ice)(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx)
Called when WebRTC ICE candidate received.
void(* on_crypto_rekey_response)(const void *payload, size_t payload_len, void *ctx)
Called when server sends crypto rekey response.
void(* on_audio_opus)(const void *opus_data, size_t opus_len, void *ctx)
Called when Opus audio received from server.
void(* on_remote_log)(const remote_log_packet_t *header, const char *message, void *ctx)
Called when remote log received.
void(* on_ascii_frame)(const ascii_frame_packet_t *header, const void *frame_data, size_t data_len, void *ctx)
Called when ASCII frame received from server.
void * app_ctx
Application context (passed to all callbacks)
void(* on_server_state)(const server_state_packet_t *state, void *ctx)
Called when server state update received.
void(* on_audio)(const void *audio_data, size_t audio_len, void *ctx)
Called when raw audio received from server.
Server-side packet handler callbacks.
void(* on_image_frame)(const image_frame_packet_t *header, const void *pixel_data, size_t data_len, void *client_ctx, void *app_ctx)
Called when client sends image frame.
void(* on_capabilities)(const void *cap_data, size_t data_len, void *client_ctx, void *app_ctx)
Called when client sends capabilities.
void(* on_pong)(void *client_ctx, void *app_ctx)
Called when pong received from client.
void * app_ctx
Application context (passed to all callbacks)
void(* on_stream_start)(uint32_t stream_types, void *client_ctx, void *app_ctx)
Called when client starts streaming.
void(* on_ping)(void *client_ctx, void *app_ctx)
Called when ping received (should send pong)
void(* on_stream_stop)(uint32_t stream_types, void *client_ctx, void *app_ctx)
Called when client stops streaming.
void(* on_crypto_rekey_response)(const void *payload, size_t payload_len, void *client_ctx, void *app_ctx)
Called when client sends crypto rekey response.
void(* on_client_leave)(void *client_ctx, void *app_ctx)
Called when client leaves.
void(* on_remote_log)(const remote_log_packet_t *header, const char *message, void *client_ctx, void *app_ctx)
Called when remote log received from client.
void(* on_error)(const error_packet_t *header, const char *message, void *client_ctx, void *app_ctx)
Called when error message received from client.
void(* on_crypto_rekey_complete)(const void *payload, size_t payload_len, void *client_ctx, void *app_ctx)
Called when client sends crypto rekey complete.
void(* on_client_join)(const void *join_data, size_t data_len, void *client_ctx, void *app_ctx)
Called when client joins.
void(* on_audio_opus_batch)(const void *batch_data, size_t batch_len, void *client_ctx, void *app_ctx)
Called when client sends Opus batch.
void(* on_audio_batch)(const audio_batch_packet_t *header, const float *samples, size_t num_samples, void *client_ctx, void *app_ctx)
Called when client sends audio batch.
void(* on_audio)(const void *audio_data, size_t audio_len, void *client_ctx, void *app_ctx)
Called when client sends raw audio.
void(* on_crypto_rekey_request)(const void *payload, size_t payload_len, void *client_ctx, void *app_ctx)
Called when client sends crypto rekey request.
void(* on_protocol_version)(const protocol_version_packet_t *version, void *client_ctx, void *app_ctx)
Called when client sends protocol version.
void(* on_audio_opus)(const void *opus_data, size_t opus_len, void *client_ctx, void *app_ctx)
Called when client sends Opus audio.
Transport instance structure.
ASCII frame packet structure (Packet Type 2)
Audio batch packet structure (Packet Type 28)
Error packet structure carrying error code and textual description.
Image frame packet structure (Packet Type 3)
Protocol version negotiation packet structure (Packet Type 1)
Remote log packet structure carrying log level and message text.
Server state packet structure.