ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
transport.h File Reference

Transport abstraction layer for ACIP protocol. More...

Go to the source code of this file.

Data Structures

struct  acip_transport_capabilities_t
 The useful part of acip transport capabilities is in independent_streams, datagrams, max_datagram_payload: together they describe what is pending, who owns it, and how much data is valid. Keep the object with its parent operation and use the parent cleanup path. application data in one QUIC DATAGRAM frame on the current path, including any transport-specific envelope. Query again after path changes. More...
struct  acip_transport_methods_t
 Transport method table (virtual function table). More...
struct  acip_transport
 Transport instance structure. More...

Typedefs

typedef struct acip_websocket_runtime acip_websocket_runtime_t

Enumerations

enum  acip_transport_type_t {
  ACIP_TRANSPORT_TCP = 1 , ACIP_TRANSPORT_WEBSOCKET = 2 , ACIP_TRANSPORT_WEBRTC = 3 , ACIP_TRANSPORT_HTTP = 4 ,
  ACIP_TRANSPORT_QUIC = 5
}
 Transport method table (virtual function table). More...
enum  acip_transport_ownership_t { ACIP_TRANSPORT_BORROWED , ACIP_TRANSPORT_OWNED }

Functions

void acip_transport_destroy (acip_transport_t *transport)
 Destroy transport and free all resources.
asciichat_error_t acip_transport_sendv (acip_transport_t *transport, const socket_buffer_t *buffers, size_t count)
 Send data through transport.
asciichat_error_t acip_transport_sendv_ex (acip_transport_t *transport, const socket_buffer_t *buffers, size_t count, const acip_send_options_t *options)
asciichat_error_t acip_transport_get_capabilities (acip_transport_t *transport, acip_transport_capabilities_t *out)
asciichat_error_t acip_transport_send (acip_transport_t *transport, const void *data, size_t len)
acip_transport_t * acip_tcp_transport_create (const char *name, socket_t sockfd, crypto_context_t *crypto_ctx)
 Create TCP transport from existing socket.
acip_transport_t * acip_tcp_transport_create_owned (const char *name, socket_t sockfd, crypto_context_t *crypto_ctx)
 Create a TCP transport that owns its connected socket.
asciichat_error_t acip_tcp_transport_set_zerocopy (acip_transport_t *transport, bool enabled)
asciichat_error_t acip_websocket_transport_flush (acip_transport_t *transport, uint64_t timeout_ns)
 Create WebSocket client transport.
asciichat_error_t acip_websocket_runtime_create (acip_websocket_runtime_t **out)
void acip_websocket_runtime_destroy (acip_websocket_runtime_t **runtime)
acip_transport_t * acip_websocket_client_transport_create (const char *name, const char *url, crypto_context_t *crypto_ctx)
 Create WebSocket client transport.
acip_transport_t * acip_websocket_server_transport_create (const char *name, struct lws *wsi, crypto_context_t *crypto_ctx)
 Create WebSocket server transport from existing connection.
acip_transport_t * acip_webrtc_transport_create (struct webrtc_peer_connection *peer_conn, struct webrtc_data_channel *data_channel, crypto_context_t *crypto_ctx)

Detailed Description

Transport abstraction layer for ACIP protocol.

Provides a transport-agnostic interface that allows ACIP protocol handlers to work with any underlying transport (TCP, WebSocket, HTTP, etc.).

Transport Layer Design

Architecture

The transport layer abstracts protocol delivery, allowing the same ACIP protocol handlers to work with multiple underlying transports:

  • TCP (acip_tcp_transport_create): Raw TCP sockets, binary packet streaming
  • WebSocket (acip_websocket_client_transport_create): HTTP upgrade, frame-based
  • WebRTC (acip_webrtc_transport_create): P2P data channels, encryption at transport
  • HTTP (reserved): Long-polling, REST-style transport slot
  • QUIC: UDP-based transport with congestion control and optional datagrams

Design Principles

  1. Protocol code never calls socket()/send()/recv() directly
  2. Same ACIP handlers work with any transport implementation
  3. Each transport handles its own connection state and reliability
  4. Clean separation between protocol logic (acip_*) and transport logic

Packet Framing

Transports handle packet framing transparently:

  • TCP: Uses packet_header_t (magic + type + length + CRC32 + client_id)
  • WebSocket: Wraps packets in WebSocket frames
  • WebRTC: Sends packets directly on data channel

Version Information

  • Transport API Version: 1.0 (stable)
  • Supported ACIP Version: 1.0

DESIGN GOALS:

  1. Protocol code never calls socket()/send()/recv() directly
  2. Same ACIP handlers work with TCP, WebSocket, WebRTC, QUIC, or another backend
  3. Transport implementations handle their own connection state
  4. Clean separation between protocol logic and transport logic

USAGE PATTERN:

// Create TCP transport from existing socket
acip_transport_t *tcp = acip_tcp_transport_create(sockfd, crypto_ctx);
// Send packet with type-specific helper
asciichat_error_t result = acip_send_ascii_frame(tcp, frame_data, frame_size, ...);
// Receive packet with dispatcher
// Or create WebSocket client transport
acip_transport_t *ws = acip_websocket_client_transport_create("ws://localhost:27225", crypto_ctx);
asciichat_error_t result = acip_send_ascii_frame(ws, frame_data, frame_size, ...);
// Cleanup
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
struct acip_transport acip_transport_t
Forward declaration of ACIP transport.
Definition host.h:506
asciichat_error_t acip_client_receive_and_dispatch(acip_transport_t *transport, const acip_client_callbacks_t *callbacks)
Receive packet from server and dispatch to callbacks.
asciichat_error_t acip_send_ascii_frame(acip_transport_t *transport, acip_frame_workspace_t *workspace, const char *text, size_t length, uint32_t width, uint32_t height, const char *client_id)
acip_transport_t * acip_tcp_transport_create(const char *name, socket_t sockfd, crypto_context_t *crypto_ctx)
Create TCP transport from existing socket.
void acip_transport_destroy(acip_transport_t *transport)
Destroy transport and free all resources.
acip_transport_t * acip_websocket_client_transport_create(const char *name, const char *url, crypto_context_t *crypto_ctx)
Create WebSocket client transport.

