Conversation
src/AsyncWebSocket.cpp
Outdated
| #define SATE_FRAME_START 0 | ||
| #define SATE_FRAME_MASK 1 | ||
| #define SATE_FRAME_DATA 2 |
There was a problem hiding this comment.
Using _pstate as suggested by @willmmiles here to keep track of the reassembling for the mask
| uint8_t *data = (uint8_t *)pbuf; | ||
|
|
||
| while (plen > 0) { | ||
| async_ws_log_v("WS[%" PRIu32 "]: _onData: plen=%" PRIu32 ", _pstate=%" PRIu8 ", _status=%" PRIu8, _clientId, plen, _pstate, static_cast<uint8_t>(_status)); |
There was a problem hiding this comment.
kept all logs async_ws_log_v to still be able to easily debug
b16f022 to
ad9be24
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes a critical bug in WebSocket frame defragmentation that was introduced in PR #353 when addressing Safari's fragmented pong data. The fix refactors the state machine for handling WebSocket frames, particularly improving mask byte accumulation across packet boundaries and restoring proper frame reassembly.
Changes:
- Introduces explicit state constants for the WebSocket parsing state machine
- Refactors mask byte reading to properly handle Safari's behavior of splitting the 4-byte mask across multiple TCP packets
- Fixes frame defragmentation logic that was broken since PR #353
- Adds comprehensive verbose logging to aid debugging
- Updates the WebSocket example to demonstrate both complete and fragmented frame handling
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/AsyncWebSocket.cpp | Core fix: adds state constants, refactors mask reading logic to use _pinfo.masked as a counter (1-5), improves frame defragmentation handling, adds verbose logging, and enhances error handling |
| examples/WebSocket/WebSocket.ino | Updates example to demonstrate fragmented frame handling with detailed logging and increases polling interval from 100ms to 500ms |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/AsyncWebSocket.cpp
Outdated
| // _pinfo.masked is 1 if we need to start reading mask bytes | ||
| // _pinfo.masked is 2, 3, or 4 if we have partially read the mask | ||
| // _pinfo.masked is 5 if the mask is complete | ||
| while (_pinfo.masked && _pstate <= SATE_FRAME_MASK && _pinfo.masked < 5) { |
There was a problem hiding this comment.
reassemble de mask using _pinfo.masked to track byte count but resets _pinfo.masked to 1 at the end for backward compat'
| _pinfo.index = 0; | ||
| } else { | ||
| // Wait for more data | ||
| // async_ws_log_w("WS[%" PRIu32 "]: waiting for more mask data: read=%zu/4", _clientId, mask_offset); | ||
| _pinfo.index = mask_offset; // Save progress | ||
| _pstate = 1; | ||
| return; | ||
| _pinfo.len = 0; |
There was a problem hiding this comment.
To go into the disconnect use case below
| } | ||
|
|
||
| const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen); | ||
| const auto datalast = datalen ? data[datalen] : 0; |
There was a problem hiding this comment.
I really hate this thing... users have to add \0 and overflow the data array (see WS examples). That's not normal. I don't know if there is a way to refactor that in a backward compatible way one day.
| _status = WS_DISCONNECTING; | ||
| if (_client) { | ||
| _client->ackLater(); | ||
| } | ||
| _queueControl(WS_DISCONNECT, data, datalen); |
There was a problem hiding this comment.
if we receive an invalid frame, nothing were done so the connection would be left in an unknown state, including frame defragmentation... So let's force a disconnect to restore the connection.
it could also be a rogue client...
ad9be24 to
5d3e467
Compare
Fix #382
WebSocket defragmentation was broken after a fix for Safari in PR #353
This PR refactors several parts:
How to test this PR:
With
WebSocket.inoexample, compile in verbose mode.Complete frame
Fragmentation test:
Safari fragmented packet test
Hit multiple time CMD+R on Safari => look for
waiting for more mask data: read=0/4