@@ -473,6 +473,8 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
473
473
_clientId = _server->_getNextId ();
474
474
_status = WS_CONNECTED;
475
475
_pstate = 0 ;
476
+ _partialHeader = nullptr ;
477
+ _partialHeaderLen = 0 ;
476
478
_lastMessageTime = millis ();
477
479
_keepAlivePeriod = 0 ;
478
480
_client->setRxTimeout (0 );
@@ -611,30 +613,107 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
611
613
_lastMessageTime = millis ();
612
614
uint8_t *data = (uint8_t *)pbuf;
613
615
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
+
616
654
_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 ;
622
660
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 ;
626
668
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 ;
630
677
plen -= 8 ;
631
678
}
632
679
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 ;
636
686
plen -= 4 ;
637
687
}
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;
638
717
}
639
718
640
719
const size_t datalen = std::min ((size_t )(_pinfo.len - _pinfo.index ), plen);
0 commit comments