MEMORY OWNERSHIP:

  • Transport owns its connection state (socket, pointers, etc.)
  • send() does NOT take ownership of data (caller must keep it valid)
  • recv() allocates buffer, caller must free via out_allocated_buffer
  • destroy() cleans up all transport resources and frees transport structure
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
January 2026
Version
1.0 (Transport API)

Definition in file include/ascii-chat/network/acip/transport.h.

Typedef Documentation

◆ acip_websocket_runtime_t

Application lifetime reference for libwebsockets TLS initialization. Keep it alive across connection contexts and release it after the last user.

Definition at line 462 of file include/ascii-chat/network/acip/transport.h.

Enumeration Type Documentation

◆ acip_transport_ownership_t

Ownership transfers only after successful registration.

Enumerator
ACIP_TRANSPORT_BORROWED 
ACIP_TRANSPORT_OWNED 

Definition at line 125 of file include/ascii-chat/network/acip/transport.h.

◆ acip_transport_type_t

Transport method table (virtual function table).

This is the handoff point for acip transport. Callers fill outbound, stats_peer, methods, crypto_ctx before passing it on; consumers read the values during the operation and return ownership only where the member contract explicitly says they do.

Transport type enumeration

Identifies which transport implementation is being used. Useful for debugging and conditional logic.

Enumerator
ACIP_TRANSPORT_TCP 

Raw TCP socket.

ACIP_TRANSPORT_WEBSOCKET 

WebSocket over TCP.

ACIP_TRANSPORT_WEBRTC 

WebRTC DataChannel (P2P).

ACIP_TRANSPORT_HTTP 

HTTP long-polling (future).

ACIP_TRANSPORT_QUIC 

QUIC/UDP (future).

Definition at line 98 of file include/ascii-chat/network/acip/transport.h.

98 {
acip_transport_type_t
Transport method table (virtual function table).
@ ACIP_TRANSPORT_TCP
Raw TCP socket.
@ ACIP_TRANSPORT_WEBSOCKET
WebSocket over TCP.
@ ACIP_TRANSPORT_WEBRTC
WebRTC DataChannel (P2P).
@ ACIP_TRANSPORT_HTTP
HTTP long-polling (future).
@ ACIP_TRANSPORT_QUIC
QUIC/UDP (future).

Function Documentation

◆ acip_tcp_transport_create()

acip_transport_t * acip_tcp_transport_create ( const char * name,
socket_t sockfd,
crypto_context_t * crypto_ctx )

Create TCP transport from existing socket.

Parameters
nameDebug name for tracking this transport (required, e.g., "transport_client_5")
sockfdConnected socket descriptor
crypto_ctxOptional crypto context (may be NULL)
Returns
Transport instance or NULL on error
Note
Caller retains socket ownership (transport doesn't close on destroy)

Definition at line 293 of file tcp/transport.c.

293 {
294 if (!name) {
295 SET_ERRNO(ERROR_INVALID_STATE, "Transport name is required");
296 return NULL;
297 }
298
299 if (sockfd == INVALID_SOCKET_VALUE) {
300 log_error("[TCP_CREATE_STATE] ❌ INVALID_SOCKET: sockfd=%d", sockfd);
301 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket descriptor");
302 return NULL;
303 }
304
305 log_info("[TCP_CREATE_STATE] 🟢 CREATE_START: sockfd=%d, crypto=%s", sockfd, crypto_ctx ? "yes" : "no");
306
307 // Allocate transport structure
309 if (!transport) {
310 log_error("[TCP_CREATE_STATE] ❌ ALLOC_TRANSPORT_FAILED: size=%zu", sizeof(acip_transport_t));
311 SET_ERRNO(ERROR_MEMORY, "Failed to allocate TCP transport");
312 return NULL;
313 }
314
315 log_debug("[TCP_CREATE_STATE] 🟢 TRANSPORT_ALLOCATED: transport=%p", (void *)transport);
316
317 // Allocate TCP-specific data
319 if (!tcp_data) {
320 log_error("[TCP_CREATE_STATE] ❌ ALLOC_TCP_DATA_FAILED: size=%zu", sizeof(tcp_transport_data_t));
321 SAFE_FREE(transport);
322 SET_ERRNO(ERROR_MEMORY, "Failed to allocate TCP transport data");
323 return NULL;
324 }
325
326 log_debug("[TCP_CREATE_STATE] 🟢 TCP_DATA_ALLOCATED: tcp_data=%p", (void *)tcp_data);
327
328 // Initialize TCP data
329 tcp_data->sockfd = sockfd;
330 tcp_data->is_connected = true;
331 tcp_data->owns_socket = false;
332 tcp_data->zerocopy_disabled = false;
333
334 // Initialize send mutex to protect concurrent sends from multiple threads
335 int mutex_result = mutex_init(&tcp_data->send_mutex, "tcp_send");
336 if (mutex_result != 0) {
337 log_error("[TCP_CREATE_STATE] ❌ MUTEX_INIT_FAILED: result=%d", mutex_result);
338 SAFE_FREE(tcp_data);
339 SAFE_FREE(transport);
340 SET_ERRNO(ERROR_MEMORY, "Failed to initialize send mutex");
341 return NULL;
342 }
343
344 log_info("[TCP_CREATE_STATE] 🟢 TCP_DATA_INITIALIZED: sockfd=%d, is_connected=true, send_mutex=initialized", sockfd);
345
346 // Enable TCP_NODELAY to disable Nagle's algorithm
347 // This ensures small packets are sent immediately instead of being buffered
348 int nodelay = 1;
349 if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (const char *)&nodelay, sizeof(nodelay)) < 0) {
350 log_warn("[TCP_CREATE_STATE] ⚠️ TCP_NODELAY_FAILED: sockfd=%d, %s", sockfd, SAFE_STRERROR(errno));
351 // Continue anyway - this is not fatal
352 } else {
353 log_debug("[TCP_CREATE_STATE] 🟢 TCP_NODELAY_SET: sockfd=%d", sockfd);
354 }
355
356 // Initialize transport
357 transport->methods = &tcp_methods;
358 transport->stats_peer = stats_runtime_peer_open("TCP");
359 transport->crypto_ctx = crypto_ctx;
360 transport->impl_data = tcp_data;
361 int send_buffer = SOCKET_STREAM_SEND_BUFFER;
362 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &send_buffer, sizeof(send_buffer)) != 0) {
363 acip_transport_destroy(transport);
364 SET_ERRNO_SYS(ERROR_NETWORK, "Cannot bound TCP send buffer");
365 return NULL;
366 }
367
368 // Register transport and impl_data before logging so named replacements work
369 NAMED_REGISTER_TRANSPORT(transport, name, NULL);
370 NAMED_REGISTER(tcp_data, "impl", "tcp_impl", "0x%tx", (uintptr_t)(const void *)(transport));
371
372 log_info("[TCP_CREATE_STATE] ✅ CREATE_COMPLETE: transport=%p, sockfd=%d, is_connected=true, crypto=%s",
373 (void *)transport, sockfd, crypto_ctx ? "enabled" : "disabled");
374
375#ifndef __EMSCRIPTEN__
376 if (transport && acip_outbound_create(transport) != ASCIICHAT_OK) {
377 acip_transport_destroy(transport);
378 return NULL;
379 }
380#endif
381 return transport;
382}
#define SET_ERRNO(code,...)
#define SET_ERRNO_SYS(code,...)
asciichat_error_t acip_outbound_create(acip_transport_t *transport)
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define SAFE_STRERROR(errnum)
Definition common.h:480
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
#define NAMED_REGISTER_TRANSPORT(transport, name, parent_ptr)
Register a transport with automatic format specifier.
#define NAMED_REGISTER(ptr, name, type, fmt, parent_ptr)
Register any pointer with base name, type, format spec, and location (auto-suffix).
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:72
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
int errno
stats_peer_t * stats_runtime_peer_open(const char *transport)
Definition runtime.c:152
void * impl_data
Transport-specific state.
stats_peer_t * stats_peer
Owned statistics handle, destroyed with transport.
const acip_transport_methods_t * methods
Method table (virtual functions).
crypto_context_t * crypto_ctx
Optional encryption context.
tcp transport data is intentionally a small, concrete record. Fields such as sockfd,...
bool owns_socket
Close the socket when this transport is closed or destroyed.
socket_t sockfd
Connected socket descriptor.
bool is_connected
Connection state.
mutex_t send_mutex
Mutex to protect concurrent sends (multiple threads may send packets).
bool zerocopy_disabled
Disable copy avoidance after unsupported or copied completion.

