Skip to content
This repository was archived by the owner on Jan 20, 2025. It is now read-only.

Commit c490689

Browse files
committed
Handle edge-case in which we receive a partial WS header
1 parent 2f78426 commit c490689

2 files changed

Lines changed: 99 additions & 16 deletions

File tree

src/AsyncWebSocket.cpp

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
484484
_clientId = _server->_getNextId();
485485
_status = WS_CONNECTED;
486486
_pstate = 0;
487+
_partialHeader = nullptr;
488+
_partialHeaderLen = 0;
487489
_lastMessageTime = millis();
488490
_keepAlivePeriod = 0;
489491
_client->setRxTimeout(0);
@@ -622,30 +624,107 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
622624
_lastMessageTime = millis();
623625
uint8_t *data = (uint8_t*)pbuf;
624626
while(plen > 0){
625-
if(!_pstate){
626-
const uint8_t *fdata = data;
627+
if(!_pstate) {
628+
ssize_t dataPayloadOffset = 0;
629+
const uint8_t *headerBuf = data;
630+
631+
// plen is backed up to initialPlen because, in case we receive a partial header, we would like to undo all of our
632+
// parsing and copy all of what we have of the header into a buffer for later use.
633+
// plen is modified during the parsing attempt, so if we don't back it up we won't know how much we need to copy.
634+
// partialHeaderLen is also backed up for the same reason.
635+
size_t initialPlen = plen;
636+
size_t partialHeaderLen = 0;
637+
638+
if (_partialHeaderLen > 0) {
639+
// We previously received a truncated header. Recover it by doing the following:
640+
// - Copy the new header chunk into the previous partial header, filling the buffer. It is allocated as a
641+
// buffer in the stack, as a class field.
642+
// - Change *headerBuf to point to said buffer
643+
// - Update the length counters so that:
644+
// - The initialPlen and plen, which refer to the length of the remaining packet data, also accounts for the
645+
// previously received truncated header
646+
// - The dataPayloadOffset, which is the offset after the header at which the payload begins, so that it
647+
// refers to a point potentially before the beginning of the buffer. As we parse the header we increment it,
648+
// and we can pretty much guarantee it will go back to being positive unless there is a major bug.
649+
// - The class _partialHeaderLen is back to zero since we took ownership of the contained data.
650+
memcpy(_partialHeader + _partialHeaderLen, data,
651+
std::min(plen, (size_t) WS_MAX_HEADER_LEN - _partialHeaderLen));
652+
headerBuf = _partialHeader;
653+
initialPlen += _partialHeaderLen;
654+
plen += _partialHeaderLen;
655+
dataPayloadOffset -= _partialHeaderLen;
656+
partialHeaderLen = _partialHeaderLen;
657+
658+
_partialHeaderLen = 0;
659+
}
660+
661+
// The following series of gotos could have been a try-catch but we are likely being built with -fno-exceptions
662+
if (plen < 2)
663+
goto _exceptionHandleFailPartialHeader;
664+
627665
_pinfo.index = 0;
628-
_pinfo.final = (fdata[0] & 0x80) != 0;
629-
_pinfo.opcode = fdata[0] & 0x0F;
630-
_pinfo.masked = (fdata[1] & 0x80) != 0;
631-
_pinfo.len = fdata[1] & 0x7F;
632-
data += 2;
666+
_pinfo.final = (headerBuf[0] & 0x80) != 0;
667+
_pinfo.opcode = headerBuf[0] & 0x0F;
668+
_pinfo.masked = (headerBuf[1] & 0x80) != 0;
669+
_pinfo.len = headerBuf[1] & 0x7F;
670+
dataPayloadOffset += 2;
633671
plen -= 2;
634-
if(_pinfo.len == 126){
635-
_pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8;
636-
data += 2;
672+
673+
if (_pinfo.len == 126) {
674+
if (plen < 2)
675+
goto _exceptionHandleFailPartialHeader;
676+
677+
_pinfo.len = headerBuf[3] | (uint16_t)(headerBuf[2]) << 8;
678+
dataPayloadOffset += 2;
637679
plen -= 2;
638-
} else if(_pinfo.len == 127){
639-
_pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
640-
data += 8;
680+
} else if (_pinfo.len == 127) {
681+
if (plen < 8)
682+
goto _exceptionHandleFailPartialHeader;
683+
684+
_pinfo.len = headerBuf[9] | (uint16_t)(headerBuf[8]) << 8 | (uint32_t)(headerBuf[7]) << 16 |
685+
(uint32_t)(headerBuf[6]) << 24 | (uint64_t)(headerBuf[5]) << 32 | (uint64_t)(headerBuf[4]) << 40 |
686+
(uint64_t)(headerBuf[3]) << 48 | (uint64_t)(headerBuf[2]) << 56;
687+
dataPayloadOffset += 8;
641688
plen -= 8;
642689
}
643690

644-
if(_pinfo.masked){
645-
memcpy(_pinfo.mask, data, 4);
646-
data += 4;
691+
if (_pinfo.masked) {
692+
if (plen < 4)
693+
goto _exceptionHandleFailPartialHeader;
694+
695+
memcpy(_pinfo.mask, headerBuf + dataPayloadOffset + partialHeaderLen, 4);
696+
dataPayloadOffset += 4;
647697
plen -= 4;
648698
}
699+
700+
// Yes I know the control flow here isn't 100% legible but we must support -fno-exceptions.
701+
// If we got to this point it means we did NOT receive a truncated header, therefore we can skip the exception
702+
// handling.
703+
// Control flow resumes after the following block.
704+
goto _headerParsingSuccessful;
705+
706+
// We DID receive a truncated header:
707+
// - We copy it to our buffer allocated in the stack and set the _partialHeaderLen
708+
// - We return early
709+
// This will trigger the partial recovery at the next call of this method, once more data is received and we have
710+
// a full header.
711+
_exceptionHandleFailPartialHeader:
712+
{
713+
if (initialPlen <= WS_MAX_HEADER_LEN) {
714+
// If initialPlen > WS_MAX_HEADER_LEN there must be something wrong with this code. It should never happen but
715+
// but it's better safe than sorry.
716+
memcpy(_partialHeader, headerBuf, initialPlen * sizeof(uint8_t));
717+
_partialHeaderLen = initialPlen;
718+
} else {
719+
DEBUGF("[AsyncWebSocketClient::_onData] initialPlen (= %d) > WS_MAX_HEADER_LEN (= %d)\n", initialPlen,
720+
WS_MAX_HEADER_LEN);
721+
}
722+
return;
723+
}
724+
725+
_headerParsingSuccessful:
726+
727+
data += dataPayloadOffset;
649728
}
650729

651730
const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen);

src/AsyncWebSocket.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
#define DEFAULT_MAX_WS_CLIENTS 4
4747
#endif
4848

49+
#define WS_MAX_HEADER_LEN 16
50+
4951
class AsyncWebSocket;
5052
class AsyncWebSocketResponse;
5153
class AsyncWebSocketClient;
@@ -166,6 +168,8 @@ class AsyncWebSocketClient {
166168

167169
uint8_t _pstate;
168170
AwsFrameInfo _pinfo;
171+
uint8_t _partialHeader[WS_MAX_HEADER_LEN];
172+
uint8_t _partialHeaderLen;
169173

170174
uint32_t _lastMessageTime;
171175
uint32_t _keepAlivePeriod;

0 commit comments

Comments
 (0)