ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
transport.c File Reference

WebSocket transport implementation for ACIP protocol. More...

Go to the source code of this file.

Macros

#define WEBSOCKET_MESSAGE_QUEUE_SIZE_INCOMING   64
 Maximum incoming message queue size (frames received from peer)
 
#define WEBSOCKET_MESSAGE_QUEUE_SIZE_OUTGOING   512
 Maximum outgoing message queue size (frames to send to peer)
 
#define WEBSOCKET_RECV_QUEUE_SIZE   WEBSOCKET_MESSAGE_QUEUE_SIZE_INCOMING
 
#define WEBSOCKET_SEND_QUEUE_SIZE   WEBSOCKET_MESSAGE_QUEUE_SIZE_OUTGOING
 

Functions

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.
 

Detailed Description

WebSocket transport implementation for ACIP protocol.

Implements the acip_transport_t interface for WebSocket connections. Enables browser clients to connect via WebSocket protocol.

ARCHITECTURE:

  • Uses libwebsockets for WebSocket protocol handling
  • Async libwebsockets callbacks bridge to sync recv() via ringbuffer
  • Thread-safe receive queue handles async message arrival
  • Same pattern as WebRTC transport for consistency

MESSAGE FLOW:

  1. send(): Synchronous write via lws_write()
  2. LWS callback: Async write to receive ringbuffer
  3. recv(): Blocking read from receive ringbuffer

MEMORY OWNERSHIP:

  • Transport OWNS wsi (WebSocket instance)
  • Receive queue owns buffered message data
  • recv() allocates message buffer, caller must free
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
February 2026

Definition in file websocket/transport.c.

Macro Definition Documentation

◆ WEBSOCKET_MESSAGE_QUEUE_SIZE_INCOMING

#define WEBSOCKET_MESSAGE_QUEUE_SIZE_INCOMING   64

Maximum incoming message queue size (frames received from peer)

Set to 64 for balance: allows buffering across one large frame's fragments while keeping latency reasonable. With ~70 fragments per 291KB frame, we need at least 64 slots to avoid dropping the frame while it's fragmenting.

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

◆ WEBSOCKET_MESSAGE_QUEUE_SIZE_OUTGOING

#define WEBSOCKET_MESSAGE_QUEUE_SIZE_OUTGOING   512

Maximum outgoing message queue size (frames to send to peer)

Set to 64 to match incoming queue and prevent "BINARY after FIN" errors. Large video frames fragment into ~70 chunks; queue must be large enough to buffer one complete message worth of fragments.

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

◆ WEBSOCKET_RECV_QUEUE_SIZE

#define WEBSOCKET_RECV_QUEUE_SIZE   WEBSOCKET_MESSAGE_QUEUE_SIZE_INCOMING

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

◆ WEBSOCKET_SEND_QUEUE_SIZE

#define WEBSOCKET_SEND_QUEUE_SIZE   WEBSOCKET_MESSAGE_QUEUE_SIZE_OUTGOING

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

Function Documentation

◆ 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 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
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
unsigned char uint8_t
Definition common.h:56
#define NAMED_REGISTER_WEBSOCKET_IMPL(data, name, parent_ptr)
#define NAMED_REGISTER_TRANSPORT(transport, name, parent_ptr)
Register a transport with automatic format specifier.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_MEMORY
Definition error_codes.h:56
@ ERROR_INVALID_PARAM
@ ERROR_INTERNAL
Definition error_codes.h:92
#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
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
int cond_init(cond_t *cond, const char *name)
Initialize a condition variable with a name.
int errno
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
#define asciichat_thread_create(thread_ptr, attr, start_routine, arg)
Transport instance structure.
Definition transport.h:214
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
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.