References acip_outbound_create(), acip_transport_destroy(), ASCIICHAT_OK, acip_transport::crypto_ctx, errno, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_NETWORK, acip_transport::impl_data, tcp_transport_data_t::is_connected, log_debug, log_error, log_info, log_warn, acip_transport::methods, NAMED_REGISTER, NAMED_REGISTER_TRANSPORT, tcp_transport_data_t::owns_socket, SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, SAFE_STRERROR, tcp_transport_data_t::send_mutex, SET_ERRNO, SET_ERRNO_SYS, tcp_transport_data_t::sockfd, acip_transport::stats_peer, stats_runtime_peer_open(), and tcp_transport_data_t::zerocopy_disabled.

Referenced by acip_tcp_transport_create_owned().

◆ acip_tcp_transport_create_owned()

acip_transport_t * acip_tcp_transport_create_owned ( const char * name,
socket_t sockfd,
crypto_context_t * crypto_ctx )

Create a TCP transport that owns its connected socket.

Parameters
nameDebug name for the transport
sockfdConnected socket; ownership transfers only on success
crypto_ctxOptional borrowed crypto context
Returns
Transport instance or NULL on error Closing or destroying the transport closes the socket.

Definition at line 384 of file tcp/transport.c.

384 {
385 acip_transport_t *transport = acip_tcp_transport_create(name, sockfd, crypto_ctx);
386 if (transport)
387 ((tcp_transport_data_t *)transport->impl_data)->owns_socket = true;
388 return transport;
389}

References acip_tcp_transport_create(), and acip_transport::impl_data.

◆ acip_tcp_transport_set_zerocopy()

asciichat_error_t acip_tcp_transport_set_zerocopy ( acip_transport_t * transport,
bool enabled )

Override the automatic kernel copy-avoidance policy for this TCP transport. Large Linux encrypted sends may pin pages; other platforms fall back to vectors. Copy avoidance is attempted automatically and disabled after copied completions.

Definition at line 112 of file tcp/transport.c.

112 {
113 if (!transport || acip_transport_get_type(transport) != ACIP_TRANSPORT_TCP)
114 return SET_ERRNO(ERROR_INVALID_PARAM, "Expected TCP transport");
115 tcp_transport_data_t *tcp = transport->impl_data;
116 mutex_lock(&tcp->send_mutex);
118 mutex_unlock(&tcp->send_mutex);
119 return ASCIICHAT_OK;
120}
bool enabled
Is filtering active?
Definition grep.c:62

