@@ -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);
0 commit comments