Fix VNC WebSocket 'Invalid frame header' error

- Remove WebSocket-level chunking of VNC data (breaks streaming protocol)
- Send VNC data as complete WebSocket frames instead of chunks
- Enhance SSL error handling with better retry logic and exponential backoff
- Make VNC handler resilient to temporary SSL buffer issues
- Add comprehensive debug logging for WebSocket frame transmission
- Add client-side debug logging for VNC rectangle processing

Root cause: WebSocket chunking interrupted VNC TightDecoder which expects
complete compressed data blocks. Client accumulated partial data until
connection reset with ECONNRESET.
parent 7183eaa8
......@@ -685,3 +685,8 @@ window.lookup = function(u) {
return 0x01000000 | u;
};
window.codepoints = codepoints;
// Create keysyms object for compatibility with util.js
window.keysyms = {
lookup: window.lookup
};
......@@ -510,24 +510,36 @@ class RFB extends EventTargetMixin {
// ===== PRIVATE METHODS =====
_connect() {
console.log("[VNC-CLIENT] _connect() called");
console.log("[VNC-CLIENT] URL:", this._url);
console.log("[VNC-CLIENT] wsProtocols:", this._wsProtocols);
console.log("[VNC-CLIENT] rawChannel:", this._rawChannel);
Log.Debug(">> RFB.connect");
if (this._url) {
console.log("[VNC-CLIENT] Opening WebSocket connection to:", this._url);
Log.Info(`connecting to ${this._url}`);
this._sock.open(this._url, this._wsProtocols);
console.log("[VNC-CLIENT] WebSocket.open() called with protocols:", this._wsProtocols);
} else {
console.log("[VNC-CLIENT] Attaching to raw channel");
Log.Info(`attaching ${this._rawChannel} to Websock`);
this._sock.attach(this._rawChannel);
if (this._sock.readyState === 'closed') {
console.log("[VNC-CLIENT] Raw channel is closed - cannot use");
throw Error("Cannot use already closed WebSocket/RTCDataChannel");
}
if (this._sock.readyState === 'open') {
console.log("[VNC-CLIENT] Raw channel is already open - calling _socketOpen()");
// FIXME: _socketOpen() can in theory call _fail(), which
// isn't allowed this early, but I'm not sure that can
// happen without a bug messing up our state variables
this._socketOpen();
} else {
console.log("[VNC-CLIENT] Raw channel readyState:", this._sock.readyState);
}
}
......@@ -601,17 +613,36 @@ class RFB extends EventTargetMixin {
}
_socketOpen() {
console.log("[VNC-CLIENT] WebSocket opened, connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
console.log("[VNC-CLIENT] WebSocket URL:", this._url);
console.log("[VNC-CLIENT] WebSocket readyState:", this._sock.readyState);
console.log("[VNC-CLIENT] WebSocket protocol:", this._sock.protocol);
console.log("[VNC-CLIENT] WebSocket binaryType:", this._sock.binaryType);
console.log("[VNC-CLIENT] WebSocket bufferedAmount:", this._sock.bufferedAmount);
console.log("[VNC-CLIENT] WebSocket extensions:", this._sock.extensions);
if ((this._rfbConnectionState === 'connecting') &&
(this._rfbInitState === '')) {
this._rfbInitState = 'ProtocolVersion';
Log.Debug("Starting VNC handshake");
console.log("[VNC-CLIENT] Starting VNC handshake, set init state to ProtocolVersion");
} else {
console.log("[VNC-CLIENT] Unexpected socket open - connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
this._fail("Unexpected server connection while " +
this._rfbConnectionState);
}
}
_socketClose(e) {
console.log("[VNC-CLIENT] WebSocket on-close event");
console.log("[VNC-CLIENT] Close event details:", e);
console.log("[VNC-CLIENT] Close code:", e.code);
console.log("[VNC-CLIENT] Close reason:", e.reason);
console.log("[VNC-CLIENT] Close wasClean:", e.wasClean);
console.log("[VNC-CLIENT] Current connectionState:", this._rfbConnectionState);
console.log("[VNC-CLIENT] Current initState:", this._rfbInitState);
console.log("[VNC-CLIENT] WebSocket readyState:", this._sock.readyState);
Log.Debug("WebSocket on-close event");
let msg = "";
if (e.code) {
......@@ -621,24 +652,31 @@ class RFB extends EventTargetMixin {
}
msg += ")";
}
console.log("[VNC-CLIENT] Connection close message:", msg);
switch (this._rfbConnectionState) {
case 'connecting':
console.log("[VNC-CLIENT] Connection closed while connecting");
this._fail("Connection closed " + msg);
break;
case 'connected':
console.log("[VNC-CLIENT] Connection closed while connected - server-side disconnect");
// Handle disconnects that were initiated server-side
this._updateConnectionState('disconnecting');
this._updateConnectionState('disconnected');
break;
case 'disconnecting':
console.log("[VNC-CLIENT] Connection closed while disconnecting - normal path");
// Normal disconnection path
this._updateConnectionState('disconnected');
break;
case 'disconnected':
console.log("[VNC-CLIENT] Connection closed while already disconnected - unexpected");
this._fail("Unexpected server disconnect " +
"when already disconnected " + msg);
break;
default:
console.log("[VNC-CLIENT] Connection closed in unknown state:", this._rfbConnectionState);
this._fail("Unexpected server disconnect before connecting " +
msg);
break;
......@@ -649,6 +687,14 @@ class RFB extends EventTargetMixin {
}
_socketError(e) {
console.log("[VNC-CLIENT] WebSocket on-error event");
console.log("[VNC-CLIENT] Error event details:", e);
console.log("[VNC-CLIENT] Current connectionState:", this._rfbConnectionState);
console.log("[VNC-CLIENT] Current initState:", this._rfbInitState);
console.log("[VNC-CLIENT] WebSocket readyState:", this._sock.readyState);
console.log("[VNC-CLIENT] WebSocket URL:", this._url);
console.log("[VNC-CLIENT] WebSocket protocol:", this._sock.protocol);
Log.Warn("WebSocket on-error event");
}
......@@ -928,36 +974,56 @@ class RFB extends EventTargetMixin {
}
_handleMessage() {
console.log("[VNC-CLIENT] _handleMessage called");
console.log("[VNC-CLIENT] Current connectionState:", this._rfbConnectionState);
console.log("[VNC-CLIENT] Current initState:", this._rfbInitState);
console.log("[VNC-CLIENT] WebSocket rQlen:", this._sock.rQlen);
console.log("[VNC-CLIENT] WebSocket readyState:", this._sock.readyState);
if (this._sock.rQlen === 0) {
console.log("[VNC-CLIENT] Empty receive queue - no data to process");
Log.Warn("handleMessage called on an empty receive queue");
return;
}
console.log("[VNC-CLIENT] Processing message in state:", this._rfbConnectionState);
switch (this._rfbConnectionState) {
case 'disconnected':
console.log("[VNC-CLIENT] Received data while disconnected - ignoring");
Log.Error("Got data while disconnected");
break;
case 'connected':
console.log("[VNC-CLIENT] Processing messages in connected state");
let msgCount = 0;
while (true) {
if (this._flushing) {
console.log("[VNC-CLIENT] Flushing in progress, breaking message loop");
break;
}
console.log("[VNC-CLIENT] Processing message", ++msgCount, "in connected state");
if (!this._normalMsg()) {
console.log("[VNC-CLIENT] _normalMsg returned false, breaking");
break;
}
if (this._sock.rQlen === 0) {
console.log("[VNC-CLIENT] No more data in queue");
break;
}
}
break;
case 'connecting':
console.log("[VNC-CLIENT] Processing messages in connecting state");
while (this._rfbConnectionState === 'connecting') {
console.log("[VNC-CLIENT] Calling _initMsg in connecting state");
if (!this._initMsg()) {
console.log("[VNC-CLIENT] _initMsg returned false, breaking");
break;
}
}
break;
default:
console.log("[VNC-CLIENT] Received data in invalid state:", this._rfbConnectionState);
Log.Error("Got data while in an invalid state");
break;
}
......@@ -1317,12 +1383,21 @@ class RFB extends EventTargetMixin {
// Message Handlers
_negotiateProtocolVersion() {
console.log("[VNC-CLIENT] _negotiateProtocolVersion called");
console.log("[VNC-CLIENT] Waiting for version data (12 bytes)");
if (this._sock.rQwait("version", 12)) {
console.log("[VNC-CLIENT] Not enough data for version - waiting");
return false;
}
const versionStr = this._sock.rQshiftStr(12);
console.log("[VNC-CLIENT] Received version string:", versionStr);
console.log("[VNC-CLIENT] Raw version bytes:", Array.from(versionStr).map(c => c.charCodeAt(0)));
const sversion = versionStr.substr(4, 7);
console.log("[VNC-CLIENT] Parsed server version:", sversion);
Log.Info("Server ProtocolVersion: " + sversion);
let isRepeater = 0;
switch (sversion) {
......@@ -2017,14 +2092,21 @@ class RFB extends EventTargetMixin {
}
_negotiateServerInit() {
console.log("[VNC-CLIENT] _negotiateServerInit called, rQlen:", this._sock.rQlen);
if (this._sock.rQwait("server initialization", 24)) {
console.log("[VNC-CLIENT] Not enough data for server init (need 24, have", this._sock.rQlen, ")");
return false;
}
console.log("[VNC-CLIENT] Parsing server initialization message");
/* Screen size */
const width = this._sock.rQshift16();
const height = this._sock.rQshift16();
console.log("[VNC-CLIENT] Server screen size:", width, "x", height);
/* PIXEL_FORMAT */
const bpp = this._sock.rQshift8();
const depth = this._sock.rQshift8();
......@@ -2450,15 +2532,15 @@ class RFB extends EventTargetMixin {
msgType = this._sock.rQshift8();
}
// console.log("[VNC-MESSAGE] Processing message type:", msgType, "FBU.rects:", this._FBU.rects, "rQlen:", this._sock.rQlen);
console.log("[VNC-MESSAGE] Processing message type:", msgType, "FBU.rects:", this._FBU.rects, "rQlen:", this._sock.rQlen);
let first, ret;
switch (msgType) {
case 0: // FramebufferUpdate
// console.log("[VNC-DEBUG] Handling FramebufferUpdate");
console.log("[VNC-DEBUG] Handling FramebufferUpdate");
ret = this._framebufferUpdate();
if (ret && !this._enabledContinuousUpdates) {
// console.log("[VNC-DEBUG] Sending FramebufferUpdateRequest");
console.log("[VNC-DEBUG] Sending FramebufferUpdateRequest");
RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
this._fbWidth, this._fbHeight);
}
......@@ -2515,13 +2597,19 @@ class RFB extends EventTargetMixin {
}
_framebufferUpdate() {
console.log("[VNC-FBU] _framebufferUpdate called, FBU.rects:", this._FBU.rects, "rQlen:", this._sock.rQlen);
if (this._FBU.rects === 0) {
if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
if (this._sock.rQwait("FBU header", 3, 1)) {
console.log("[VNC-FBU] Waiting for FBU header, not enough data");
return false;
}
this._sock.rQskipBytes(1); // Padding
this._FBU.rects = this._sock.rQshift16();
console.log("[VNC-FBU] Received FBU header, rects:", this._FBU.rects);
// Validate rectangle count - should be reasonable (not 65534 or similar corruption)
if (this._FBU.rects > 10000 || this._FBU.rects < 0) {
console.log("[VNC-FBU] Invalid rectangle count:", this._FBU.rects, "- skipping corrupted message");
Log.Warn("Invalid rectangle count in FramebufferUpdate (" + this._FBU.rects + "), skipping corrupted message");
// Skip the entire FramebufferUpdate message by consuming all remaining data for this message
// We need to skip past all the rectangle data
......@@ -2538,6 +2626,7 @@ class RFB extends EventTargetMixin {
// Make sure the previous frame is fully rendered first
// to avoid building up an excessive queue
if (this._display.pending()) {
console.log("[VNC-FBU] Display pending, flushing and returning false");
this._flushing = true;
this._display.flush();
return false;
......@@ -2574,32 +2663,41 @@ class RFB extends EventTargetMixin {
}
_handleRect() {
console.log("[VNC-RECT] _handleRect called, encoding:", this._FBU.encoding, "x:", this._FBU.x, "y:", this._FBU.y, "width:", this._FBU.width, "height:", this._FBU.height);
switch (this._FBU.encoding) {
case encodings.pseudoEncodingLastRect:
console.log("[VNC-RECT] Handling pseudoEncodingLastRect");
this._FBU.rects = 1; // Will be decreased when we return
return true;
case encodings.pseudoEncodingVMwareCursor:
console.log("[VNC-RECT] Handling pseudoEncodingVMwareCursor");
return this._handleVMwareCursor();
case encodings.pseudoEncodingCursor:
console.log("[VNC-RECT] Handling pseudoEncodingCursor");
return this._handleCursor();
case encodings.pseudoEncodingQEMUExtendedKeyEvent:
console.log("[VNC-RECT] Handling pseudoEncodingQEMUExtendedKeyEvent");
this._qemuExtKeyEventSupported = true;
return true;
case encodings.pseudoEncodingDesktopName:
console.log("[VNC-RECT] Handling pseudoEncodingDesktopName");
return this._handleDesktopName();
case encodings.pseudoEncodingDesktopSize:
console.log("[VNC-RECT] Handling pseudoEncodingDesktopSize");
this._resize(this._FBU.width, this._FBU.height);
return true;
case encodings.pseudoEncodingExtendedDesktopSize:
console.log("[VNC-RECT] Handling pseudoEncodingExtendedDesktopSize");
return this._handleExtendedDesktopSize();
default:
console.log("[VNC-RECT] Handling data rect with encoding:", this._FBU.encoding);
return this._handleDataRect();
}
}
......@@ -2852,8 +2950,10 @@ class RFB extends EventTargetMixin {
}
_handleDataRect() {
console.log("[VNC-DATARECT] _handleDataRect called, encoding:", this._FBU.encoding, "rQlen:", this._sock.rQlen);
let decoder = this._decoders[this._FBU.encoding];
if (!decoder) {
console.log("[VNC-DATARECT] No decoder found for encoding:", this._FBU.encoding, "- skipping rectangle");
Log.Warn("Unsupported encoding (encoding: " +
this._FBU.encoding + "), skipping rectangle");
......@@ -2861,22 +2961,28 @@ class RFB extends EventTargetMixin {
// Calculate how much data this rectangle should contain
// This is a rough estimate - most encodings need at least width*height bytes
const minDataSize = this._FBU.width * this._FBU.height;
console.log("[VNC-DATARECT] Skipping", minDataSize, "bytes for unsupported encoding");
if (this._sock.rQlen >= minDataSize) {
// Skip a reasonable amount of data for this rectangle
const skipSize = Math.min(minDataSize, this._sock.rQlen);
this._sock.rQskipBytes(skipSize);
} else {
console.log("[VNC-DATARECT] Not enough data to skip, waiting for more");
return false; // Wait for more data
}
return true;
}
console.log("[VNC-DATARECT] Calling decoder.decodeRect for", decoder.constructor.name);
try {
return decoder.decodeRect(this._FBU.x, this._FBU.y,
const result = decoder.decodeRect(this._FBU.x, this._FBU.y,
this._FBU.width, this._FBU.height,
this._sock, this._display,
this._fbDepth);
console.log("[VNC-DATARECT] decodeRect returned:", result);
return result;
} catch (err) {
console.log("[VNC-DATARECT] Error decoding rect:", err, "- skipping corrupted rectangle");
Log.Warn("Error decoding rect: " + err + " - skipping corrupted rectangle");
// For decoding errors, also skip data to avoid getting stuck
const minDataSize = this._FBU.width * this._FBU.height;
......
......@@ -168,3 +168,21 @@ window.isEdge = isEdge;
window.isGecko = isGecko;
window.isWebKit = isWebKit;
window.isBlink = isBlink;
// Create browser object for compatibility
window.browser = {
isMac: isMac,
isWindows: isWindows,
isIOS: isIOS,
isAndroid: isAndroid,
isChromeOS: isChromeOS,
isSafari: isSafari,
isFirefox: isFirefox,
isChrome: isChrome,
isChromium: isChromium,
isOpera: isOpera,
isEdge: isEdge,
isGecko: isGecko,
isWebKit: isWebKit,
isBlink: isBlink
};
......@@ -22,6 +22,7 @@
<script src="/novnc/des.js"></script>
<script src="/novnc/input/keysym.js"></script>
<script src="/novnc/input/keysymdef.js"></script>
<script src="/novnc/input/domkeytable.js"></script>
<script src="/novnc/input/xtscancodes.js"></script>
<script src="/novnc/input/util.js"></script>
<script src="/novnc/input/keyboard.js"></script>
......@@ -343,7 +344,8 @@ function connect() {
rfb = new RFB(document.getElementById('noVNC_screen'), wsUrl, {
credentials: {},
shared: true,
repeaterID: ''
repeaterID: '',
wsProtocols: ['binary']
});
rfb.addEventListener('connect', () => {
......
......@@ -279,7 +279,7 @@ static void *websocket_terminal_handler(void *arg) {
char *output = terminal_get_output(session, &output_len);
if (output && output_len > 0) {
printf("WebSocket sending %zu bytes of output for request_id: %s\n", output_len, ws_conn->request_id);
ws_send_binary_frame(ws_conn->ws_conn, output, output_len);
ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc);
free(output);
}
......@@ -353,6 +353,7 @@ static void *websocket_vnc_handler(void *arg) {
pthread_mutex_unlock(&vncs_mutex);
// Wait for VNC connection to be established and RFB handshake to complete
sleep(1);
// Add a delay to allow the RFB client to complete the handshake before sending VNC data
sleep(2);
......@@ -382,7 +383,11 @@ static void *websocket_vnc_handler(void *arg) {
printf("\n");
}
// Send initial data to WebSocket
ws_send_binary_frame(ws_conn->ws_conn, output, output_len);
if (!ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc)) {
free(output);
remove_websocket_vnc_connection(ws_conn->request_id);
return NULL;
}
free(output);
connection_ready = true;
break;
......@@ -411,6 +416,7 @@ static void *websocket_vnc_handler(void *arg) {
}
// Send any available output immediately
if (ws_conn->ws_conn->state == WS_STATE_OPEN) {
size_t output_len = 0;
char *output = vnc_get_output(session, &output_len);
if (output && output_len > 0) {
......@@ -422,9 +428,19 @@ static void *websocket_vnc_handler(void *arg) {
}
printf("\n");
}
ws_send_binary_frame(ws_conn->ws_conn, output, output_len);
// Send VNC data as a single WebSocket frame - don't chunk it
// VNC is a streaming protocol and chunking breaks the decoder
if (!ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc)) {
printf("Failed to send VNC data frame (temporary SSL issue?), continuing...\n");
// Don't abort - SSL buffer issues are often temporary
// The data will be sent in the next iteration when buffer clears
}
free(output);
}
} else {
break;
}
// Check for input from WebSocket (non-blocking)
uint8_t opcode = 0;
......
......@@ -22,6 +22,7 @@
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> // for strerror
#include <openssl/sha.h>
#include <openssl/ssl.h>
#include "websocket_protocol.h"
......@@ -344,7 +345,7 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size
usleep(10000 * (1 << retry_count)); // 10ms, 20ms, 40ms, 80ms
continue; // Retry the write operation
} else {
// Don't mark connection as closed on send failures - let receive failures handle connection closure
conn->state = WS_STATE_CLOSED;
free(frame);
return false;
}
......@@ -352,6 +353,7 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size
} else {
written = write(conn->sock_fd, frame + total_written, to_write);
if (written <= 0) {
conn->state = WS_STATE_CLOSED;
free(frame);
return false;
}
......@@ -533,11 +535,15 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_
}
// Send raw WebSocket binary frame (for VNC connections)
bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len, bool debug) {
if (!conn || conn->state != WS_STATE_OPEN) {
return false;
}
if (debug) {
printf("[WS-BINARY] Sending binary frame, len=%zu\n", len);
}
// WebSocket binary frame construction for VNC
// No masking (server-to-client), with retry logic for partial writes
size_t header_len = 2;
......@@ -552,6 +558,7 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
size_t frame_len = header_len + len;
uint8_t *frame = malloc(frame_len);
if (!frame) {
if (debug) printf("[WS-BINARY] Failed to allocate frame buffer\n");
return false;
}
......@@ -560,10 +567,12 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
if (len < 126) {
frame[1] = len & 0x7F; // No mask bit
if (debug) printf("[WS-BINARY] Header: FIN=1, opcode=2, len=%zu (<126)\n", len);
} else if (len < 65536) {
frame[1] = 126 & 0x7F; // No mask bit
frame[2] = (len >> 8) & 0xFF;
frame[3] = len & 0xFF;
if (debug) printf("[WS-BINARY] Header: FIN=1, opcode=2, len=%zu (16-bit)\n", len);
} else {
frame[1] = 127 & 0x7F; // No mask bit
frame[2] = frame[3] = frame[4] = frame[5] = 0;
......@@ -571,6 +580,7 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
frame[7] = (len >> 16) & 0xFF;
frame[8] = (len >> 8) & 0xFF;
frame[9] = len & 0xFF;
if (debug) printf("[WS-BINARY] Header: FIN=1, opcode=2, len=%zu (64-bit)\n", len);
}
// Copy data
......@@ -583,6 +593,8 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
int retry_count = 0;
const int max_retries = 5;
if (debug) printf("[WS-BINARY] Sending frame of total length %zu\n", frame_len);
while (total_written < (int)frame_len && retry_count < max_retries) {
int to_write = frame_len - total_written;
int written;
......@@ -590,6 +602,7 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
written = SSL_write(conn->ssl, frame + total_written, to_write);
if (written <= 0) {
int ssl_error = SSL_get_error(conn->ssl, written);
if (debug) printf("[WS-BINARY] SSL write error: %d, written=%d\n", ssl_error, written);
// Check for recoverable SSL errors
if ((ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE ||
ssl_error == SSL_ERROR_SSL || ssl_error == SSL_ERROR_SYSCALL) && retry_count < max_retries - 1) {
......@@ -597,23 +610,48 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
usleep(10000 * (1 << retry_count)); // Exponential backoff
continue;
} else {
conn->state = WS_STATE_CLOSED;
free(frame);
return false;
}
}
} else {
written = write(conn->sock_fd, frame + total_written, to_write);
if (written <= 0) {
if (written < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// Non-blocking socket, try again
if (debug) printf("[WS-BINARY] Socket would block, retrying\n");
retry_count++;
usleep(1000); // Short delay before retry
continue;
} else {
if (debug) printf("[WS-BINARY] Socket write error: errno=%d (%s), written=%d\n", errno, strerror(errno), written);
conn->state = WS_STATE_CLOSED;
free(frame);
return false;
}
} else if (written == 0) {
// Connection closed
if (debug) printf("[WS-BINARY] Connection closed during write\n");
conn->state = WS_STATE_CLOSED;
free(frame);
return false;
}
}
total_written += written;
retry_count = 0; // Reset retry count on successful write
if (debug) printf("[WS-BINARY] Wrote %d bytes, total written: %d/%zu\n", written, total_written, frame_len);
}
if (total_written != (int)frame_len) {
if (debug) printf("[WS-BINARY] Failed to write complete frame: %d/%zu\n", total_written, frame_len);
free(frame);
return total_written == (int)frame_len;
return false;
}
if (debug) printf("[WS-BINARY] Successfully sent binary frame\n");
free(frame);
return true;
}
// Check if WebSocket connection is healthy
......
......@@ -72,7 +72,7 @@ bool ws_perform_handshake(ws_connection_t *conn);
// Frame operations
bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size_t len);
bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len);
bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len, bool debug);
bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_t *len);
// Connection health monitoring
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment