@@ -473,6 +473,8 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
473473 _clientId = _server->_getNextId ();
474474 _status = WS_CONNECTED;
475475 _pstate = 0 ;
476+ _partialHeader = nullptr ;
477+ _partialHeaderLen = 0 ;
476478 _lastMessageTime = millis ();
477479 _keepAlivePeriod = 0 ;
478480 _client->setRxTimeout (0 );
@@ -611,30 +613,107 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
611613 _lastMessageTime = millis ();
612614 uint8_t *data = (uint8_t *)pbuf;
613615 while (plen > 0 ){
614- if (!_pstate){
615- const uint8_t *fdata = data;
616+ if (!_pstate) {
617+ ssize_t dataPayloadOffset = 0 ;
618+ const uint8_t *headerBuf = data;
619+
620+ // plen is backed up to initialPlen because, in case we receive a partial header, we would like to undo all of our
621+ // parsing and copy all of what we have of the header into a buffer for later use.
622+ // 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.
623+ // partialHeaderLen is also backed up for the same reason.
624+ size_t initialPlen = plen;
625+ size_t partialHeaderLen = 0 ;
626+
627+ if (_partialHeaderLen > 0 ) {
628+ // We previously received a truncated header. Recover it by doing the following:
629+ // - Copy the new header chunk into the previous partial header, filling the buffer. It is allocated as a
630+ // buffer in a class field.
631+ // - Change *headerBuf to point to said buffer
632+ // - Update the length counters so that:
633+ // - The initialPlen and plen, which refer to the length of the remaining packet data, also accounts for the
634+ // previously received truncated header
635+ // - The dataPayloadOffset, which is the offset after the header at which the payload begins, so that it
636+ // refers to a point potentially before the beginning of the buffer. As we parse the header we increment it,
637+ // and we can pretty much guarantee it will go back to being positive unless there is a major bug.
638+ // - The class _partialHeaderLen is back to zero since we took ownership of the contained data.
639+ memcpy (_partialHeader + _partialHeaderLen, data,
640+ std::min (plen, (size_t ) WS_MAX_HEADER_LEN - _partialHeaderLen));
641+ headerBuf = _partialHeader;
642+ initialPlen += _partialHeaderLen;
643+ plen += _partialHeaderLen;
644+ dataPayloadOffset -= _partialHeaderLen;
645+ partialHeaderLen = _partialHeaderLen;
646+
647+ _partialHeaderLen = 0 ;
648+ }
649+
650+ // The following series of gotos could have been a try-catch but we are likely being built with -fno-exceptions
651+ if (plen < 2 )
652+ goto _exceptionHandleFailPartialHeader;
653+
616654 _pinfo.index = 0 ;
617- _pinfo.final = (fdata [0 ] & 0x80 ) != 0 ;
618- _pinfo.opcode = fdata [0 ] & 0x0F ;
619- _pinfo.masked = (fdata [1 ] & 0x80 ) != 0 ;
620- _pinfo.len = fdata [1 ] & 0x7F ;
621- data += 2 ;
655+ _pinfo.final = (headerBuf [0 ] & 0x80 ) != 0 ;
656+ _pinfo.opcode = headerBuf [0 ] & 0x0F ;
657+ _pinfo.masked = (headerBuf [1 ] & 0x80 ) != 0 ;
658+ _pinfo.len = headerBuf [1 ] & 0x7F ;
659+ dataPayloadOffset += 2 ;
622660 plen -= 2 ;
623- if (_pinfo.len == 126 ){
624- _pinfo.len = fdata[3 ] | (uint16_t )(fdata[2 ]) << 8 ;
625- data += 2 ;
661+
662+ if (_pinfo.len == 126 ) {
663+ if (plen < 2 )
664+ goto _exceptionHandleFailPartialHeader;
665+
666+ _pinfo.len = headerBuf[3 ] | (uint16_t )(headerBuf[2 ]) << 8 ;
667+ dataPayloadOffset += 2 ;
626668 plen -= 2 ;
627- } else if (_pinfo.len == 127 ){
628- _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 ;
629- data += 8 ;
669+ } else if (_pinfo.len == 127 ) {
670+ if (plen < 8 )
671+ goto _exceptionHandleFailPartialHeader;
672+
673+ _pinfo.len = headerBuf[9 ] | (uint16_t )(headerBuf[8 ]) << 8 | (uint32_t )(headerBuf[7 ]) << 16 |
674+ (uint32_t )(headerBuf[6 ]) << 24 | (uint64_t )(headerBuf[5 ]) << 32 | (uint64_t )(headerBuf[4 ]) << 40 |
675+ (uint64_t )(headerBuf[3 ]) << 48 | (uint64_t )(headerBuf[2 ]) << 56 ;
676+ dataPayloadOffset += 8 ;
630677 plen -= 8 ;
631678 }
632679
633- if (_pinfo.masked ){
634- memcpy (_pinfo.mask , data, 4 );
635- data += 4 ;
680+ if (_pinfo.masked ) {
681+ if (plen < 4 )
682+ goto _exceptionHandleFailPartialHeader;
683+
684+ memcpy (_pinfo.mask , headerBuf + dataPayloadOffset + partialHeaderLen, 4 );
685+ dataPayloadOffset += 4 ;
636686 plen -= 4 ;
637687 }
688+
689+ // Yes I know the control flow here isn't 100% legible but we must support -fno-exceptions.
690+ // If we got to this point it means we did NOT receive a truncated header, therefore we can skip the exception
691+ // handling.
692+ // Control flow resumes after the following block.
693+ goto _headerParsingSuccessful;
694+
695+ // We DID receive a truncated header:
696+ // - We copy it to our buffer and set the _partialHeaderLen
697+ // - We return early
698+ // This will trigger the partial recovery at the next call of this method, once more data is received and we have
699+ // a full header.
700+ _exceptionHandleFailPartialHeader:
701+ {
702+ if (initialPlen <= WS_MAX_HEADER_LEN) {
703+ // If initialPlen > WS_MAX_HEADER_LEN there must be something wrong with this code. It should never happen but
704+ // but it's better safe than sorry.
705+ memcpy (_partialHeader, headerBuf, initialPlen * sizeof (uint8_t ));
706+ _partialHeaderLen = initialPlen;
707+ } else {
708+ DEBUGF (" [AsyncWebSocketClient::_onData] initialPlen (= %d) > WS_MAX_HEADER_LEN (= %d)\n " , initialPlen,
709+ WS_MAX_HEADER_LEN);
710+ }
711+ return ;
712+ }
713+
714+ _headerParsingSuccessful:
715+
716+ data += dataPayloadOffset;
638717 }
639718
640719 const size_t datalen = std::min ((size_t )(_pinfo.len - _pinfo.index ), plen);
0 commit comments