@@ -473,6 +473,7 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
473
473
_clientId = _server->_getNextId ();
474
474
_status = WS_CONNECTED;
475
475
_pstate = 0 ;
476
+ _partialHeaderLen = 0 ;
476
477
_lastMessageTime = millis ();
477
478
_keepAlivePeriod = 0 ;
478
479
_client->setRxTimeout (0 );
@@ -616,30 +617,107 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
616
617
_lastMessageTime = millis ();
617
618
uint8_t *data = (uint8_t *)pbuf;
618
619
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
+
621
658
_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 ;
627
664
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 ;
631
672
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 ;
635
681
plen -= 8 ;
636
682
}
637
683
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 ;
641
690
plen -= 4 ;
642
691
}
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;
643
721
}
644
722
645
723
const size_t datalen = std::min ((size_t )(_pinfo.len - _pinfo.index ), plen);
0 commit comments