@@ -565,7 +565,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
565565 uint8_t *data = (uint8_t *)pbuf;
566566
567567 while (plen > 0 ) {
568- async_ws_log_v (" WS[%" PRIu32 " ]: _onData: plen=%" PRIu32 " , _pstate=%" PRIu8 " , _status=%" PRIu8, _clientId, plen, _pstate, static_cast <uint8_t >(_status));
568+ async_ws_log_v (" WS[%" PRIu32 " ] _onData: plen=%" PRIu32 " , _pstate=%" PRIu8 " , _status=%" PRIu8, _clientId, plen, _pstate, static_cast <uint8_t >(_status));
569569
570570 if (_pstate == STATE_FRAME_START) {
571571 const uint8_t *fdata = data;
@@ -593,7 +593,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
593593 }
594594
595595 async_ws_log_v (
596- " WS[%" PRIu32 " ]: _pinfo: index: %" PRIu64 " , final: %" PRIu8 " , opcode: %" PRIu8 " , masked: %" PRIu8 " , len: %" PRIu64, _clientId, _pinfo.index ,
596+ " WS[%" PRIu32 " ] _pinfo: index: %" PRIu64 " , final: %" PRIu8 " , opcode: %" PRIu8 " , masked: %" PRIu8 " , len: %" PRIu64, _clientId, _pinfo.index ,
597597 _pinfo.final , _pinfo.opcode , _pinfo.masked , _pinfo.len
598598 );
599599
@@ -606,7 +606,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
606606 if (plen == 0 ) {
607607 // Safari close frame edge case: masked bit set but no mask data
608608 if (_pinfo.opcode == WS_DISCONNECT) {
609- async_ws_log_v (" WS[%" PRIu32 " ]: close frame with incomplete mask, treating as unmasked" , _clientId);
609+ async_ws_log_v (" WS[%" PRIu32 " ] close frame with incomplete mask, treating as unmasked" , _clientId);
610610 _pinfo.masked = 0 ;
611611 _pinfo.index = 0 ;
612612 _pinfo.len = 0 ;
@@ -616,7 +616,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
616616
617617 // wait for more data
618618 _pstate = STATE_FRAME_MASK;
619- async_ws_log_v (" WS[%" PRIu32 " ]: waiting for more mask data: read=%" PRIu8 " /4" , _clientId, _pinfo.masked - 1 );
619+ async_ws_log_v (" WS[%" PRIu32 " ] waiting for more mask data: read=%" PRIu8 " /4" , _clientId, _pinfo.masked - 1 );
620620 return ;
621621 }
622622
@@ -632,7 +632,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
632632
633633 // restore masked to 1 for backward compatibility
634634 if (_pinfo.masked >= 5 ) {
635- async_ws_log_v (" WS[%" PRIu32 " ]: mask read complete" , _clientId);
635+ async_ws_log_v (" WS[%" PRIu32 " ] mask read complete" , _clientId);
636636 _pinfo.masked = 1 ;
637637 }
638638
@@ -644,30 +644,37 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
644644 }
645645 }
646646
647- if ((datalen + _pinfo.index ) < _pinfo.len ) {
648- _pstate = STATE_FRAME_DATA;
649- async_ws_log_v (" WS[%" PRIu32 " ]: processing next fragment index=%" PRIu64 " , len=%" PRIu32 " " , _clientId, _pinfo.index , (uint32_t )datalen);
650-
651- if (_pinfo.index == 0 ) {
652- if (_pinfo.opcode ) {
653- _pinfo.message_opcode = _pinfo.opcode ;
654- _pinfo.num = 0 ;
655- }
647+ if (_pinfo.index == 0 ) { // first fragment of the frame
648+ // init message_opcode for this frame
649+ // note: For next WS_CONTINUATION frames, they have opcode 0, so message_opcode will stay like the first frame
650+ if (_pinfo.opcode == WS_TEXT || _pinfo.opcode == WS_BINARY) {
651+ _pinfo.message_opcode = _pinfo.opcode ;
656652 }
653+ // init frame number to 0 if only 1 frame or if this is the first frame of a fragmented message
654+ if (_pinfo.final || datalen < _pinfo.len ) {
655+ _pinfo.num = 0 ;
656+ }
657+ }
658+
659+ if ((datalen + _pinfo.index ) < _pinfo.len ) { // more fragments to read for this frame
660+ _pstate = STATE_FRAME_DATA;
657661
658662 if (datalen > 0 ) {
659- _handleDataEvent (data, datalen);
663+ async_ws_log_v (
664+ " WS[%" PRIu32 " ] processing next fragment of %s frame %" PRIu32 " , index: %" PRIu64 " , len: %" PRIu32 " " , _clientId,
665+ (_pinfo.message_opcode == WS_TEXT) ? " text" : " binary" , _pinfo.num , _pinfo.index , (uint32_t )datalen
666+ );
667+ _handleDataEvent (data, datalen, datalen == plen); // datalen == plen means that we are processing the last part of the current TCP packet
660668 }
661669
662670 // track index for next fragment
663671 _pinfo.index += datalen;
664672
665- } else if ((datalen + _pinfo.index ) == _pinfo.len ) {
673+ } else if ((datalen + _pinfo.index ) == _pinfo.len ) { // this is the last fragment for this frame
666674 _pstate = STATE_FRAME_START;
667- async_ws_log_v (" WS[%" PRIu32 " ]: processing final fragment index=%" PRIu64 " , len=%" PRIu32 " " , _clientId, _pinfo.index , (uint32_t )datalen);
668675
669676 if (_pinfo.opcode == WS_DISCONNECT) {
670- async_ws_log_v (" WS[%" PRIu32 " ]: processing disconnect" , _clientId);
677+ async_ws_log_v (" WS[%" PRIu32 " ] processing disconnect" , _clientId);
671678
672679 if (datalen) {
673680 uint16_t reasonCode = (uint16_t )(data[0 ] << 8 ) + data[1 ];
@@ -690,20 +697,23 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
690697 }
691698
692699 } else if (_pinfo.opcode == WS_PING) {
693- async_ws_log_v (" WS[%" PRIu32 " ]: processing ping" , _clientId);
700+ async_ws_log_v (" WS[%" PRIu32 " ] processing ping" , _clientId);
694701 _server->_handleEvent (this , WS_EVT_PING, NULL , NULL , 0 );
695702 _queueControl (WS_PONG, data, datalen);
696703
697704 } else if (_pinfo.opcode == WS_PONG) {
698- async_ws_log_v (" WS[%" PRIu32 " ]: processing pong" , _clientId);
705+ async_ws_log_v (" WS[%" PRIu32 " ] processing pong" , _clientId);
699706 if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp (AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0 ) {
700707 _server->_handleEvent (this , WS_EVT_PONG, NULL , NULL , 0 );
701708 }
702709
703710 } else if (_pinfo.opcode < WS_DISCONNECT) { // continuation or text/binary frame
704- async_ws_log_v (" WS[%" PRIu32 " ]: processing data frame num=%" PRIu32 " " , _clientId, _pinfo.num );
711+ async_ws_log_v (
712+ " WS[%" PRIu32 " ] processing final fragment of %s frame %" PRIu32 " , index: %" PRIu64 " , len: %" PRIu32 " " , _clientId,
713+ (_pinfo.message_opcode == WS_TEXT) ? " text" : " binary" , _pinfo.num , _pinfo.index , (uint32_t )datalen
714+ );
705715
706- _handleDataEvent (data, datalen);
716+ _handleDataEvent (data, datalen, datalen == plen); // datalen == plen means that we are processing the last part of the current TCP packet
707717
708718 if (_pinfo.final ) {
709719 _pinfo.num = 0 ;
@@ -731,7 +741,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
731741 }
732742}
733743
734- void AsyncWebSocketClient::_handleDataEvent (uint8_t *data, size_t len) {
744+ void AsyncWebSocketClient::_handleDataEvent (uint8_t *data, size_t len, bool endOfPaquet ) {
735745 // ------------------------------------------------------------
736746 // Issue 384: https://github.com/ESP32Async/ESPAsyncWebServer/issues/384
737747 // Discussion: https://github.com/ESP32Async/ESPAsyncWebServer/pull/383#discussion_r2760425739
@@ -740,21 +750,37 @@ void AsyncWebSocketClient::_handleDataEvent(uint8_t *data, size_t len) {
740750 // This was a bit hacky but it was working and it was documented, although completely incorrect because it was modifying a byte outside of the data buffer.
741751 // So to fix this behavior and to avoid breaking existing client code that may be relying on this behavior, we now have to copy the data to a temporary buffer that has an extra byte for the null terminator.
742752 // ------------------------------------------------------------
743- // Optimization note:
753+ //
754+ // Optimization notes:
755+ //
756+ // 1) opcodes
757+ //
744758 // - info->opcode stores the current WS frame type (binary, text, continuation)
745759 // - info->message_opcode stores the WS frame type of the first frame of the message, which is used for fragmented messages to know the message type when processing subsequent frame with opcode 0 (continuation)
746760 // So we can use info->message_opcode to avoid copying the data for non-text frames, and only copy the data for text frames when we need to add a null terminator for client code convenience.
761+ //
762+ // 2) data copy vs data backup/restore
763+ // - endOfPaquet: is true when datalen == plen. plen is the remaining bytes in the current TCP packet, so if datalen == plen, it means that we are processing the last part of the current TCP packet.
764+ // In that case, we have to copy since we cannot backup/restore the byte after the data buffer.
765+ // Otherwise we can backup the byte and restore since we know that the byte after is owned by the current TCP packet (same pointer).
747766 if (_pinfo.message_opcode == WS_TEXT) {
748- std::unique_ptr<uint8_t []> copy (new (std::nothrow) uint8_t [len + 1 ]());
749- if (copy) {
750- memcpy (copy.get (), data, len);
751- copy[len] = 0 ;
752- _server->_handleEvent (this , WS_EVT_DATA, (void *)&_pinfo, copy.get (), len);
753- } else {
754- async_ws_log_e (" Failed to allocate" );
755- if (_client) {
756- _client->abort ();
767+ if (endOfPaquet) {
768+ std::unique_ptr<uint8_t []> copy (new (std::nothrow) uint8_t [len + 1 ]());
769+ if (copy) {
770+ memcpy (copy.get (), data, len);
771+ copy[len] = 0 ;
772+ _server->_handleEvent (this , WS_EVT_DATA, (void *)&_pinfo, copy.get (), len);
773+ } else {
774+ async_ws_log_e (" Failed to allocate" );
775+ if (_client) {
776+ _client->abort ();
777+ }
757778 }
779+ } else {
780+ uint8_t backup = data[len];
781+ data[len] = 0 ;
782+ _server->_handleEvent (this , WS_EVT_DATA, (void *)&_pinfo, data, len);
783+ data[len] = backup;
758784 }
759785 } else {
760786 _server->_handleEvent (this , WS_EVT_DATA, (void *)&_pinfo, data, len);
0 commit comments