ascii-chat 0.11.33
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_methods_t
 Transport method table (virtual function table) More...
 
struct  acip_transport
 Transport instance structure. More...
 

Typedefs

typedef struct acip_transport acip_transport_t
 Forward declaration of transport implementation.
 

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 type enumeration. More...
 

Functions

void acip_transport_destroy (acip_transport_t *transport)
 Destroy transport and free all resources.
 
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_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 (future): Long-polling, REST-style
  • QUIC (future): UDP with congestion control

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
  • Release Date: January 2026

DESIGN GOALS:

  1. Protocol code never calls socket()/send()/recv() directly
  2. Same ACIP handlers work with TCP, WebSocket, or any future transport
  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:49
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, const char *frame_data, size_t frame_size, uint32_t width, uint32_t height, const char *client_id)
Send ASCII frame to client (server → client)
Transport instance structure.
Definition transport.h:214
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 transport.h.

Typedef Documentation

◆ acip_transport_t

Forward declaration of transport implementation.

Opaque handle to transport-specific state. Each transport implementation defines its own state structure.

Definition at line 107 of file transport.h.

Enumeration Type Documentation

◆ acip_transport_type_t

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 93 of file transport.h.

93 {
acip_transport_type_t
Transport type enumeration.
Definition transport.h:93
@ ACIP_TRANSPORT_TCP
Raw TCP socket.
Definition transport.h:94
@ ACIP_TRANSPORT_WEBSOCKET
WebSocket over TCP.
Definition transport.h:95
@ ACIP_TRANSPORT_WEBRTC
WebRTC DataChannel (P2P)
Definition transport.h:96
@ ACIP_TRANSPORT_HTTP
HTTP long-polling (future)
Definition transport.h:97
@ ACIP_TRANSPORT_QUIC
QUIC/UDP (future)
Definition transport.h:98

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 398 of file tcp/transport.c.

398 {
399 if (!name) {
400 SET_ERRNO(ERROR_INVALID_STATE, "Transport name is required");
401 return NULL;
402 }
403
404 if (sockfd == INVALID_SOCKET_VALUE) {
405 log_error("[TCP_CREATE_STATE] ❌ INVALID_SOCKET: sockfd=%d", sockfd);
406 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket descriptor");
407 return NULL;
408 }
409
410 log_info("[TCP_CREATE_STATE] 🟢 CREATE_START: sockfd=%d, crypto=%s", sockfd, crypto_ctx ? "yes" : "no");
411
412 // Allocate transport structure
414 if (!transport) {
415 log_error("[TCP_CREATE_STATE] ❌ ALLOC_TRANSPORT_FAILED: size=%zu", sizeof(acip_transport_t));
416 SET_ERRNO(ERROR_MEMORY, "Failed to allocate TCP transport");
417 return NULL;
418 }
419
420 log_debug("[TCP_CREATE_STATE] 🟢 TRANSPORT_ALLOCATED: transport=%p", (void *)transport);
421
422 // Allocate TCP-specific data
424 if (!tcp_data) {
425 log_error("[TCP_CREATE_STATE] ❌ ALLOC_TCP_DATA_FAILED: size=%zu", sizeof(tcp_transport_data_t));
426 SAFE_FREE(transport);
427 SET_ERRNO(ERROR_MEMORY, "Failed to allocate TCP transport data");
428 return NULL;
429 }
430
431 log_debug("[TCP_CREATE_STATE] 🟢 TCP_DATA_ALLOCATED: tcp_data=%p", (void *)tcp_data);
432
433 // Initialize TCP data
434 tcp_data->sockfd = sockfd;
435 tcp_data->is_connected = true;
436
437 // Initialize send mutex to protect concurrent sends from multiple threads
438 int mutex_result = mutex_init(&tcp_data->send_mutex, "tcp_send");
439 if (mutex_result != 0) {
440 log_error("[TCP_CREATE_STATE] ❌ MUTEX_INIT_FAILED: result=%d", mutex_result);
441 SAFE_FREE(tcp_data);
442 SAFE_FREE(transport);
443 SET_ERRNO(ERROR_MEMORY, "Failed to initialize send mutex");
444 return NULL;
445 }
446
447 log_info("[TCP_CREATE_STATE] 🟢 TCP_DATA_INITIALIZED: sockfd=%d, is_connected=true, send_mutex=initialized", sockfd);
448
449 // Enable TCP_NODELAY to disable Nagle's algorithm
450 // This ensures small packets are sent immediately instead of being buffered
451 int nodelay = 1;
452 if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (const char *)&nodelay, sizeof(nodelay)) < 0) {
453 log_warn("[TCP_CREATE_STATE] ⚠️ TCP_NODELAY_FAILED: sockfd=%d, %s", sockfd, SAFE_STRERROR(errno));
454 // Continue anyway - this is not fatal
455 } else {
456 log_debug("[TCP_CREATE_STATE] 🟢 TCP_NODELAY_SET: sockfd=%d", sockfd);
457 }
458
459 // Initialize transport
460 transport->methods = &tcp_methods;
461 transport->crypto_ctx = crypto_ctx;
462 transport->impl_data = tcp_data;
463
464 // Register transport and impl_data before logging so named replacements work
465 NAMED_REGISTER_TRANSPORT(transport, name, NULL);
466 NAMED_REGISTER(tcp_data, "impl", "tcp_impl", "0x%tx", (uintptr_t)(const void *)(transport));
467
468 log_info("[TCP_CREATE_STATE] ✅ CREATE_COMPLETE: transport=%p, sockfd=%d, is_connected=true, crypto=%s",
469 (void *)transport, sockfd, crypto_ctx ? "enabled" : "disabled");
470
471 return transport;
472}
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_STRERROR(errnum)
Definition common.h:465
#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)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:56
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
int errno
void * impl_data
Transport-specific state.
Definition transport.h:218
const acip_transport_methods_t * methods
Method table (virtual functions)
Definition transport.h:215
crypto_context_t * crypto_ctx
Optional encryption context.
Definition transport.h:216
TCP transport implementation data.
socket_t sockfd
Socket descriptor (NOT owned - don't close)
bool is_connected
Connection state.
mutex_t send_mutex
Mutex to protect concurrent sends (multiple threads may send packets)

References errno, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, INVALID_SOCKET_VALUE, tcp_transport_data_t::is_connected, log_debug, log_error, log_info, log_warn, mutex_init(), NAMED_REGISTER, NAMED_REGISTER_TRANSPORT, SAFE_FREE, SAFE_MALLOC, SAFE_STRERROR, tcp_transport_data_t::send_mutex, SET_ERRNO, and tcp_transport_data_t::sockfd.

Referenced by acds_client_connect(), acds_client_handler(), add_client(), connection_attempt_tcp(), and server_connection_establish().

◆ 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 478 of file tcp/transport.c.

478 {
479 if (!transport) {
480 log_debug("[TRANSPORT_DESTROY] ⚠️ NULL_TRANSPORT: nothing to destroy");
481 return;
482 }
483
484 log_warn("[TRANSPORT_DESTROY] 🔴 DESTROY_START: transport=%p, impl_data=%p", (void *)transport, transport->impl_data);
485
486 // Get type before we destroy for logging
487 acip_transport_type_t type = 0;
488 if (transport->methods && transport->methods->get_type) {
489 type = transport->methods->get_type(transport);
490 log_info("[TRANSPORT_DESTROY] 📋 TRANSPORT_TYPE: type=%d (1=TCP, 2=WebSocket, 3=WebRTC, ...)", type);
491 }
492
493 // Close if still connected
494 if (transport->methods && transport->methods->close && transport->methods->is_connected &&
495 transport->methods->is_connected(transport)) {
496 log_warn("[TRANSPORT_DESTROY] 🔴 STILL_CONNECTED: calling close() first (type=%d)", type);
497 asciichat_error_t close_result = transport->methods->close(transport);
498 log_info("[TRANSPORT_DESTROY] 🔴 CLOSE_CALLED: result=%d", close_result != ASCIICHAT_OK ? -1 : 0);
499 } else {
500 log_debug("[TRANSPORT_DESTROY] ✅ ALREADY_CLOSED: skipping close (type=%d)", type);
501 }
502
503 // Call custom destroy implementation if provided
504 if (transport->methods && transport->methods->destroy_impl) {
505 log_info("[TRANSPORT_DESTROY] 🔧 CALLING_DESTROY_IMPL: type=%d", type);
506 transport->methods->destroy_impl(transport);
507 log_info("[TRANSPORT_DESTROY] ✅ DESTROY_IMPL_COMPLETE: type=%d", type);
508 } else {
509 log_debug("[TRANSPORT_DESTROY] ⏭️ NO_CUSTOM_DESTROY: type=%d (using generic cleanup)", type);
510 }
511
512 // Free implementation data
513 if (transport->impl_data) {
514 log_debug("[TRANSPORT_DESTROY] 🗑️ FREEING_IMPL_DATA: %p", transport->impl_data);
515 NAMED_UNREGISTER(transport->impl_data);
516 SAFE_FREE(transport->impl_data);
517 log_debug("[TRANSPORT_DESTROY] ✅ IMPL_DATA_FREED");
518 }
519
520 // Free transport structure
521 log_debug("[TRANSPORT_DESTROY] 🗑️ FREEING_TRANSPORT: %p", (void *)transport);
522 SAFE_FREE(transport);
523
524 log_warn("[TRANSPORT_DESTROY] ✅ DESTROY_COMPLETE: type=%d", type);
525}
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
@ ASCIICHAT_OK
Definition error_codes.h:51
bool(* is_connected)(acip_transport_t *transport)
Check if transport is connected.
Definition transport.h:183
asciichat_error_t(* close)(acip_transport_t *transport)
Close this transport.
Definition transport.h:156
void(* destroy_impl)(acip_transport_t *transport)
Custom destroy implementation (optional)
Definition transport.h:205
acip_transport_type_t(* get_type)(acip_transport_t *transport)
Get transport type.
Definition transport.h:164

References 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_info, log_warn, acip_transport::methods, NAMED_UNREGISTER, and SAFE_FREE.

Referenced by acds_client_connect(), acds_client_disconnect(), acds_client_handler(), connection_attempt_tcp(), connection_handle_cleanup(), discovery_session_destroy(), discovery_session_process(), remove_client(), server_connection_close(), server_connection_establish(), server_connection_set_transport(), and session_participant_destroy().

◆ 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 614 of file webrtc/transport.c.

615 {
616 if (!peer_conn || !data_channel) {
617 SET_ERRNO(ERROR_INVALID_PARAM, "peer_conn and data_channel are required");
618 return NULL;
619 }
620
621 // Allocate transport structure
623 if (!transport) {
624 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebRTC transport");
625 return NULL;
626 }
627
628 // Allocate WebRTC-specific data
630 if (!wrtc_data) {
631 SAFE_FREE(transport);
632 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebRTC transport data");
633 return NULL;
634 }
635
636 // Create receive queue
638 if (!wrtc_data->recv_queue) {
639 SAFE_FREE(wrtc_data);
640 SAFE_FREE(transport);
641 SET_ERRNO(ERROR_MEMORY, "Failed to create receive queue");
642 return NULL;
643 }
644
645 // Initialize synchronization primitives
646 if (mutex_init(&wrtc_data->queue_mutex, "webrtc_queue") != 0) {
647 ringbuffer_destroy(wrtc_data->recv_queue);
648 SAFE_FREE(wrtc_data);
649 SAFE_FREE(transport);
650 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize queue mutex");
651 return NULL;
652 }
653
654 if (cond_init(&wrtc_data->queue_cond, "queue") != 0) {
655 mutex_destroy(&wrtc_data->queue_mutex);
656 ringbuffer_destroy(wrtc_data->recv_queue);
657 SAFE_FREE(wrtc_data);
658 SAFE_FREE(transport);
659 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize queue condition variable");
660 return NULL;
661 }
662
663 if (mutex_init(&wrtc_data->state_mutex, "webrtc_state") != 0) {
664 cond_destroy(&wrtc_data->queue_cond);
665 mutex_destroy(&wrtc_data->queue_mutex);
666 ringbuffer_destroy(wrtc_data->recv_queue);
667 SAFE_FREE(wrtc_data);
668 SAFE_FREE(transport);
669 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize state mutex");
670 return NULL;
671 }
672
673 if (mutex_init(&wrtc_data->send_mutex, "webrtc_send") != 0) {
674 mutex_destroy(&wrtc_data->state_mutex);
675 cond_destroy(&wrtc_data->queue_cond);
676 mutex_destroy(&wrtc_data->queue_mutex);
677 ringbuffer_destroy(wrtc_data->recv_queue);
678 SAFE_FREE(wrtc_data);
679 SAFE_FREE(transport);
680 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize send mutex");
681 return NULL;
682 }
683
684 // Initialize WebRTC data
685 wrtc_data->peer_conn = peer_conn;
686 wrtc_data->data_channel = data_channel;
687 wrtc_data->is_connected = false; // Will be set to true in on_open callback
688
689 // Register DataChannel callbacks
691 .on_open = webrtc_on_open,
692 .on_close = webrtc_on_close,
693 .on_error = webrtc_on_error,
694 .on_message = webrtc_on_message,
695 .user_data = wrtc_data,
696 };
697
698 asciichat_error_t result = webrtc_datachannel_set_callbacks(data_channel, &callbacks);
699 if (result != ASCIICHAT_OK) {
700 mutex_destroy(&wrtc_data->send_mutex);
701 mutex_destroy(&wrtc_data->state_mutex);
702 cond_destroy(&wrtc_data->queue_cond);
703 mutex_destroy(&wrtc_data->queue_mutex);
704 ringbuffer_destroy(wrtc_data->recv_queue);
705 SAFE_FREE(wrtc_data);
706 SAFE_FREE(transport);
707 SET_ERRNO(ERROR_INTERNAL, "Failed to set DataChannel callbacks");
708 return NULL;
709 }
710
711 // IMPORTANT: The transport is always created from peer_manager's on_datachannel_open callback,
712 // which means the DataChannel is ALREADY OPEN when we get here. However, by setting our own
713 // callbacks above (webrtc_datachannel_set_callbacks), we replaced the callbacks that would
714 // have set dc->is_open=true. So we need to manually mark both the transport AND the DataChannel
715 // as open/connected now.
716 //
717 // We cannot rely on webrtc_on_open being called later because:
718 // 1. The DataChannel is already open
719 // 2. libdatachannel won't fire the open event again
720 // 3. Setting callbacks after open doesn't trigger a retroactive open event
721
722 // Mark DataChannel as open (needed for webrtc_datachannel_send() check)
723 webrtc_datachannel_set_open_state(data_channel, true);
724
725 // Mark transport as connected
726 mutex_lock(&wrtc_data->state_mutex);
727 wrtc_data->is_connected = true;
728 mutex_unlock(&wrtc_data->state_mutex);
729 log_debug("Transport and DataChannel marked as connected/open (already open from peer_manager callback)");
730
731 // Initialize transport
732 transport->methods = &webrtc_methods;
733 transport->crypto_ctx = crypto_ctx;
734 transport->impl_data = wrtc_data;
735
736 log_info("Created WebRTC transport (crypto: %s)", crypto_ctx ? "enabled" : "disabled");
737
738 // Register WebRTC transport implementation data with transport as parent
739 NAMED_REGISTER(wrtc_data, "impl", "webrtc_impl", "0x%tx", (uintptr_t)(const void *)(transport));
740
741 return transport;
742}
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
@ ERROR_INTERNAL
Definition error_codes.h:92
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
int cond_init(cond_t *cond, const char *name)
Initialize a condition variable with a name.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
int cond_destroy(cond_t *cond)
Destroy a condition variable.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
Definition threading.c:22
ringbuffer_t * ringbuffer_create(size_t element_size, size_t capacity)
Create a new ring buffer.
Definition ringbuffer.c:29
void ringbuffer_destroy(ringbuffer_t *rb)
Destroy a ring buffer and free its memory.
Definition ringbuffer.c:53
asciichat_error_t webrtc_datachannel_set_callbacks(webrtc_data_channel_t *dc, const webrtc_datachannel_callbacks_t *callbacks)
Set DataChannel callbacks.
void webrtc_datachannel_set_open_state(webrtc_data_channel_t *dc, bool is_open)
Set DataChannel open state (internal use)
void(* on_open)(webrtc_data_channel_t *dc, void *user_data)
Channel opened.
Receive queue element (variable-length message)
WebRTC transport implementation data.
webrtc_peer_connection_t * peer_conn
Borrowed peer connection.
bool is_connected
Connection state.
mutex_t queue_mutex
Protect queue operations.
webrtc_data_channel_t * data_channel
Borrowed data channel.
mutex_t send_mutex
Keep chunks from concurrent packets together.
mutex_t state_mutex
Protect state changes.
cond_t queue_cond
Signal when messages arrive.
ringbuffer_t * recv_queue
Receive message queue.
#define WEBRTC_RECV_QUEUE_SIZE
Maximum receive queue size (messages buffered before recv())