References ACIP_TRANSPORT_TCP, ASCIICHAT_OK, enabled, ERROR_INVALID_PARAM, acip_transport::impl_data, tcp_transport_data_t::send_mutex, SET_ERRNO, and tcp_transport_data_t::zerocopy_disabled.

◆ acip_transport_destroy()

void acip_transport_destroy ( acip_transport_t * transport)

Destroy transport and free all resources.

Parameters
transportTransport to destroy (may be NULL)
Note
Calls close() first if still connected
Frees the transport structure itself
Safe to pass NULL

Definition at line 395 of file tcp/transport.c.

395 {
396 if (!transport) {
397 log_debug("[TRANSPORT_DESTROY] ⚠️ NULL_TRANSPORT: nothing to destroy");
398 return;
399 }
400
401 log_debug("[TRANSPORT_DESTROY] 🔴 DESTROY_START: transport=%p, impl_data=%p", (void *)transport,
402 transport->impl_data);
403
404#ifndef __EMSCRIPTEN__
405 acip_outbound_destroy(transport);
406#endif
407
408 // Get type before we destroy for logging
409 acip_transport_type_t type = 0;
410 if (transport->methods && transport->methods->get_type) {
411 type = transport->methods->get_type(transport);
412 log_debug("[TRANSPORT_DESTROY] 📋 TRANSPORT_TYPE: type=%d (1=TCP, 2=WebSocket, 3=WebRTC, ...)", type);
413 }
414
415 // Close if still connected
416 if (transport->methods && transport->methods->close && transport->methods->is_connected &&
417 transport->methods->is_connected(transport)) {
418 log_debug("[TRANSPORT_DESTROY] 🔴 STILL_CONNECTED: calling close() first (type=%d)", type);
419 asciichat_error_t close_result = transport->methods->close(transport);
420 if (close_result != ASCIICHAT_OK)
421 log_warn("Transport close during cleanup failed: %d", close_result);
422 log_debug("[TRANSPORT_DESTROY] 🔴 CLOSE_CALLED: result=%d", close_result != ASCIICHAT_OK ? -1 : 0);
423 } else {
424 log_debug("[TRANSPORT_DESTROY] ✅ ALREADY_CLOSED: skipping close (type=%d)", type);
425 }
426
427 // Call custom destroy implementation if provided
428 if (transport->methods && transport->methods->destroy_impl) {
429 log_debug("[TRANSPORT_DESTROY] 🔧 CALLING_DESTROY_IMPL: type=%d", type);
430 transport->methods->destroy_impl(transport);
431 log_debug("[TRANSPORT_DESTROY] ✅ DESTROY_IMPL_COMPLETE: type=%d", type);
432 } else {
433 log_debug("[TRANSPORT_DESTROY] ⏭️ NO_CUSTOM_DESTROY: type=%d (using generic cleanup)", type);
434 }
435
436 // Free implementation data
437 if (transport->impl_data) {
438 log_debug("[TRANSPORT_DESTROY] 🗑️ FREEING_IMPL_DATA: %p", transport->impl_data);
439 NAMED_UNREGISTER(transport->impl_data);
440 SAFE_FREE(transport->impl_data);
441 log_debug("[TRANSPORT_DESTROY] ✅ IMPL_DATA_FREED");
442 }
443
445 transport->stats_peer = NULL;
446
447 // Free transport structure
448 log_debug("[TRANSPORT_DESTROY] 🗑️ FREEING_TRANSPORT: %p", (void *)transport);
449 SAFE_FREE(transport);
450
451 log_debug("[TRANSPORT_DESTROY] ✅ DESTROY_COMPLETE: type=%d", type);
452}
void acip_outbound_destroy(acip_transport_t *transport)
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
void stats_runtime_peer_close(stats_peer_t *peer)
Definition runtime.c:181
bool(* is_connected)(acip_transport_t *transport)
Check if transport is connected.
asciichat_error_t(* close)(acip_transport_t *transport)
Close this transport.
void(* destroy_impl)(acip_transport_t *transport)
acip_transport_type_t(* get_type)(acip_transport_t *transport)
Get transport type.

References acip_outbound_destroy(), ASCIICHAT_OK, acip_transport_methods_t::close, acip_transport_methods_t::destroy_impl, acip_transport_methods_t::get_type, acip_transport::impl_data, acip_transport_methods_t::is_connected, log_debug, log_warn, acip_transport::methods, NAMED_UNREGISTER, SAFE_FREE, acip_transport::stats_peer, and stats_runtime_peer_close().

Referenced by acip_async_tcp_accepted_create(), acip_async_tcp_client_create(), acip_async_tcp_transport_create(), acip_tcp_transport_create(), acip_websocket_client_transport_create(), acip_websocket_server_transport_create(), network_connection_open(), and quic_release().

◆ acip_transport_get_capabilities()

asciichat_error_t acip_transport_get_capabilities ( acip_transport_t * transport,
acip_transport_capabilities_t * out )

A backend with no capability method reports no optional capabilities. Negotiated backends can return ERROR_INVALID_STATE until negotiation finishes.

Definition at line 104 of file send.c.

104 {
105 if (!out)
106 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing transport capabilities output");
108 if (!transport || !transport->methods)
109 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid transport capabilities query");
110 return transport->methods->get_capabilities ? transport->methods->get_capabilities(transport, out) : ASCIICHAT_OK;
111}
static bool size_t gotel_query_t * out
Definition queries.h:8
The useful part of acip transport capabilities is in independent_streams, datagrams,...
asciichat_error_t(* get_capabilities)(acip_transport_t *transport, acip_transport_capabilities_t *out)

References ASCIICHAT_OK, ERROR_INVALID_PARAM, acip_transport_methods_t::get_capabilities, acip_transport::methods, out, and SET_ERRNO.

Referenced by packet_send_via_transportv().

◆ acip_transport_send()

asciichat_error_t acip_transport_send ( acip_transport_t * transport,
const void * data,
size_t len )

Definition at line 150 of file send.c.

150 {
151 socket_buffer_t buffer = {data, len};
152 return acip_transport_sendv(transport, &buffer, 1);
153}
asciichat_error_t acip_transport_sendv(acip_transport_t *transport, const socket_buffer_t *buffers, size_t count)
Send data through transport.
Definition send.c:140

References acip_transport_sendv().

◆ acip_transport_sendv()

asciichat_error_t acip_transport_sendv ( acip_transport_t * transport,
const socket_buffer_t * buffers,
size_t count )

Send data through transport.

Convenience wrapper around transport->methods->send().

Parameters
transportTransport instance
dataData to send
lenData length
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 140 of file send.c.

140 {
141 acip_send_options_t options = {.traffic_class = ACIP_TRAFFIC_CONTROL, .delivery = ACIP_DELIVERY_RELIABLE};
142 if (buffers && count && buffers[0].data && buffers[0].len >= sizeof(packet_header_t)) {
143 packet_header_t header;
144 memcpy(&header, buffers[0].data, sizeof(header));
146 }
147 return acip_transport_sendv_ex(transport, buffers, count, &options);
148}
#define NET_TO_HOST_U16(val)
Definition endian.h:111
asciichat_error_t acip_transport_sendv_ex(acip_transport_t *transport, const socket_buffer_t *buffers, size_t count, const acip_send_options_t *options)
Definition send.c:113
acip_traffic_class_t traffic_class
Definition traffic.h:17
Fixed prefix shared by every ACIP packet on the wire.
Definition packet.h:628
acip_traffic_class_t acip_packet_traffic_class(uint16_t type)
Definition traffic.c:4
@ ACIP_TRAFFIC_CONTROL
Definition traffic.h:6
@ ACIP_DELIVERY_RELIABLE
Definition traffic.h:12

References ACIP_DELIVERY_RELIABLE, acip_packet_traffic_class(), ACIP_TRAFFIC_CONTROL, acip_transport_sendv_ex(), NET_TO_HOST_U16, and acip_send_options_t::traffic_class.

Referenced by acip_transport_send().

◆ acip_transport_sendv_ex()

asciichat_error_t acip_transport_sendv_ex ( acip_transport_t * transport,
const socket_buffer_t * buffers,
size_t count,
const acip_send_options_t * options )

Send with explicit delivery metadata. Unsupported datagrams or deadlines return ERROR_NOT_SUPPORTED and are never silently changed into reliable delivery. A deadline uses time_get_ns()'s monotonic clock.

Definition at line 113 of file send.c.

