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