References ASCIICHAT_OK, cond_destroy(), cond_init(), acip_transport::crypto_ctx, webrtc_transport_data_t::data_channel, ERROR_INTERNAL, ERROR_INVALID_PARAM, ERROR_MEMORY, acip_transport::impl_data, webrtc_transport_data_t::is_connected, log_debug, log_info, acip_transport::methods, mutex_destroy(), mutex_init(), mutex_lock, mutex_unlock, NAMED_REGISTER, webrtc_datachannel_callbacks_t::on_open, webrtc_transport_data_t::peer_conn, webrtc_transport_data_t::queue_cond, webrtc_transport_data_t::queue_mutex, webrtc_transport_data_t::recv_queue, ringbuffer_create(), ringbuffer_destroy(), SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, webrtc_transport_data_t::send_mutex, SET_ERRNO, webrtc_transport_data_t::state_mutex, webrtc_datachannel_set_callbacks(), webrtc_datachannel_set_open_state(), and WEBRTC_RECV_QUEUE_SIZE.

◆ 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.

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
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 1412 of file websocket/transport.c.

1413 {
1414 if (!name) {
1415 SET_ERRNO(ERROR_INVALID_STATE, "Transport name is required");
1416 return NULL;
1417 }
1418
1419 if (!url) {
1420 SET_ERRNO(ERROR_INVALID_PARAM, "url is required");
1421 return NULL;
1422 }
1423
1424 // Parse URL to extract host, port, and path
1425 // Format: ws://host:port/path or wss://host:port/path
1426 const char *protocol_end = strstr(url, "://");
1427 if (!protocol_end) {
1428 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid WebSocket URL format (missing ://)");
1429 return NULL;
1430 }
1431
1432 bool use_ssl = (strncmp(url, "wss://", 6) == 0);
1433 const char *host_start = protocol_end + 3;
1434
1435 // Find port (if specified)
1436 const char *port_start = strchr(host_start, ':');
1437 const char *path_start = strchr(host_start, '/');
1438
1439 char host[256] = {0};
1440 int port = use_ssl ? 443 : 27226; // Default: wss:// uses 443, ws:// uses 27226 (ascii-chat WebSocket port)
1441 char path[256] = "/";
1442
1443 if (port_start && (!path_start || port_start < path_start)) {
1444 // Port is specified
1445 size_t host_len = port_start - host_start;
1446 if (host_len >= sizeof(host)) {
1447 SET_ERRNO(ERROR_INVALID_PARAM, "Host name too long");
1448 return NULL;
1449 }
1450 memcpy(host, host_start, host_len);
1451 host[host_len] = '\0';
1452
1453 // Extract port
1454 char *endptr;
1455 errno = 0;
1456 long port_val = strtol(port_start + 1, &endptr, 10);
1457 if (*endptr != '\0' || errno != 0 || port_val <= 0 || port_val > 65535) {
1458 log_error("websocket_transport_create: Invalid port number: %s", port_start + 1);
1459 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port number");
1460 return NULL;
1461 }
1462 port = (uint16_t)port_val;
1463 } else {
1464 // No port specified, use default
1465 size_t host_len = path_start ? (size_t)(path_start - host_start) : strlen(host_start);
1466 if (host_len >= sizeof(host)) {
1467 SET_ERRNO(ERROR_INVALID_PARAM, "Host name too long");
1468 return NULL;
1469 }
1470 memcpy(host, host_start, host_len);
1471 host[host_len] = '\0';
1472 }
1473
1474 // Extract path
1475 if (path_start) {
1476 SAFE_STRNCPY(path, path_start, sizeof(path) - 1);
1477 path[sizeof(path) - 1] = '\0';
1478 }
1479
1480 log_info("Connecting to WebSocket: %s (host=%s, port=%d, path=%s, ssl=%d)", url, host, port, path, use_ssl);
1481
1482 // Allocate transport structure
1484 if (!transport) {
1485 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebSocket transport");
1486 return NULL;
1487 }
1488
1489 // Allocate WebSocket-specific data
1492 if (!ws_data) {
1493 SAFE_FREE(transport);
1494 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebSocket transport data");
1495 return NULL;
1496 }
1497
1498 // Initialize destruction state flag (CALLOC zero-initializes, but explicit for clarity)
1499 atomic_store_bool(&ws_data->is_destroying, false);
1500
1501 // Create receive queue
1503 if (!ws_data->recv_queue) {
1504 SAFE_FREE(ws_data);
1505 SAFE_FREE(transport);
1506 SET_ERRNO(ERROR_MEMORY, "Failed to create receive queue");
1507 return NULL;
1508 }
1509
1510 // Create send queue for client transport to buffer outgoing messages
1512 if (!ws_data->send_queue) {
1514 SAFE_FREE(ws_data);
1515 SAFE_FREE(transport);
1516 SET_ERRNO(ERROR_MEMORY, "Failed to create send queue");
1517 return NULL;
1518 }
1519
1520 // Initialize synchronization primitives with transport-aware names
1521 char recv_name[64], state_name[64];
1522 snprintf(recv_name, sizeof(recv_name), "recv_%s", name);
1523 snprintf(state_name, sizeof(state_name), "state_%s", name);
1524
1525 if (mutex_init(&ws_data->recv_mutex, recv_name) != 0) {
1528 SAFE_FREE(ws_data);
1529 SAFE_FREE(transport);
1530 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize recv mutex");
1531 return NULL;
1532 }
1533
1534 if (cond_init(&ws_data->recv_cond, recv_name) != 0) {
1535 mutex_destroy(&ws_data->recv_mutex);
1538 SAFE_FREE(ws_data);
1539 SAFE_FREE(transport);
1540 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize recv condition variable");
1541 return NULL;
1542 }
1543
1544 if (mutex_init(&ws_data->state_mutex, state_name) != 0) {
1545 cond_destroy(&ws_data->recv_cond);
1546 mutex_destroy(&ws_data->recv_mutex);
1549 SAFE_FREE(ws_data);
1550 SAFE_FREE(transport);
1551 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize state mutex");
1552 return NULL;
1553 }
1554
1555 if (cond_init(&ws_data->state_cond, state_name) != 0) {
1556 mutex_destroy(&ws_data->state_mutex);
1557 cond_destroy(&ws_data->recv_cond);
1558 mutex_destroy(&ws_data->recv_mutex);
1561 SAFE_FREE(ws_data);
1562 SAFE_FREE(transport);
1563 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize state condition variable");
1564 return NULL;
1565 }
1566
1567 // Allocate initial send buffer
1568 ws_data->send_buffer_capacity = LWS_PRE + 524288; // Match tx_packet_size (512KB)
1569 ws_data->send_buffer = SAFE_MALLOC(ws_data->send_buffer_capacity, uint8_t *);
1570 if (!ws_data->send_buffer) {
1571 mutex_destroy(&ws_data->state_mutex);
1572 cond_destroy(&ws_data->recv_cond);
1573 mutex_destroy(&ws_data->recv_mutex);
1576 SAFE_FREE(ws_data);
1577 SAFE_FREE(transport);
1578 SET_ERRNO(ERROR_MEMORY, "Failed to allocate send buffer");
1579 return NULL;
1580 }
1581
1582 // Create libwebsockets context
1583 // Protocol array must persist for lifetime of context - use static
1584 // Register "http" protocol for WebSocket connections - matches server-side protocol name
1585 // This ensures libwebsockets will invoke our websocket_callback during upgrade handshake and communication
1586 static struct lws_protocols client_protocols[] = {
1587 {
1588 "http", // Default HTTP protocol - handles WebSocket upgrade and ACIP communication
1589 websocket_callback,
1590 0, // Per-session data - using connect_info.userdata instead
1591 524288, // RX buffer size
1592 0, // ID
1593 NULL, // User pointer (set from connect_info.userdata)
1594 524288 // TX packet size
1595 },
1596 {
1597 "acip", // Application subprotocol selected by the ACIP WebSocket client
1598 websocket_callback,
1599 0,
1600 524288,
1601 0,
1602 NULL,
1603 524288
1604 },
1605 {NULL, NULL, 0, 0, 0, NULL, 0} // Terminator
1606 };
1607
1608 // Disable client compression for now - causes assertion in lws_set_extension_option()
1609 // This is a known issue with libwebsockets permessage-deflate negotiation
1610 // Server-side compression is still enabled, data will be compressed from server to client
1611 // but client->server traffic remains uncompressed (acceptable since client sends less data)
1612 // static const struct lws_extension client_extensions[] = {
1613 // {"permessage-deflate", lws_extension_callback_pm_deflate, "permessage-deflate; client_max_window_bits=15"},
1614 // {NULL, NULL, NULL}};
1615
1616 struct lws_context_creation_info info;
1617 memset(&info, 0, sizeof(info));
1618 info.port = CONTEXT_PORT_NO_LISTEN; // Client mode - no listening
1619 info.protocols = client_protocols;
1620 info.gid = (gid_t)-1; // Cast to avoid undefined behavior with unsigned type
1621 info.uid = (uid_t)-1; // Cast to avoid undefined behavior with unsigned type
1622 // Initialize SSL globally for client (required even for clients to use OpenSSL)
1623 // The LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT flag works for both servers and clients
1624 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
1625 info.extensions = NULL; // Disable client compression due to lws_set_extension_option() assertion
1626
1627 // Increase per-thread service buffer to prevent fragmentation of large messages
1628 // Default is 4KB, causing 291KB frames to fragment into 73 × 4KB chunks
1629 // Increase to 512KB to match server and allow larger WebSocket frames without fragmentation
1630 info.pt_serv_buf_size = 512 * 1024; // 512KB per-thread service buffer
1631
1632 // Configure keep-alive to prevent idle disconnects during handshake
1633 // PING every 30 seconds if idle, close after 35 seconds total with no response
1634 static const lws_retry_bo_t client_keep_alive_policy = {
1635 .secs_since_valid_ping = 30, // Send PING after 30s idle
1636 .secs_since_valid_hangup = 35, // Hangup if still idle after 35s
1637 };
1638 info.retry_and_idle_policy = &client_keep_alive_policy;
1639
1640 // Enable libwebsockets logging through centralized logging system
1642
1643 ws_data->context = lws_create_context(&info);
1644
1645 if (!ws_data->context) {
1646 SAFE_FREE(ws_data->send_buffer);
1647 cond_destroy(&ws_data->state_cond);
1648 mutex_destroy(&ws_data->state_mutex);
1649 cond_destroy(&ws_data->recv_cond);
1650 mutex_destroy(&ws_data->recv_mutex);
1653 SAFE_FREE(ws_data);
1654 SAFE_FREE(transport);
1655 SET_ERRNO(ERROR_NETWORK, "Failed to create libwebsockets context");
1656 return NULL;
1657 }
1658
1659 // Initialize connection state flags BEFORE calling lws_client_connect_via_info
1660 // The callback might fire immediately, so all fields must be ready
1661 ws_data->is_connected = false; // Will be set to true in LWS_CALLBACK_CLIENT_ESTABLISHED
1662 ws_data->connection_failed = false; // Set to true in LWS_CALLBACK_CLIENT_CONNECTION_ERROR
1663 ws_data->owns_context = true; // Client transport owns the context
1664
1665 // Connect to WebSocket server
1666 log_debug("Initiating WebSocket connection to %s:%d%s", host, port, path);
1667 struct lws_client_connect_info connect_info;
1668 memset(&connect_info, 0, sizeof(connect_info));
1669 connect_info.context = ws_data->context;
1670 connect_info.address = host;
1671 connect_info.port = port;
1672 connect_info.path = path;
1673 connect_info.host = host;
1674 connect_info.origin = host;
1675 // Request the ACIP subprotocol so server routes to acip_protocol handler
1676 // This ensures WebSocket upgrade includes Sec-WebSocket-Protocol: acip
1677 connect_info.local_protocol_name = "acip";
1678 connect_info.protocol = "acip";
1679 // Use SSL + skip server certificate hostname verification + allow self-signed certs (for development)
1680 connect_info.ssl_connection =
1681 use_ssl ? (LCCSCF_USE_SSL | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_SELFSIGNED) : 0;
1682 connect_info.opaque_user_data = ws_data;
1683
1684 log_debug("Calling lws_client_connect_via_info...");
1685 ws_data->wsi = lws_client_connect_via_info(&connect_info);
1686 log_debug("lws_client_connect_via_info returned: %p", (void *)ws_data->wsi);
1687 if (!ws_data->wsi) {
1688 lws_context_destroy(ws_data->context);
1689 SAFE_FREE(ws_data->send_buffer);
1690 cond_destroy(&ws_data->state_cond);
1691 mutex_destroy(&ws_data->state_mutex);
1692 cond_destroy(&ws_data->recv_cond);
1693 mutex_destroy(&ws_data->recv_mutex);
1696 SAFE_FREE(ws_data);
1697 SAFE_FREE(transport);
1698 SET_ERRNO(ERROR_NETWORK, "Failed to connect to WebSocket server");
1699 return NULL;
1700 }
1701
1702 // Initialize transport
1703 transport->methods = &websocket_methods;
1704 transport->crypto_ctx = crypto_ctx;
1705 transport->impl_data = ws_data;
1706
1707 // Start service thread immediately - it will drive TLS handshake with frequent polling
1708 // The service thread checks is_destroying and context validity before calling lws_service,
1709 // so it's safe to start it right away without delays.
1710 ws_data->service_running = true;
1711 if (asciichat_thread_create(&ws_data->service_thread, "ws_service", websocket_service_thread, ws_data) != 0) {
1712 log_error("Failed to create WebSocket service thread");
1713 ws_data->service_running = false;
1714 lws_context_destroy(ws_data->context);
1715 SAFE_FREE(ws_data->send_buffer);
1716 cond_destroy(&ws_data->state_cond);
1717 mutex_destroy(&ws_data->state_mutex);
1718 cond_destroy(&ws_data->recv_cond);
1719 mutex_destroy(&ws_data->recv_mutex);
1722 SAFE_FREE(ws_data);
1723 SAFE_FREE(transport);
1724 SET_ERRNO(ERROR_INTERNAL, "Failed to create service thread");
1725 return NULL;
1726 }
1727 log_debug("WebSocket service thread started");
1728
1729 // CRITICAL FIX: Do NOT block the main thread waiting for connection!
1730 // This prevents stdin/keyboard input from being processed and causes the client to hang.
1731 // The service thread will establish the connection asynchronously.
1732 // The protocol layer (recv) will detect if connection fails and handle it there.
1733 // Return immediately so the main thread can respond to keyboard input.
1734 log_debug("WebSocket transport created, service thread will establish connection asynchronously");
1735
1736 // Register transport
1737 NAMED_REGISTER_TRANSPORT(transport, name, NULL);
1738
1739 // Register websocket implementation data with transport as parent
1740 (void)NAMED_REGISTER_WEBSOCKET_IMPL(ws_data, "impl", (uintptr_t)(const void *)(transport));
1741
1742 // Return transport immediately - connection will be established by service thread
1743 // If connection fails, recv() will detect is_connected=false and return error
1744 return transport;
1745}
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
unsigned short uint16_t
Definition common.h:57
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
unsigned char uint8_t
Definition common.h:56
#define NAMED_REGISTER_WEBSOCKET_IMPL(data, name, parent_ptr)
@ ERROR_NETWORK
Definition error_codes.h:77
#define asciichat_thread_create(thread_ptr, attr, start_routine, arg)
WebSocket message element (individual frame or fragment)
WebSocket transport implementation data.
struct lws_context * context
libwebsockets context (may be owned or borrowed)
bool owns_context
True if transport owns context (client), false if borrowed (server)
cond_t recv_cond
Signal when recv_queue messages arrive.
mutex_t state_mutex
Protect state changes.
ringbuffer_t * send_queue
Send message queue (for server-side transports)
struct lws * wsi
libwebsockets instance (owned)
ringbuffer_t * recv_queue
Receive message queue.
size_t send_buffer_capacity
Current send buffer capacity.
cond_t state_cond
Signal when is_connected changes (for connection wait)
uint8_t * send_buffer
Send buffer with LWS_PRE padding.
bool connection_failed
Connection attempt failed (for client-side detection)
mutex_t recv_mutex
Protect recv_queue operations only.
asciichat_thread_t service_thread
Thread that services libwebsockets context.
volatile bool service_running
Service thread running flag.
atomic_t is_destroying
True when destruction has started.
#define WEBSOCKET_SEND_QUEUE_SIZE
#define WEBSOCKET_RECV_QUEUE_SIZE
void lws_log_init_client(void)
Initialize libwebsockets logging for client mode.
Definition websocket.c:99

References asciichat_thread_create, atomic_store_bool(), cond_destroy(), cond_init(), websocket_transport_data_t::connection_failed, websocket_transport_data_t::context, acip_transport::crypto_ctx, errno, ERROR_INTERNAL, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_NETWORK, acip_transport::impl_data, websocket_transport_data_t::is_connected, websocket_transport_data_t::is_destroying, log_debug, log_error, log_info, lws_log_init_client(), acip_transport::methods, mutex_destroy(), mutex_init(), NAMED_REGISTER_TRANSPORT, NAMED_REGISTER_WEBSOCKET_IMPL, websocket_transport_data_t::owns_context, websocket_transport_data_t::recv_cond, websocket_transport_data_t::recv_mutex, websocket_transport_data_t::recv_queue, ringbuffer_create(), ringbuffer_destroy(), SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, websocket_transport_data_t::send_buffer, websocket_transport_data_t::send_buffer_capacity, 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, WEBSOCKET_RECV_QUEUE_SIZE, WEBSOCKET_SEND_QUEUE_SIZE, and websocket_transport_data_t::wsi.

Referenced by connection_factory_open(), and websocket_client_connect().

◆ 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 1757 of file websocket/transport.c.

1758 {
1759 if (!name) {
1760 SET_ERRNO(ERROR_INVALID_STATE, "Transport name is required");
1761 return NULL;
1762 }
1763
1764 if (!wsi) {
1765 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid wsi parameter");
1766 return NULL;
1767 }
1768
1769 // Allocate transport structure
1771 if (!transport) {
1772 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebSocket transport");
1773 return NULL;
1774 }
1775
1776 // Allocate transport-specific data
1779 if (!ws_data) {
1780 SAFE_FREE(transport);
1781 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebSocket transport data");
1782 return NULL;
1783 }
1784
1785 // Initialize receive queue
1787 if (!ws_data->recv_queue) {
1788 SAFE_FREE(ws_data);
1789 SAFE_FREE(transport);
1790 SET_ERRNO(ERROR_MEMORY, "Failed to create receive queue");
1791 return NULL;
1792 }
1793
1794 // Initialize send queue (for server-side transports)
1796 if (!ws_data->send_queue) {
1798 SAFE_FREE(ws_data);
1799 SAFE_FREE(transport);
1800 SET_ERRNO(ERROR_MEMORY, "Failed to create send queue");
1801 return NULL;
1802 }
1803
1804 // Initialize pending-free queue for deferred buffer freeing
1805 // permessage-deflate compression holds buffer references asynchronously,
1806 // so we defer freeing to prevent use-after-free errors
1808 if (!ws_data->pending_free_queue) {
1811 SAFE_FREE(ws_data);
1812 SAFE_FREE(transport);
1813 SET_ERRNO(ERROR_MEMORY, "Failed to create pending-free queue");
1814 return NULL;
1815 }
1816
1817 // Initialize synchronization primitives with transport-aware names
1818 char recv_name[64], send_name[64], state_name[64], pending_free_name[64];
1819 snprintf(recv_name, sizeof(recv_name), "recv_%s", name);
1820 snprintf(send_name, sizeof(send_name), "send_%s", name);
1821 snprintf(state_name, sizeof(state_name), "state_%s", name);
1822 snprintf(pending_free_name, sizeof(pending_free_name), "pending_free_%s", name);
1823
1824 if (mutex_init(&ws_data->recv_mutex, recv_name) != 0) {
1827 SAFE_FREE(ws_data);
1828 SAFE_FREE(transport);
1829 SET_ERRNO(ERROR_NETWORK, "Failed to initialize recv mutex");
1830 return NULL;
1831 }
1832
1833 if (cond_init(&ws_data->recv_cond, recv_name) != 0) {
1834 mutex_destroy(&ws_data->recv_mutex);
1837 SAFE_FREE(ws_data);
1838 SAFE_FREE(transport);
1839 SET_ERRNO(ERROR_NETWORK, "Failed to initialize recv condition variable");
1840 return NULL;
1841 }
1842
1843 if (mutex_init(&ws_data->send_mutex, send_name) != 0) {
1844 cond_destroy(&ws_data->recv_cond);
1845 mutex_destroy(&ws_data->recv_mutex);
1848 SAFE_FREE(ws_data);
1849 SAFE_FREE(transport);
1850 SET_ERRNO(ERROR_NETWORK, "Failed to initialize send mutex");
1851 return NULL;
1852 }
1853
1854 if (mutex_init(&ws_data->state_mutex, state_name) != 0) {
1855 mutex_destroy(&ws_data->send_mutex);
1856 cond_destroy(&ws_data->recv_cond);
1857 mutex_destroy(&ws_data->recv_mutex);
1860 SAFE_FREE(ws_data);
1861 SAFE_FREE(transport);
1862 SET_ERRNO(ERROR_NETWORK, "Failed to initialize state mutex");
1863 return NULL;
1864 }
1865
1866 if (cond_init(&ws_data->state_cond, state_name) != 0) {
1867 mutex_destroy(&ws_data->state_mutex);
1868 mutex_destroy(&ws_data->send_mutex);
1869 cond_destroy(&ws_data->recv_cond);
1870 mutex_destroy(&ws_data->recv_mutex);
1873 SAFE_FREE(ws_data);
1874 SAFE_FREE(transport);
1875 SET_ERRNO(ERROR_NETWORK, "Failed to initialize state condition variable");
1876 return NULL;
1877 }
1878
1879 if (mutex_init(&ws_data->pending_free_mutex, pending_free_name) != 0) {
1880 cond_destroy(&ws_data->state_cond);
1881 mutex_destroy(&ws_data->state_mutex);
1882 mutex_destroy(&ws_data->send_mutex);
1883 cond_destroy(&ws_data->recv_cond);
1884 mutex_destroy(&ws_data->recv_mutex);
1888 SAFE_FREE(ws_data);
1889 SAFE_FREE(transport);
1890 SET_ERRNO(ERROR_NETWORK, "Failed to initialize pending-free mutex");
1891 return NULL;
1892 }
1893
1894 // Allocate send buffer with LWS_PRE padding
1895 size_t initial_capacity = 4096 + LWS_PRE;
1896 ws_data->send_buffer = SAFE_MALLOC(initial_capacity, uint8_t *);
1897 if (!ws_data->send_buffer) {
1898 mutex_destroy(&ws_data->state_mutex);
1899 mutex_destroy(&ws_data->send_mutex);
1900 cond_destroy(&ws_data->recv_cond);
1901 mutex_destroy(&ws_data->recv_mutex);
1904 SAFE_FREE(ws_data);
1905 SAFE_FREE(transport);
1906 SET_ERRNO(ERROR_MEMORY, "Failed to allocate send buffer");
1907 return NULL;
1908 }
1909 ws_data->send_buffer_capacity = initial_capacity;
1910
1911 // Store connection info (server-side: no context ownership, connection already established)
1912 ws_data->wsi = wsi;
1913 ws_data->context = lws_get_context(wsi); // Get context from wsi (not owned)
1914 ws_data->owns_context = false; // Server owns context, not transport
1915 ws_data->is_connected = true; // Already connected (server-side)
1916 log_info("[WEBSOCKET_TRANSPORT_CREATE] ★★★ SERVER TRANSPORT CREATED: is_connected=true, wsi=%p, ws_data=%p",
1917 (void *)wsi, (void *)ws_data);
1918 log_debug("Server transport created: is_connected=true, wsi=%p", (void *)wsi);
1919
1920 // Initialize transport
1921 transport->methods = &websocket_methods;
1922 transport->crypto_ctx = crypto_ctx;
1923 transport->impl_data = ws_data;
1924
1925 log_info("Created WebSocket server transport (crypto: %s)", crypto_ctx ? "enabled" : "disabled");
1926
1927 // Register transport
1928 NAMED_REGISTER_TRANSPORT(transport, name, NULL);
1929
1930 // Register websocket implementation data with transport as parent
1931 (void)NAMED_REGISTER_WEBSOCKET_IMPL(ws_data, "impl", (uintptr_t)(const void *)(transport));
1932
1933 return transport;
1934}
Pending buffer free item for deferred cleanup Used to defer buffer freeing when permessage-deflate co...
mutex_t send_mutex
Protect send_queue operations only.
ringbuffer_t * pending_free_queue
Queue of buffers pending delayed free.
mutex_t pending_free_mutex
Protect pending_free_queue operations.

References cond_destroy(), cond_init(), websocket_transport_data_t::context, acip_transport::crypto_ctx, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_NETWORK, acip_transport::impl_data, websocket_transport_data_t::is_connected, log_debug, log_info, acip_transport::methods, mutex_destroy(), mutex_init(), NAMED_REGISTER_TRANSPORT, NAMED_REGISTER_WEBSOCKET_IMPL, websocket_transport_data_t::owns_context, websocket_transport_data_t::pending_free_mutex, websocket_transport_data_t::pending_free_queue, websocket_transport_data_t::recv_cond, websocket_transport_data_t::recv_mutex, websocket_transport_data_t::recv_queue, ringbuffer_create(), ringbuffer_destroy(), SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, websocket_transport_data_t::send_buffer, websocket_transport_data_t::send_buffer_capacity, websocket_transport_data_t::send_mutex, websocket_transport_data_t::send_queue, SET_ERRNO, websocket_transport_data_t::state_cond, websocket_transport_data_t::state_mutex, WEBSOCKET_RECV_QUEUE_SIZE, WEBSOCKET_SEND_QUEUE_SIZE, and websocket_transport_data_t::wsi.