114 {
115 if (!transport || !transport->methods || !buffers || !count || count > SOCKET_IOV_MAX || !options ||
116 (unsigned)options->traffic_class > ACIP_TRAFFIC_VISUAL ||
118 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid transport send options");
119 if (options->delivery != ACIP_DELIVERY_RELIABLE || options->deadline_ns) {
120 if (!transport->methods->sendv_ex || !transport->methods->validate_send_options)
121 return SET_ERRNO(ERROR_NOT_SUPPORTED, "Datagrams and deadlines are not supported by this transport");
122 asciichat_error_t result = transport->methods->validate_send_options(transport, options);
123 if (result != ASCIICHAT_OK)
124 return result;
125 }
126#ifndef __EMSCRIPTEN__
127 if (transport && transport->outbound) {
128 acip_admission_t admission;
129 asciichat_error_t result = acip_outbound_submitv_ex(transport, buffers, count, options, &admission);
130 if (result != ASCIICHAT_OK)
131 return result;
132 if (admission != ACIP_ADMISSION_ACCEPTED)
133 return SET_ERRNO(ERROR_BUFFER_FULL, "Outbound media not admitted (%d)", admission);
134 return ASCIICHAT_OK;
135 }
136#endif
137 return transport_sendv_impl(transport, buffers, count, options);
138}
asciichat_error_t acip_outbound_submitv_ex(acip_transport_t *transport, const socket_buffer_t *buffers, size_t count, const acip_send_options_t *options, acip_admission_t *admission)
acip_admission_t
@ ACIP_ADMISSION_ACCEPTED
@ ERROR_BUFFER_FULL
@ ERROR_NOT_SUPPORTED
Definition error_codes.h:69
uint64_t deadline_ns
Definition traffic.h:19
acip_delivery_t delivery
Definition traffic.h:18
asciichat_error_t(* validate_send_options)(acip_transport_t *transport, const acip_send_options_t *options)
asciichat_error_t(* sendv_ex)(acip_transport_t *transport, const socket_buffer_t *buffers, size_t count, const acip_send_options_t *options)
struct acip_outbound * outbound
Per-connection plaintext admission and scheduling.
@ ACIP_TRAFFIC_VISUAL
Definition traffic.h:8
@ ACIP_DELIVERY_DATAGRAM
Definition traffic.h:13

References ACIP_ADMISSION_ACCEPTED, ACIP_DELIVERY_DATAGRAM, ACIP_DELIVERY_RELIABLE, acip_outbound_submitv_ex(), ACIP_TRAFFIC_VISUAL, ASCIICHAT_OK, acip_send_options_t::deadline_ns, acip_send_options_t::delivery, ERROR_BUFFER_FULL, ERROR_INVALID_PARAM, ERROR_NOT_SUPPORTED, acip_transport::methods, acip_transport::outbound, acip_transport_methods_t::sendv_ex, SET_ERRNO, acip_send_options_t::traffic_class, and acip_transport_methods_t::validate_send_options.

Referenced by acip_transport_sendv(), and packet_send_via_transportv().

◆ acip_webrtc_transport_create()

acip_transport_t * acip_webrtc_transport_create ( struct webrtc_peer_connection * peer_conn,
struct webrtc_data_channel * data_channel,
crypto_context_t * crypto_ctx )

Definition at line 913 of file webrtc/transport.c.

914 {
915 return webrtc_transport_create(peer_conn, data_channel, crypto_ctx, NULL, NULL, NULL);
916}

◆ acip_websocket_client_transport_create()

acip_transport_t * acip_websocket_client_transport_create ( const char * name,
const char * url,
crypto_context_t * crypto_ctx )

Create WebSocket client transport.

Parameters
urlWebSocket URL (e.g., "ws://localhost:27225")
crypto_ctxOptional encryption context (can be NULL)
Returns
Transport instance or NULL on failure

Definition at line 1537 of file websocket/transport.c.

1538 {
1539 if (!name) {
1540 SET_ERRNO(ERROR_INVALID_STATE, "Transport name is required");
1541 return NULL;
1542 }
1543
1544 if (!url) {
1545 SET_ERRNO(ERROR_INVALID_PARAM, "url is required");
1546 return NULL;
1547 }
1548
1549 websocket_endpoint_t endpoint;
1550 if (websocket_parse_endpoint(url, &endpoint) != ASCIICHAT_OK) {
1551 return NULL;
1552 }
1553 const char *host = endpoint.host;
1554 const char *path = endpoint.path;
1555 int port = endpoint.port;
1556 bool use_ssl = endpoint.use_ssl;
1557
1558 acip_transport_t *transport = websocket_allocate_client_transport(name);
1559 if (!transport) {
1560 return NULL;
1561 }
1563
1564 ws_data->context = websocket_create_client_context();
1565
1566 if (!ws_data->context) {
1567 cond_destroy(&ws_data->state_cond);
1568 mutex_destroy(&ws_data->state_mutex);
1569 cond_destroy(&ws_data->recv_cond);
1570 mutex_destroy(&ws_data->recv_mutex);
1571 ringbuffer_destroy(ws_data->send_queue);
1572 ringbuffer_destroy(ws_data->recv_queue);
1573 SAFE_FREE(ws_data);
1574 SAFE_FREE(transport);
1575 SET_ERRNO(ERROR_NETWORK, "Failed to create libwebsockets context");
1576 return NULL;
1577 }
1578
1579 // Initialize connection state flags BEFORE calling lws_client_connect_via_info
1580 // The callback might fire immediately, so all fields must be ready
1581 ws_data->is_connected = false; // Will be set to true in LWS_CALLBACK_CLIENT_ESTABLISHED
1582 ws_data->connection_failed = false; // Set to true in LWS_CALLBACK_CLIENT_CONNECTION_ERROR
1583 ws_data->owns_context = true; // Client transport owns the context
1584
1585 // Connect to WebSocket server
1586 log_debug("Initiating WebSocket connection to %s:%d%s", host, port, path);
1587 struct lws_client_connect_info connect_info;
1588 memset(&connect_info, 0, sizeof(connect_info));
1589 connect_info.context = ws_data->context;
1590 connect_info.address = host;
1591 connect_info.port = port;
1592 connect_info.path = path;
1593 connect_info.host = host;
1594 connect_info.origin = host;
1595 // Request the ACIP subprotocol so server routes to acip_protocol handler
1596 // This ensures WebSocket upgrade includes Sec-WebSocket-Protocol: acip
1597 connect_info.local_protocol_name = "acip";
1598 connect_info.protocol = "acip";
1599 // Use SSL + skip server certificate hostname verification + allow self-signed certs (for development)
1600 connect_info.ssl_connection =
1601 use_ssl ? (LCCSCF_USE_SSL | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_SELFSIGNED) : 0;
1602 connect_info.opaque_user_data = ws_data;
1603
1604 log_debug("Calling lws_client_connect_via_info...");
1605 ws_data->wsi = lws_client_connect_via_info(&connect_info);
1606 log_debug("lws_client_connect_via_info returned: %p", (void *)ws_data->wsi);
1607 if (!ws_data->wsi) {
1608 lws_context_destroy(ws_data->context);
1609 cond_destroy(&ws_data->state_cond);
1610 mutex_destroy(&ws_data->state_mutex);
1611 cond_destroy(&ws_data->recv_cond);
1612 mutex_destroy(&ws_data->recv_mutex);
1613 ringbuffer_destroy(ws_data->send_queue);
1614 ringbuffer_destroy(ws_data->recv_queue);
1615 SAFE_FREE(ws_data);
1616 SAFE_FREE(transport);
1617 SET_ERRNO(ERROR_NETWORK, "Failed to connect to WebSocket server");
1618 return NULL;
1619 }
1620
1621 // Initialize transport
1622 transport->methods = &websocket_methods;
1623 transport->stats_peer = stats_runtime_peer_open("WebSocket");
1624 transport->crypto_ctx = crypto_ctx;
1625 transport->impl_data = ws_data;
1626
1627 // Start service thread immediately - it will drive TLS handshake with frequent polling
1628 // The service thread checks is_destroying and context validity before calling lws_service,
1629 // so it's safe to start it right away without delays.
1630 ws_data->service_running = true;
1631 if (platform_thread_create(&ws_data->service_thread, "ws_service", websocket_service_thread, ws_data) != 0) {
1632 log_error("Failed to create WebSocket service thread");
1633 ws_data->service_running = false;
1634 lws_context_destroy(ws_data->context);
1635 cond_destroy(&ws_data->state_cond);
1636 mutex_destroy(&ws_data->state_mutex);
1637 cond_destroy(&ws_data->recv_cond);
1638 mutex_destroy(&ws_data->recv_mutex);
1639 ringbuffer_destroy(ws_data->send_queue);
1640 ringbuffer_destroy(ws_data->recv_queue);
1641 SAFE_FREE(ws_data);
1643 SAFE_FREE(transport);
1644 SET_ERRNO(ERROR_INTERNAL, "Failed to create service thread");
1645 return NULL;
1646 }
1647 log_debug("WebSocket service thread started");
1648
1649 // Do NOT block the main thread waiting for connection!
1650 // This prevents stdin/keyboard input from being processed and causes the client to hang.
1651 // The service thread will establish the connection asynchronously.
1652 // The protocol layer (recv) will detect if connection fails and handle it there.
1653 // Return immediately so the main thread can respond to keyboard input.
1654 log_debug("WebSocket transport created, service thread will establish connection asynchronously");
1655
1656 // Register transport
1657 NAMED_REGISTER_TRANSPORT(transport, name, NULL);
1658
1659 // Register websocket implementation data with transport as parent
1660 (void)NAMED_REGISTER_WEBSOCKET_IMPL(ws_data, "impl", (uintptr_t)(const void *)(transport));
1661
1662 // Return transport immediately - connection will be established by service thread
1663 // If connection fails, recv() will detect is_connected=false and return error
1664 if (transport->methods && acip_outbound_create(transport) != ASCIICHAT_OK) {
1665 acip_transport_destroy(transport);
1666 return NULL;
1667 }
1668 return transport;
1669}
#define NAMED_REGISTER_WEBSOCKET_IMPL(data, name, parent_ptr)
@ ERROR_INTERNAL
Definition error_codes.h:87
void ringbuffer_destroy(ringbuffer_t *rb)
Destroy a ring buffer and free its memory.
Definition ringbuffer.c:61
char host[HOSTNAME_BUFFER_SIZE]
char path[URL_BUFFER_SIZE]
Private state for one libwebsockets-backed ACIP transport.

References acip_outbound_create(), acip_transport_destroy(), ASCIICHAT_OK, websocket_transport_data_t::connection_failed, websocket_transport_data_t::context, acip_transport::crypto_ctx, ERROR_INTERNAL, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_NETWORK, websocket_endpoint_t::host, acip_transport::impl_data, websocket_transport_data_t::is_connected, log_debug, log_error, acip_transport::methods, NAMED_REGISTER_TRANSPORT, NAMED_REGISTER_WEBSOCKET_IMPL, websocket_transport_data_t::owns_context, websocket_endpoint_t::path, websocket_endpoint_t::port, websocket_transport_data_t::recv_cond, websocket_transport_data_t::recv_mutex, websocket_transport_data_t::recv_queue, ringbuffer_destroy(), SAFE_FREE, websocket_transport_data_t::send_queue, websocket_transport_data_t::service_running, websocket_transport_data_t::service_thread, SET_ERRNO, websocket_transport_data_t::state_cond, websocket_transport_data_t::state_mutex, acip_transport::stats_peer, stats_runtime_peer_close(), stats_runtime_peer_open(), websocket_endpoint_t::use_ssl, and websocket_transport_data_t::wsi.

Referenced by network_connection_open().

◆ acip_websocket_runtime_create()

asciichat_error_t acip_websocket_runtime_create ( acip_websocket_runtime_t ** out)

Definition at line 1416 of file websocket/transport.c.

1416 {
1417 if (!out || *out)
1418 return SET_ERRNO(ERROR_INVALID_PARAM, "WebSocket runtime output must be empty");
1419 acip_websocket_runtime_t *runtime = SAFE_CALLOC(1, sizeof(*runtime), acip_websocket_runtime_t *);
1420 if (!runtime)
1421 return SET_ERRNO(ERROR_MEMORY, "Could not allocate WebSocket runtime");
1422 // libwebsockets 4.5 calls OPENSSL_cleanup when its last TLS context goes away.
1423 // An application reference prevents connection teardown from ending TLS lifetime.
1424 runtime->context = websocket_create_client_context();
1425 if (!runtime->context) {
1426 SAFE_FREE(runtime);
1427 return SET_ERRNO(ERROR_NETWORK, "Could not initialize WebSocket runtime");
1428 }
1429 *out = runtime;
1430 return ASCIICHAT_OK;
1431}
struct acip_websocket_runtime acip_websocket_runtime_t
struct lws_context * context

References ASCIICHAT_OK, acip_websocket_runtime::context, ERROR_INVALID_PARAM, ERROR_MEMORY, ERROR_NETWORK, out, SAFE_CALLOC, SAFE_FREE, and SET_ERRNO.

Referenced by discovery_main().

◆ acip_websocket_runtime_destroy()

void acip_websocket_runtime_destroy ( acip_websocket_runtime_t ** runtime)

Definition at line 1433 of file websocket/transport.c.

1433 {
1434 if (!runtime || !*runtime)
1435 return;
1436 lws_context_destroy((*runtime)->context);
1437 SAFE_FREE(*runtime);
1438 *runtime = NULL;
1439}

References SAFE_FREE.

Referenced by discovery_main().

◆ acip_websocket_server_transport_create()

acip_transport_t * acip_websocket_server_transport_create ( const char * name,
struct lws * wsi,
crypto_context_t * crypto_ctx )

Create WebSocket server transport from existing connection.

Wraps an already-established libwebsockets connection (from server accept). Used by websocket_server module to create transports for incoming clients.

Parameters
wsiEstablished libwebsockets connection (not owned by transport)
crypto_ctxOptional crypto context
Returns
Transport instance or NULL on error

Definition at line 1773 of file websocket/transport.c.

1774 {
1775 if (!name) {
1776 SET_ERRNO(ERROR_INVALID_STATE, "Transport name is required");
1777 return NULL;
1778 }
1779
1780 if (!wsi) {
1781 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid wsi parameter");
1782 return NULL;
1783 }
1784
1785 acip_transport_t *transport = websocket_allocate_server_transport(name);
1786 if (!transport) {
1787 return NULL;
1788 }
1790
1791 // Store connection info (server-side: no context ownership, connection already established)
1792 ws_data->wsi = wsi;
1793
1794 ws_data->context = lws_get_context(wsi); // Get context from wsi (not owned)
1795 ws_data->owns_context = false; // Server owns context, not transport
1796 ws_data->is_connected = true; // Already connected (server-side)
1797 log_debug_every(US_PER_SEC_INT, "★★★ SERVER TRANSPORT CREATED: is_connected=true, wsi=%p, ws_data=%p", (void *)wsi,
1798 (void *)ws_data);
1799 log_debug("Server transport created: is_connected=true, wsi=%p", (void *)wsi);
1800
1801 // Initialize transport
1802 transport->methods = &websocket_methods;
1803 transport->stats_peer = stats_runtime_peer_open("WebSocket");
1804 transport->crypto_ctx = crypto_ctx;
1805 transport->impl_data = ws_data;
1806
1807 log_debug_every(US_PER_SEC_INT, "Created WebSocket server transport (crypto: %s)",
1808 crypto_ctx ? "enabled" : "disabled");
1809
1810 // Register transport
1811 NAMED_REGISTER_TRANSPORT(transport, name, NULL);
1812
1813 // Register websocket implementation data with transport as parent
1814 (void)NAMED_REGISTER_WEBSOCKET_IMPL(ws_data, "impl", (uintptr_t)(const void *)(transport));
1815
1816 int send_buffer = SOCKET_STREAM_SEND_BUFFER;
1817 if (socket_setsockopt(lws_get_socket_fd(wsi), SOL_SOCKET, SO_SNDBUF, &send_buffer, sizeof(send_buffer)) != 0) {
1818 acip_transport_destroy(transport);
1819 SET_ERRNO_SYS(ERROR_NETWORK, "Cannot bound WebSocket send buffer");
1820 return NULL;
1821 }
1822
1823 if (transport->methods && acip_outbound_create(transport) != ASCIICHAT_OK) {
1824 acip_transport_destroy(transport);
1825 return NULL;
1826 }
1827 return transport;
1828}
#define US_PER_SEC_INT
Definition time.h:173
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:749

References acip_outbound_create(), acip_transport_destroy(), ASCIICHAT_OK, websocket_transport_data_t::context, acip_transport::crypto_ctx, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_NETWORK, acip_transport::impl_data, websocket_transport_data_t::is_connected, log_debug, log_debug_every, acip_transport::methods, NAMED_REGISTER_TRANSPORT, NAMED_REGISTER_WEBSOCKET_IMPL, websocket_transport_data_t::owns_context, SET_ERRNO, SET_ERRNO_SYS, acip_transport::stats_peer, stats_runtime_peer_open(), US_PER_SEC_INT, and websocket_transport_data_t::wsi.

◆ acip_websocket_transport_flush()

asciichat_error_t acip_websocket_transport_flush ( acip_transport_t * transport,
uint64_t timeout_ns )

Create WebSocket client transport.

Connects to a WebSocket server at the specified URL and creates a transport for sending/receiving ACIP packets over the WebSocket connection.

Parameters
nameDebug name for tracking this transport (required, e.g., "transport_websocket_client")
urlWebSocket URL (e.g., "ws://localhost:27225" or "wss://example.com/path")
crypto_ctxOptional encryption context (can be NULL)
Returns
Transport instance or NULL on failure
Note
The URL must include the protocol (ws:// or wss://)
Port defaults to 80 for ws:// and 443 for wss:// if not specified
Connection is established synchronously during creation Wait for queued messages to reach their writable completion callback. Call from a worker, never from the libwebsockets service thread.

Definition at line 1087 of file websocket/transport.c.

1087 {
1088 if (!transport || acip_transport_get_type(transport) != ACIP_TRANSPORT_WEBSOCKET)
1089 return SET_ERRNO(ERROR_INVALID_PARAM, "Expected WebSocket transport");
1090 websocket_transport_data_t *ws_data = transport->impl_data;
1091 uint64_t started = time_get_ns();
1092 while (acip_transport_is_connected(transport)) {
1093 mutex_lock(&ws_data->send_mutex);
1094 bool drained = ringbuffer_is_empty(ws_data->send_queue) && !ws_data->inflight_buffer;
1095 mutex_unlock(&ws_data->send_mutex);
1096 if (drained)
1097 return ASCIICHAT_OK;
1098 if (time_get_ns() - started >= timeout_ns)
1099 return SET_ERRNO(ERROR_NETWORK_TIMEOUT, "WebSocket send queue did not drain");
1100 platform_sleep_us(1000);
1101 }
1102 return SET_ERRNO(ERROR_NETWORK, "WebSocket closed before send queue drained");
1103}
unsigned long long uint64_t
Definition common.h:63
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:75
bool ringbuffer_is_empty(const ringbuffer_t *rb)
Check if buffer is empty.
Definition ringbuffer.c:136
mutex_t send_mutex
Protect send_queue operations only.
uint8_t * inflight_buffer
Owned until the next writable callback or teardown.
ringbuffer_t * send_queue
Send message queue (for server-side transports).

References ACIP_TRANSPORT_WEBSOCKET, ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_NETWORK, ERROR_NETWORK_TIMEOUT, acip_transport::impl_data, websocket_transport_data_t::inflight_buffer, ringbuffer_is_empty(), websocket_transport_data_t::send_mutex, websocket_transport_data_t::send_queue, and SET_ERRNO.