master
c 882 lines 37.5 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "websocket-internal.h"
4
5 // --------------------------------------------------------------------------------------------------------------------
6 // reading from the socket
7
8 static inline bool cbuffer_has_enough_data_for_next_frame(WS_CLIENT *wsc) {
9 return wsc->next_frame_size > 0 &&
10 cbuffer_used_size_unsafe(&wsc->in_buffer) >= wsc->next_frame_size;
11 }
12
13 static inline bool cbuffer_next_frame_is_fragmented(WS_CLIENT *wsc) {
14 return cbuffer_has_enough_data_for_next_frame(wsc) &&
15 cbuffer_next_unsafe(&wsc->in_buffer, NULL) < wsc->next_frame_size;
16 }
17
18 static ssize_t websocket_received_data_process(WS_CLIENT *wsc, ssize_t bytes_read) {
19 if(cbuffer_next_frame_is_fragmented(wsc))
20 cbuffer_ensure_unwrapped_size(&wsc->in_buffer, wsc->next_frame_size);
21
22 char *buffer_pos;
23 size_t contiguous_input = cbuffer_next_unsafe(&wsc->in_buffer, &buffer_pos);
24
25 // Now we have contiguous data for processing
26 ssize_t bytes_consumed = websocket_protocol_got_data(wsc, buffer_pos, contiguous_input);
27 if (bytes_consumed < 0) {
28 if (bytes_consumed < -1) {
29 bytes_consumed = -bytes_consumed;
30 cbuffer_remove_unsafe(&wsc->in_buffer, bytes_consumed);
31 }
32
33 websocket_error(wsc, "Failed to process received data");
34 return -1;
35 }
36
37 // Check if bytes_processed is 0 but this was a successful call
38 // This means we have an incomplete frame and need to keep the entire buffer
39 if (bytes_consumed == 0) {
40 websocket_debug(
41 wsc, "Incomplete frame detected - keeping all %zu bytes in buffer for next read", contiguous_input);
42 return bytes_read; // Return the bytes read so caller knows we made progress
43 }
44
45 // We've processed some data - remove it from the circular buffer
46 cbuffer_remove_unsafe(&wsc->in_buffer, bytes_consumed);
47
48 return bytes_consumed;
49 }
50
51 // Process incoming WebSocket data
52 ssize_t websocket_receive_data(WS_CLIENT *wsc) {
53 internal_fatal(wsc->wth->tid != gettid_cached(), "Function %s() should only be used by the websocket thread", __FUNCTION__ );
54
55 worker_is_busy(WORKERS_WEBSOCKET_SOCK_RECEIVE);
56
57 if (!wsc->in_buffer.data || wsc->sock.fd < 0)
58 return -1;
59
60 size_t available_space = WEBSOCKET_RECEIVE_BUFFER_SIZE;
61 if(wsc->next_frame_size > 0) {
62 size_t used_space = cbuffer_used_size_unsafe(&wsc->in_buffer);
63 if(used_space < wsc->next_frame_size) {
64 size_t missing_for_next_frame = wsc->next_frame_size - used_space;
65 available_space = MAX(missing_for_next_frame, WEBSOCKET_RECEIVE_BUFFER_SIZE);
66 }
67 }
68
69 char *buffer = cbuffer_reserve_unsafe(&wsc->in_buffer, available_space);
70 if(!buffer) {
71 websocket_error(wsc, "Not enough space to read %zu bytes", available_space);
72 return -1;
73 }
74
75 // Read data from socket into temporary buffer using ND_SOCK
76 ssize_t bytes_read = nd_sock_read(&wsc->sock, buffer, available_space, 0);
77
78 if (bytes_read <= 0) {
79 if (bytes_read == 0) {
80 // Connection closed
81 websocket_debug(wsc, "Client closed connection");
82 return -1;
83 }
84
85 if (errno == EAGAIN || errno == EWOULDBLOCK)
86 return 0; // No data available right now
87
88 websocket_error(wsc, "Failed to read from client: %s", strerror(errno));
89 return -1;
90 }
91
92 if (bytes_read > (ssize_t)available_space) {
93 websocket_error(wsc, "Received more data (%zd) than available space in buffer (%zd)",
94 bytes_read, available_space);
95 return -1;
96 }
97
98 cbuffer_commit_reserved_unsafe(&wsc->in_buffer, bytes_read);
99
100 // Update last activity time
101 wsc->last_activity_t = now_monotonic_sec();
102
103 // Dump the received data for debugging
104 websocket_dump_debug(wsc, buffer, bytes_read, "RX SOCK %zd bytes", bytes_read);
105
106 if(wsc->next_frame_size == 0 || cbuffer_has_enough_data_for_next_frame(wsc)) {
107 // we don't know the next frame size
108 // or, we know it and we have all the data for it
109
110 // process the received data
111 if(websocket_received_data_process(wsc, bytes_read) < 0)
112 return -1;
113
114 // we may still have wrapped data in the circular buffer that can satisfy the entire next frame
115 if(cbuffer_next_frame_is_fragmented(wsc)) {
116 // we have enough data to process this frame, no need to wait for more input
117 if(websocket_received_data_process(wsc, bytes_read) < 0)
118 return -1;
119 }
120 }
121
122 // Return the number of bytes we processed from this read
123 // Even if bytes_processed is 0, we still read data which will be processed later
124 return bytes_read;
125 }
126
127 // --------------------------------------------------------------------------------------------------------------------
128
129 // Validate a WebSocket close code according to RFC 6455
130 bool websocket_validate_close_code(uint16_t code) {
131 // 1000-2999 are reserved for the WebSocket protocol
132 // 3000-3999 are reserved for use by libraries, frameworks, and applications
133 // 4000-4999 are reserved for private use
134
135 // Check if code is in valid ranges
136 if ((code >= 1000 && code <= 1011) || // Protocol-defined codes
137 (code >= 3000 && code <= 4999)) // Application/library/private codes
138 {
139 // Codes 1004, 1005, and 1006 must not be used in a Close frame by an endpoint
140 if (code != WS_CLOSE_RESERVED && code != WS_CLOSE_NO_STATUS && code != WS_CLOSE_ABNORMAL)
141 return true;
142 }
143
144 return false;
145 }
146
147 // Centralized function to handle WebSocket protocol exceptions
148 void websocket_protocol_exception(WS_CLIENT *wsc, WEBSOCKET_CLOSE_CODE reason_code, const char *reason_txt) {
149 if (!wsc) return;
150
151 websocket_error(wsc, "Protocol exception: %s (code: %d, %s)",
152 reason_txt, reason_code, WEBSOCKET_CLOSE_CODE_2str(reason_code));
153
154 // Always send a close frame with the reason
155 websocket_protocol_send_close(wsc, reason_code, reason_txt);
156
157 // Update state based on current state
158 if (wsc->state == WS_STATE_OPEN) {
159 // We're initiating the close - transition to server-initiated closing
160 wsc->state = WS_STATE_CLOSING_SERVER;
161 }
162 else if (wsc->state == WS_STATE_CLOSING_CLIENT || wsc->state == WS_STATE_CLOSING_SERVER) {
163 // Already in closing state, nothing to do
164 websocket_debug(wsc, "Protocol exception during closing state %s",
165 WEBSOCKET_STATE_2str(wsc->state));
166 }
167 else {
168 // For any other state, move straight to CLOSED
169 wsc->state = WS_STATE_CLOSED;
170 }
171
172 // For severe protocol errors, force immediate disconnection
173 if (reason_code == WS_CLOSE_PROTOCOL_ERROR ||
174 reason_code == WS_CLOSE_POLICY_VIOLATION ||
175 reason_code == WS_CLOSE_INVALID_PAYLOAD) {
176
177 websocket_info(wsc, "Forcing immediate disconnection due to protocol exception");
178
179 // First try to flush outgoing data to send the close frame
180 websocket_write_data(wsc);
181
182 // Remove client from thread
183 if (wsc->wth) {
184 websocket_thread_send_command(wsc->wth, WEBSOCKET_THREAD_CMD_REMOVE_CLIENT, wsc->id);
185 }
186 }
187 }
188
189 #define WS_ALLOW_USE ( 1)
190 #define WS_ALLOW_DISCARD ( 0)
191 #define WS_ALLOW_ERROR (-1)
192
193 // Centralized function to check if a frame is allowed based on connection state
194 static int websocket_is_frame_allowed(WS_CLIENT *wsc, const WEBSOCKET_FRAME_HEADER *header) {
195 if (!wsc || !header)
196 return WS_ALLOW_ERROR;
197
198 bool is_control = websocket_frame_is_control_opcode(header->opcode);
199
200 // Check state-based restrictions
201 switch (wsc->state) {
202 case WS_STATE_OPEN:
203 // In OPEN state, all frames are allowed
204 return WS_ALLOW_USE;
205
206 case WS_STATE_CLOSING_SERVER:
207 // When server initiated closing, only control frames are allowed
208 if (!is_control) {
209 websocket_debug(wsc, "Non-control frame rejected in CLOSING_SERVER state");
210 return WS_ALLOW_DISCARD;
211 }
212 return WS_ALLOW_USE;
213
214 case WS_STATE_CLOSING_CLIENT:
215 // When client initiated closing, we shouldn't process any further frames
216 // All frames in this state should be silently ignored
217 websocket_debug(wsc, "Frame rejected in CLOSING_CLIENT state (will be silently ignored)");
218 return WS_ALLOW_DISCARD;
219
220 case WS_STATE_CLOSED:
221 // In CLOSED state, no frames should be processed
222 websocket_debug(wsc, "Frame rejected in CLOSED state");
223 return WS_ALLOW_DISCARD;
224
225 case WS_STATE_HANDSHAKE:
226 // In HANDSHAKE state, no WebSocket frames should be processed yet
227 websocket_debug(wsc, "Frame rejected in HANDSHAKE state");
228 return WS_ALLOW_ERROR;
229
230 default:
231 // Unknown state - reject frame
232 websocket_debug(wsc, "Frame rejected in unknown state: %d", wsc->state);
233 return WS_ALLOW_DISCARD;
234 }
235 }
236
237 // Helper function to handle frame header parsing
238 bool websocket_protocol_parse_header_from_buffer(const char *buffer, size_t length,
239 WEBSOCKET_FRAME_HEADER *header) {
240 if (!buffer || !header || length < 2) {
241 websocket_debug(NULL, "We need at least 2 bytes to parse a header: buffer=%p, length=%zu", buffer, length);
242 return false;
243 }
244
245 // Get first byte - contains FIN bit, RSV bits, and opcode
246 unsigned char byte1 = (unsigned char)buffer[0];
247 header->fin = (byte1 & WS_FIN) ? 1 : 0;
248 header->rsv1 = (byte1 & WS_RSV1) ? 1 : 0;
249 header->rsv2 = (byte1 & (WS_RSV1 >> 1)) ? 1 : 0;
250 header->rsv3 = (byte1 & (WS_RSV1 >> 2)) ? 1 : 0;
251 header->opcode = byte1 & 0x0F;
252
253 // Get second byte - contains MASK bit and initial length
254 unsigned char byte2 = (unsigned char)buffer[1];
255 header->mask = (byte2 & WS_MASK) ? 1 : 0;
256 header->len = byte2 & 0x7F;
257
258 // Calculate header size and payload length based on length field
259 header->header_size = 2; // Start with 2 bytes for the basic header
260
261 // Determine payload length
262 if (header->len < 126) {
263 header->payload_length = header->len;
264 }
265 else if (header->len == 126) {
266 // 16-bit length
267 if (length < 4) {
268 websocket_debug(NULL, "We need at least 4 bytes to parse this header: buffer=%p, length=%zu", buffer, length);
269 return false; // Not enough data
270 }
271
272 header->payload_length = ((uint64_t)((unsigned char)buffer[2]) << 8) | ((uint64_t)((unsigned char)buffer[3]));
273 header->header_size += 2;
274 }
275 else if (header->len == 127) {
276 // 64-bit length
277 if (length < 10) {
278 websocket_debug(NULL, "We need at least 10 bytes to parse this header: buffer=%p, length=%zu", buffer, length);
279 return false; // Not enough data
280 }
281
282 header->payload_length =
283 ((uint64_t)((unsigned char)buffer[2]) << 56) |
284 ((uint64_t)((unsigned char)buffer[3]) << 48) |
285 ((uint64_t)((unsigned char)buffer[4]) << 40) |
286 ((uint64_t)((unsigned char)buffer[5]) << 32) |
287 ((uint64_t)((unsigned char)buffer[6]) << 24) |
288 ((uint64_t)((unsigned char)buffer[7]) << 16) |
289 ((uint64_t)((unsigned char)buffer[8]) << 8) |
290 ((uint64_t)((unsigned char)buffer[9]));
291 header->header_size += 8;
292 }
293
294 // Read masking key if frame is masked
295 if (header->mask) {
296 if (length < header->header_size + 4) return false; // Not enough data
297
298 // Copy masking key
299 memcpy(header->mask_key, buffer + header->header_size, 4);
300 header->header_size += 4;
301 } else {
302 // Clear mask key if not masked
303 memset(header->mask_key, 0, 4);
304 }
305
306 header->payload = (void *)&buffer[header->header_size];
307 header->frame_size = header->header_size + header->payload_length;
308
309 return true;
310 }
311
312 // Validate a parsed frame header according to WebSocket protocol rules
313 static bool websocket_protocol_validate_header(
314 WS_CLIENT *wsc, WEBSOCKET_FRAME_HEADER *header,
315 uint64_t payload_length, bool in_fragment_sequence) {
316 if (!wsc || !header)
317 return false;
318
319 // Check RSV bits - must be 0 unless extensions are negotiated
320 if (header->rsv2 || header->rsv3) {
321 websocket_error(wsc, "Invalid frame: RSV2 or RSV3 bits set");
322 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "RSV2 or RSV3 bits set");
323 return false;
324 }
325
326 // RSV1 is only valid if compression is enabled
327 if (header->rsv1 && (!wsc->compression.enabled)) {
328 websocket_error(wsc, "Invalid frame: RSV1 bit set but compression not enabled");
329 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "RSV1 bit set without compression");
330 return false;
331 }
332
333 // For continuation frames in a compressed message, RSV1 must be 0 per RFC 7692
334 // Continuation frames for a compressed message must not have RSV1 set
335 if (header->opcode == WS_OPCODE_CONTINUATION && in_fragment_sequence && header->rsv1) {
336 websocket_error(wsc, "Invalid frame: Continuation frame should not have RSV1 bit set");
337 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "RSV1 bit set on continuation frame");
338 return false;
339 }
340
341 // Check opcode validity
342 switch (header->opcode) {
343 case WS_OPCODE_CONTINUATION:
344 // Continuation frames must be in a fragment sequence
345 if (!in_fragment_sequence) {
346 websocket_error(wsc, "Invalid frame: Continuation frame without initial frame");
347 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "Continuation frame without initial frame");
348 return false;
349 }
350 break;
351
352 case WS_OPCODE_TEXT:
353 case WS_OPCODE_BINARY:
354 // New data frames cannot start inside a fragment sequence
355 if (in_fragment_sequence) {
356 websocket_error(wsc, "Invalid frame: New data frame during fragmented message");
357 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "New data frame during fragmented message");
358 return false;
359 }
360 break;
361
362 case WS_OPCODE_CLOSE:
363 case WS_OPCODE_PING:
364 case WS_OPCODE_PONG:
365 // Control frames must not be fragmented
366 if (!header->fin) {
367 websocket_error(wsc, "Invalid frame: Fragmented control frame");
368 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "Fragmented control frame");
369 return false;
370 }
371
372 // Control frames must have payload ≤ 125 bytes
373 if (payload_length > 125) {
374 websocket_error(wsc, "Invalid frame: Control frame payload too large (%llu bytes)",
375 (unsigned long long)payload_length);
376 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "Control frame payload too large");
377 return false;
378 }
379 break;
380
381 default:
382 // Unknown opcode
383 websocket_error(wsc, "Invalid frame: Unknown opcode: 0x%x", (unsigned int)header->opcode);
384 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "Unknown opcode");
385 return false;
386 }
387
388 // Validate payload length against limits
389 if (payload_length > (uint64_t)WS_MAX_INCOMING_FRAME_SIZE) {
390 websocket_error(wsc, "Invalid frame: Payload too large (%llu bytes)",
391 (unsigned long long)payload_length);
392 websocket_protocol_exception(wsc, WS_CLOSE_MESSAGE_TOO_BIG, "Frame payload too large");
393 return false;
394 }
395
396 // All checks passed
397 return true;
398 }
399
400 // Process a control frame directly without creating a message structure
401 static bool websocket_protocol_process_control_message(
402 WS_CLIENT *wsc, WEBSOCKET_OPCODE opcode,
403 char *payload, size_t payload_length,
404 bool is_masked, const unsigned char *mask_key) {
405 websocket_debug(wsc, "Processing control frame opcode=0x%x, payload_length=%zu, is_masked=%d, connection state=%d",
406 opcode, payload_length, is_masked, wsc->state);
407
408 // If payload is masked, unmask it first
409 if (is_masked && mask_key && payload && payload_length > 0)
410 websocket_unmask(payload, payload, payload_length, mask_key);
411
412 switch (opcode) {
413 case WS_OPCODE_CLOSE: {
414 worker_is_busy(WORKERS_WEBSOCKET_MSG_CLOSE);
415
416 uint16_t code = WS_CLOSE_NORMAL;
417 char reason[124];
418 reason[0] = '\0'; // Initialize reason string
419
420 // Check for malformed CLOSE frame payload
421 if (payload && payload_length == 1) {
422 websocket_error(wsc, "Invalid CLOSE frame payload length: 1 byte (must be 0 or >= 2 bytes)");
423
424 // This is a protocol violation - handle it consistently through the protocol exception mechanism
425 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "Invalid payload length");
426
427 // Return true since we handled the message
428 return true;
429 }
430 // Parse close code if present
431 else if (payload && payload_length >= 2) {
432 code = ((uint16_t)((unsigned char)payload[0]) << 8) |
433 ((uint16_t)((unsigned char)payload[1]));
434
435 // Validate close code
436 if (!websocket_validate_close_code(code)) {
437 websocket_error(wsc, "Invalid close code: %u", code);
438
439 // This is a protocol violation - handle it through the protocol exception mechanism
440 // This will send a close frame with 1002 (protocol error) and set the appropriate state
441 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "Invalid close code");
442
443 // Return true since we handled the message
444 return true;
445 }
446 // Check UTF-8 validity of the reason text
447 else if (payload_length > 2) {
448 // RFC 6455 requires all control frame payloads (including close reasons) to be valid UTF-8
449 if (!websocket_validate_utf8(payload + 2, payload_length - 2)) {
450 websocket_error(wsc, "Invalid UTF-8 in close frame reason");
451 code = WS_CLOSE_INVALID_PAYLOAD; // 1007 - Invalid frame payload data
452 strncpyz(reason, "Invalid UTF-8 in close reason", sizeof(reason) - 1);
453 }
454 else {
455 // Valid UTF-8, copy the reason
456 strncpyz(reason, payload + 2, MIN(payload_length - 2, sizeof(reason) - 1));
457 }
458 }
459 }
460
461 // Different handling based on connection state
462 if (wsc->state == WS_STATE_OPEN) {
463 // This is the initial CLOSE from client - respond with our own CLOSE
464 websocket_debug(wsc, "Received initial CLOSE frame from client, responding with CLOSE");
465
466 // Send close frame in response
467 websocket_protocol_send_close(wsc, code, reason);
468
469 wsc->state = WS_STATE_CLOSING_CLIENT;
470 wsc->flush_and_remove_client = true;
471
472 // IMPORTANT: do not call websocket_write_data() here
473 // because it prevents wsc->flush_and_remove_client from removing the client!
474 }
475 else if (wsc->state == WS_STATE_CLOSING_SERVER) {
476 // We initiated the closing handshake and now received client's response
477 // This completes the closing handshake
478 websocket_debug(wsc, "Closing handshake complete - received client's CLOSE response to our close");
479
480 // Ensure immediate removal from thread/poll
481 if (wsc->wth) {
482 websocket_info(wsc, "Closing TCP connection after completed handshake (server initiated)");
483 websocket_thread_send_command(wsc->wth, WEBSOCKET_THREAD_CMD_REMOVE_CLIENT, wsc->id);
484 }
485
486 // The RFC requires us to close the TCP connection immediately now
487 wsc->state = WS_STATE_CLOSED;
488 }
489 else if (wsc->state == WS_STATE_CLOSING_CLIENT) {
490 // Client already sent a CLOSE, and we responded, but they sent another CLOSE
491 // This is not strictly according to protocol, but we'll handle it gracefully
492 websocket_debug(wsc, "Received another CLOSE frame while in client-initiated closing state");
493
494 // Remove client from thread
495 if (wsc->wth) {
496 websocket_info(wsc, "Closing TCP connection (duplicate close from client)");
497 websocket_thread_send_command(wsc->wth, WEBSOCKET_THREAD_CMD_REMOVE_CLIENT, wsc->id);
498 }
499
500 // Move to CLOSED state and close the connection
501 wsc->state = WS_STATE_CLOSED;
502 }
503 else {
504 // Already in CLOSED state - ignore
505 websocket_debug(wsc, "Ignoring CLOSE frame - connection already in CLOSED state");
506 }
507 return true;
508 }
509
510 case WS_OPCODE_PING:
511 worker_is_busy(WORKERS_WEBSOCKET_MSG_PING);
512
513 // If we're in CLOSING or CLOSED state, decide how to handle PING based on state
514 if (wsc->state == WS_STATE_CLOSING_SERVER || wsc->state == WS_STATE_CLOSING_CLIENT || wsc->state == WS_STATE_CLOSED) {
515 // When we initiated closing, we should still respond to control frames
516 if (wsc->state == WS_STATE_CLOSING_SERVER) {
517 websocket_debug(wsc, "Received PING during server-initiated closing, responding with PONG");
518 return websocket_protocol_send_pong(wsc, payload, payload_length) > 0;
519 }
520
521 // For client-initiated closing or CLOSED, we should ignore control frames
522 websocket_debug(wsc, "Ignoring PING frame - connection in %s state",
523 wsc->state == WS_STATE_CLOSING_CLIENT ? "client closing" : "closed");
524 return true; // Successfully processed (by ignoring)
525 }
526
527 // Ping frame - respond with pong
528 websocket_debug(wsc, "Received PING frame with %zu bytes, responding with PONG", payload_length);
529
530 // Send pong with the same payload
531 return websocket_protocol_send_pong(wsc, payload, payload_length) > 0;
532
533 case WS_OPCODE_PONG:
534 worker_is_busy(WORKERS_WEBSOCKET_MSG_PONG);
535
536 // If we're in CLOSING or CLOSED state, decide how to handle PONG
537 if (wsc->state == WS_STATE_CLOSING_SERVER || wsc->state == WS_STATE_CLOSING_CLIENT || wsc->state == WS_STATE_CLOSED) {
538 // We can safely ignore PONG frames in any closing or closed state
539 websocket_debug(wsc, "Ignoring PONG frame - connection in %s state",
540 wsc->state == WS_STATE_CLOSING_SERVER ? "server closing" :
541 wsc->state == WS_STATE_CLOSING_CLIENT ? "client closing" : "closed");
542 return true; // Successfully processed (by ignoring)
543 }
544
545 // Pong frame - update last activity time
546 websocket_debug(wsc, "Received PONG frame, updating last activity time");
547 wsc->last_activity_t = now_monotonic_sec();
548 return true;
549
550 default:
551 worker_is_busy(WORKERS_WEBSOCKET_MSG_INVALID);
552 break;
553 }
554
555 websocket_error(wsc, "Unknown control opcode: %d", opcode);
556 return false;
557 }
558
559 // Parse a frame from a buffer and append it to the current message if applicable.
560 // Returns one of the following:
561 // - WS_FRAME_ERROR: An error occurred, connection should be closed
562 // - WS_FRAME_NEED_MORE_DATA: More data is needed to complete the frame
563 // - WS_FRAME_COMPLETE: Frame was successfully parsed and handled, but is not a complete message yet
564 // - WS_FRAME_MESSAGE_READY: Message is ready for processing
565 static WEBSOCKET_FRAME_RESULT
566 websocket_protocol_consume_frame(WS_CLIENT *wsc, char *data, size_t length, ssize_t *bytes_processed) {
567 if (!wsc || !data || !length || !bytes_processed)
568 return WS_FRAME_ERROR;
569
570 size_t bytes = *bytes_processed = 0;
571
572 // Local variables for frame processing
573 WEBSOCKET_FRAME_HEADER header = { 0 };
574
575 // Step 1: Parse the frame header
576 if (!websocket_protocol_parse_header_from_buffer(data, length, &header)) {
577 websocket_debug(wsc, "Not enough data to parse a complete header: bytes available = %zu",
578 length);
579 return WS_FRAME_NEED_MORE_DATA;
580 }
581
582 if(header.frame_size > wsc->max_message_size)
583 wsc->max_message_size = header.frame_size;
584
585 // Check if we have enough data for the complete frame (header + payload)
586 // If not, don't consume any bytes and wait for more data
587 if (bytes + header.frame_size > length) {
588
589 // let the circular buffer know how much data we need
590 wsc->next_frame_size = header.frame_size;
591
592 worker_is_busy(WORKERS_WEBSOCKET_INCOMPLETE_FRAME);
593 websocket_debug(wsc,
594 "RX FRAME INCOMPLETE (need %zu bytes more): OPCODE=0x%x, FIN=%s, RSV1=%d, RSV2=%d, RSV3=%d, MASK=%s, LEN=%d, "
595 "PAYLOAD_LEN=%zu, HEADER_SIZE=%zu, FRAME_SIZE=%zu, MASK=%02x%02x%02x%02x, bytes available = %zu",
596 (bytes + header.frame_size) - length,
597 header.opcode, header.fin ? "True" : "False", header.rsv1, header.rsv2, header.rsv3,
598 header.mask ? "True" : "False", header.len,
599 header.payload_length, header.header_size, header.frame_size,
600 header.mask_key[0], header.mask_key[1], header.mask_key[2], header.mask_key[3],
601 length);
602
603 return WS_FRAME_NEED_MORE_DATA;
604 }
605 wsc->next_frame_size = 0; // reset it, since we have enough data now
606
607 worker_is_busy(WORKERS_WEBSOCKET_COMPLETE_FRAME);
608
609 // Log detailed header information for debugging
610 websocket_debug(wsc,
611 "RX FRAME: OPCODE=0x%x, FIN=%s, RSV1=%d, RSV2=%d, RSV3=%d, MASK=%s, LEN=%d, "
612 "PAYLOAD_LEN=%zu, HEADER_SIZE=%zu, FRAME_SIZE=%zu, MASK=%02x%02x%02x%02x",
613 header.opcode, header.fin ? "True" : "False", header.rsv1, header.rsv2, header.rsv3,
614 header.mask ? "True" : "False", header.len,
615 header.payload_length, header.header_size, header.frame_size,
616 header.mask_key[0], header.mask_key[1], header.mask_key[2], header.mask_key[3]);
617
618 // Check for invalid RSV bits
619 if (header.rsv2 || header.rsv3 || (header.rsv1 && !wsc->compression.enabled)) {
620 const char *reason = header.rsv2 ? "RSV2 bit set" :
621 (header.rsv3 ? "RSV3 bit set" : "RSV1 bit set without compression");
622
623 // Handle protocol exception in a centralized way
624 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, reason);
625 return WS_FRAME_ERROR;
626 }
627
628 // Check if this frame is allowed in the current connection state
629 switch(websocket_is_frame_allowed(wsc, &header)) {
630 case WS_ALLOW_USE:
631 break;
632
633 case WS_ALLOW_DISCARD:
634 // we have already logged in websocket_is_frame_allowed()
635 return WS_FRAME_COMPLETE;
636
637 default:
638 case WS_ALLOW_ERROR: {
639 char reason[128];
640
641 snprintf(reason, sizeof(reason), "Frame not allowed in %s state",
642 wsc->state == WS_STATE_CLOSING_SERVER ? "server closing" :
643 wsc->state == WS_STATE_CLOSING_CLIENT ? "client closing" :
644 "current");
645
646 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, reason);
647 return WS_FRAME_ERROR;
648 }
649 }
650
651 // Step 2: Validate the frame header
652 if (!websocket_protocol_validate_header(wsc, &header, header.payload_length, !wsc->message_complete)) {
653 // Invalid frame - websocket_protocol_validate_header sent a close frame
654 // but we should handle the connection closing consistently
655 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "Invalid frame header");
656 return WS_FRAME_ERROR;
657 }
658
659 // Advance past the header
660 bytes += header.header_size;
661
662 if (websocket_frame_is_control_opcode(header.opcode)) {
663 // Handle control frames (PING, PONG, CLOSE) directly
664 websocket_debug(wsc, "Handling control frame: opcode=0x%x, payload_length=%zu",
665 (unsigned)header.opcode, header.payload_length);
666
667 // Process the control frame with optional payload
668 char *payload = (header.payload_length > 0) ? (data + bytes) : NULL;
669
670 // Process control message directly without creating a message object
671 if (!websocket_protocol_process_control_message(
672 wsc, (WEBSOCKET_OPCODE)header.opcode,
673 payload, header.payload_length,
674 header.mask ? true : false,
675 header.mask_key)) {
676 websocket_error(wsc, "Failed to process control frame");
677 return WS_FRAME_ERROR;
678 }
679
680 // Update bytes processed
681 if (header.payload_length > 0)
682 bytes += (size_t)header.payload_length;
683
684 *bytes_processed = bytes;
685
686 return WS_FRAME_COMPLETE; // Return COMPLETE so we continue processing other frames
687 }
688
689 // For non-control frames (text, binary, continuation), check if connection is closing
690 if (wsc->state == WS_STATE_CLOSING_SERVER || wsc->state == WS_STATE_CLOSING_CLIENT || wsc->state == WS_STATE_CLOSED) {
691 // Per RFC 6455, once the closing handshake is started, we should ignore non-control frames
692 websocket_debug(wsc, "Ignoring non-control frame (opcode=0x%x) - connection in %s state",
693 header.opcode,
694 wsc->state == WS_STATE_CLOSING_SERVER ? "server closing" :
695 wsc->state == WS_STATE_CLOSING_CLIENT ? "client closing" : "closed");
696
697 // Consume the frame bytes but don't process it
698 bytes += header.header_size + header.payload_length;
699 *bytes_processed = bytes;
700
701 return WS_FRAME_COMPLETE;
702 }
703
704 // Step 3: Handle the frame based on its opcode
705 if (header.opcode == WS_OPCODE_CONTINUATION) {
706 // This is a continuation frame - need an existing message in progress
707 if (wsc->message_complete) {
708 websocket_error(wsc, "Received continuation frame with no message in progress");
709 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "Continuation frame without initial frame");
710 return WS_FRAME_ERROR;
711 }
712
713 // If it's a zero-length frame, we don't need to append any data
714 if (header.payload_length == 0) {
715 // For zero-length non-final frames, just update and continue
716 if (!header.fin) {
717 // Non-final zero-length frame
718 websocket_debug(wsc, "Zero-length non-final continuation frame");
719 *bytes_processed = bytes;
720 wsc->frame_id++;
721 return WS_FRAME_COMPLETE;
722 }
723
724 // Final zero-length frame - mark message as complete
725 wsc->message_complete = true;
726 *bytes_processed = bytes;
727 return WS_FRAME_MESSAGE_READY;
728 }
729
730 // The message buffer length is updated as we append frame data
731 }
732 else {
733 if(!header.payload_length) {
734 websocket_debug(wsc, "Received data frame with zero-length payload (fin=%d)", header.fin);
735
736 // Initialize the client's message state for a new message
737 websocket_client_message_reset(wsc);
738 wsc->opcode = (WEBSOCKET_OPCODE)header.opcode;
739 wsc->is_compressed = header.rsv1 ? true : false;
740
741 // The most important part: for fragmented messages (non-final frames),
742 // we must set message_complete to false, regardless of payload length
743 wsc->message_complete = header.fin;
744
745 // Buffer length is already 0 after reset
746 wsc->frame_id = 0;
747
748 // Check if this is a final frame
749 if (header.fin) {
750 // Final frame - message is already marked as complete
751 *bytes_processed = bytes;
752 return WS_FRAME_MESSAGE_READY;
753 } else {
754 // Non-final frame - continue to next frame
755 *bytes_processed = bytes;
756 wsc->frame_id++;
757 return WS_FRAME_COMPLETE;
758 }
759 }
760
761 // This is a new data frame (TEXT or BINARY)
762 // If we have an existing message in progress, it's an error
763 if (!wsc->message_complete) {
764 websocket_error(wsc, "Received new data frame while another message is in progress");
765 websocket_protocol_exception(wsc, WS_CLOSE_PROTOCOL_ERROR, "New data frame during fragmented message");
766 return WS_FRAME_ERROR;
767 }
768
769 // Initialize the client's message state for a new message
770 websocket_client_message_reset(wsc);
771 wsc->opcode = (WEBSOCKET_OPCODE)header.opcode;
772 wsc->is_compressed = header.rsv1 ? true : false;
773
774 // For fragmented messages (non-final frames), we must set message_complete to false
775 // This needs to be consistently done for both empty and non-empty frames
776 wsc->message_complete = header.fin;
777
778 // Buffer length will be updated when we append the payload data
779 wsc->frame_id = 0;
780 }
781
782 // Step 4: Append payload data to the message
783
784 if (header.payload_length > 0) {
785 char *src = header.payload;
786
787 if (header.mask) {
788 // Payload is masked - need to unmask it first
789 websocket_debug(wsc, "Unmasking and appending payload data at position %zu (key=%02x%02x%02x%02x)",
790 wsb_length(&wsc->payload),
791 header.mask_key[0], header.mask_key[1], header.mask_key[2], header.mask_key[3]);
792
793 // Use the new helper function to unmask and append the data in one step
794 wsb_unmask_and_append(&wsc->payload, src, header.payload_length, header.mask_key);
795 }
796 else {
797 // Payload is not masked - can directly append
798 websocket_debug(wsc, "Appending unmasked payload data at position %zu", wsb_length(&wsc->payload));
799
800 // Append unmasked data directly
801 wsb_append(&wsc->payload, src, header.payload_length);
802 }
803
804 // Dump payload for debugging
805 size_t buffer_length = wsb_length(&wsc->payload);
806 websocket_dump_debug(wsc,
807 wsb_data(&wsc->payload) + (buffer_length - header.payload_length),
808 header.payload_length,
809 "RX FRAME PAYLOAD");
810 }
811
812 // Step 5: At this point, we know we've processed a complete frame
813 wsc->frame_id++;
814
815 bytes += header.payload_length;
816 *bytes_processed = bytes;
817
818 // If this is a final frame, mark the message as complete
819 if (header.fin)
820 return WS_FRAME_MESSAGE_READY;
821
822 // Non-final frame, message is incomplete - move to next frame
823 return WS_FRAME_COMPLETE;
824 }
825
826 // Process incoming data from the WebSocket client
827 // This function's job is to:
828 // 1. Consume frames from the input buffer
829 // 2. Build messages
830 // 3. Process complete messages
831 ssize_t websocket_protocol_got_data(WS_CLIENT *wsc, char *data, size_t length) {
832 if (!wsc || !data || !length)
833 return -1;
834
835 // Keep processing frames until we can't process any more
836 size_t processed = 0;
837 while (processed < length) {
838 // Try to consume one complete frame
839 ssize_t consumed = 0;
840 WEBSOCKET_FRAME_RESULT result = websocket_protocol_consume_frame(wsc, data + processed, length - processed, &consumed);
841
842 websocket_debug(wsc, "Frame processing result: %d, processed: %zu/%zu", result, consumed, length);
843
844 // Safety check to ensure we always move forward in the buffer
845 if (consumed == 0 && result != WS_FRAME_NEED_MORE_DATA && result != WS_FRAME_ERROR) {
846 // If we're processing a frame but not consuming bytes, we might be stuck
847 websocket_error(wsc, "Protocol processing stalled - consumed 0 bytes but not waiting for more data (%d)", (int)result);
848 return -(ssize_t)processed;
849 }
850
851 switch (result) {
852 case WS_FRAME_ERROR:
853 // Error occurred during frame processing
854 websocket_error(wsc, "Error processing WebSocket frame");
855 return processed ? -(ssize_t)processed : -1;
856
857 case WS_FRAME_NEED_MORE_DATA:
858 // Need more data to complete the current frame
859 websocket_debug(wsc, "Need more data to complete the current frame");
860 return (ssize_t)processed;
861
862 case WS_FRAME_COMPLETE:
863 // Frame was processed successfully, but more frames are needed for a complete message
864 websocket_debug(wsc, "Frame complete, but message not yet complete");
865 processed += consumed;
866 continue;
867
868 case WS_FRAME_MESSAGE_READY:
869 worker_is_busy(WORKERS_WEBSOCKET_MESSAGE);
870 processed += consumed;
871
872 wsc->message_complete = true;
873 if (!websocket_client_process_message(wsc))
874 websocket_error(wsc, "Failed to process completed message");
875
876 continue;
877 }
878 }
879
880 return (ssize_t)processed;
881 }
882