Skip to content

Commit 4a9f2d8

Browse files
committed
Merge remote-tracking branch 'depau/partial-header'
2 parents 08556de + ee4495a commit 4a9f2d8

File tree

2 files changed

+98
-16
lines changed

2 files changed

+98
-16
lines changed

src/AsyncWebSocket.cpp

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
473473
_clientId = _server->_getNextId();
474474
_status = WS_CONNECTED;
475475
_pstate = 0;
476+
_partialHeaderLen = 0;
476477
_lastMessageTime = millis();
477478
_keepAlivePeriod = 0;
478479
_client->setRxTimeout(0);
@@ -616,30 +617,107 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
616617
_lastMessageTime = millis();
617618
uint8_t *data = (uint8_t*)pbuf;
618619
while(plen > 0){
619-
if(!_pstate){
620-
const uint8_t *fdata = data;
620+
if(!_pstate) {
621+
ssize_t dataPayloadOffset = 0;
622+
const uint8_t *headerBuf = data;
623+
624+
// plen is backed up to initialPlen because, in case we receive a partial header, we would like to undo all of our
625+
// parsing and copy all of what we have of the header into a buffer for later use.
626+
// 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.
627+
// partialHeaderLen is also backed up for the same reason.
628+
size_t initialPlen = plen;
629+
size_t partialHeaderLen = 0;
630+
631+
if (_partialHeaderLen > 0) {
632+
// We previously received a truncated header. Recover it by doing the following:
633+
// - Copy the new header chunk into the previous partial header, filling the buffer. It is allocated as a
634+
// buffer in a class field.
635+
// - Change *headerBuf to point to said buffer
636+
// - Update the length counters so that:
637+
// - The initialPlen and plen, which refer to the length of the remaining packet data, also accounts for the
638+
// previously received truncated header
639+
// - The dataPayloadOffset, which is the offset after the header at which the payload begins, so that it
640+
// refers to a point potentially before the beginning of the buffer. As we parse the header we increment it,
641+
// and we can pretty much guarantee it will go back to being positive unless there is a major bug.
642+
// - The class _partialHeaderLen is back to zero since we took ownership of the contained data.
643+
memcpy(_partialHeader + _partialHeaderLen, data,
644+
std::min(plen, (size_t) WS_MAX_HEADER_LEN - _partialHeaderLen));
645+
headerBuf = _partialHeader;
646+
initialPlen += _partialHeaderLen;
647+
plen += _partialHeaderLen;
648+
dataPayloadOffset -= _partialHeaderLen;
649+
partialHeaderLen = _partialHeaderLen;
650+
651+
_partialHeaderLen = 0;
652+
}
653+
654+
// The following series of gotos could have been a try-catch but we are likely being built with -fno-exceptions
655+
if (plen < 2)
656+
goto _exceptionHandleFailPartialHeader;
657+
621658
_pinfo.index = 0;
622-
_pinfo.final = (fdata[0] & 0x80) != 0;
623-
_pinfo.opcode = fdata[0] & 0x0F;
624-
_pinfo.masked = (fdata[1] & 0x80) != 0;
625-
_pinfo.len = fdata[1] & 0x7F;
626-
data += 2;
659+
_pinfo.final = (headerBuf[0] & 0x80) != 0;
660+
_pinfo.opcode = headerBuf[0] & 0x0F;
661+
_pinfo.masked = (headerBuf[1] & 0x80) != 0;
662+
_pinfo.len = headerBuf[1] & 0x7F;
663+
dataPayloadOffset += 2;
627664
plen -= 2;
628-
if(_pinfo.len == 126){
629-
_pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8;
630-
data += 2;
665+
666+
if (_pinfo.len == 126) {
667+
if (plen < 2)
668+
goto _exceptionHandleFailPartialHeader;
669+
670+
_pinfo.len = headerBuf[3] | (uint16_t)(headerBuf[2]) << 8;
671+
dataPayloadOffset += 2;
631672
plen -= 2;
632-
} else if(_pinfo.len == 127){
633-
_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;
634-
data += 8;
673+
} else if (_pinfo.len == 127) {
674+
if (plen < 8)
675+
goto _exceptionHandleFailPartialHeader;
676+
677+
_pinfo.len = headerBuf[9] | (uint16_t)(headerBuf[8]) << 8 | (uint32_t)(headerBuf[7]) << 16 |
678+
(uint32_t)(headerBuf[6]) << 24 | (uint64_t)(headerBuf[5]) << 32 | (uint64_t)(headerBuf[4]) << 40 |
679+
(uint64_t)(headerBuf[3]) << 48 | (uint64_t)(headerBuf[2]) << 56;
680+
dataPayloadOffset += 8;
635681
plen -= 8;
636682
}
637683

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

645723
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)