@@ -515,12 +515,15 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
515515 _pinfo.index = 0 ;
516516 _pinfo.final = (fdata[0 ] & 0x80 ) != 0 ;
517517 _pinfo.opcode = fdata[0 ] & 0x0F ;
518- _pinfo.masked = (fdata[1 ] & 0x80 ) != 0 ;
518+ _pinfo.masked = (( fdata[1 ] & 0x80 ) != 0 ) ? 1 : 0 ;
519519 _pinfo.len = fdata[1 ] & 0x7F ;
520520
521- // async_ws_log_d("WS[%" PRIu32 "]: _onData: %" PRIu32, _clientId, plen);
522- // async_ws_log_d("WS[%" PRIu32 "]: _status = %" PRIu32, _clientId, _status);
523- // async_ws_log_d("WS[%" PRIu32 "]: _pinfo: index: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", masked: %" PRIu8 ", len: %" PRIu64, _clientId, _pinfo.index, _pinfo.final, _pinfo.opcode, _pinfo.masked, _pinfo.len);
521+ // async_ws_log_w("WS[%" PRIu32 "]: _onData: %" PRIu32, _clientId, plen);
522+ // async_ws_log_w("WS[%" PRIu32 "]: _status = %" PRIu32, _clientId, _status);
523+ // async_ws_log_w(
524+ // "WS[%" PRIu32 "]: _pinfo: index: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", masked: %" PRIu8 ", len: %" PRIu64, _clientId, _pinfo.index,
525+ // _pinfo.final, _pinfo.opcode, _pinfo.masked, _pinfo.len
526+ // );
524527
525528 data += 2 ;
526529 plen -= 2 ;
@@ -536,18 +539,50 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
536539 data += 8 ;
537540 plen -= 8 ;
538541 }
542+ }
543+
544+ if (_pinfo.masked ) {
545+ // Read mask bytes (may be fragmented across packets in Safari)
546+ size_t mask_offset = 0 ;
547+
548+ // If we're resuming from a previous fragmented read, check _pinfo.index
549+ if (_pstate == 1 && _pinfo.index < 4 ) {
550+ mask_offset = _pinfo.index ;
551+ }
539552
540- if (_pinfo.masked
541- && plen >= 4 ) { // if ws.close() is called, Safari sends a close frame with plen 2 and masked bit set. We must not decrement plen which is already 0.
542- memcpy (_pinfo.mask , data, 4 );
543- data += 4 ;
544- plen -= 4 ;
553+ // Read as many mask bytes as available
554+ while (mask_offset < 4 && plen > 0 ) {
555+ _pinfo.mask [mask_offset++] = *data++;
556+ plen--;
557+ }
558+
559+ // Check if we have all 4 mask bytes
560+ if (mask_offset < 4 ) {
561+ // Incomplete mask
562+ if (_pinfo.opcode == WS_DISCONNECT && plen == 0 ) {
563+ // Safari close frame edge case: masked bit set but no mask data
564+ // async_ws_log_w("WS[%" PRIu32 "]: close frame with incomplete mask, treating as unmasked", _clientId);
565+ _pinfo.masked = 0 ;
566+ _pinfo.index = 0 ;
567+ } else {
568+ // Wait for more data
569+ // async_ws_log_w("WS[%" PRIu32 "]: waiting for more mask data: read=%zu/4", _clientId, mask_offset);
570+ _pinfo.index = mask_offset; // Save progress
571+ _pstate = 1 ;
572+ return ;
573+ }
574+ } else {
575+ // All mask bytes received
576+ // async_ws_log_w("WS[%" PRIu32 "]: mask complete", _clientId);
577+ _pinfo.index = 0 ; // Reset index for payload processing
545578 }
546579 }
547580
548581 const size_t datalen = std::min ((size_t )(_pinfo.len - _pinfo.index ), plen);
549582 const auto datalast = data[datalen];
550583
584+ // async_ws_log_w("WS[%" PRIu32 "]: _processing data: datalen=%" PRIu32 ", plen=%" PRIu32, _clientId, datalen, plen);
585+
551586 if (_pinfo.masked ) {
552587 for (size_t i = 0 ; i < datalen; i++) {
553588 data[i] ^= _pinfo.mask [(_pinfo.index + i) % 4 ];
@@ -606,7 +641,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
606641 }
607642 }
608643 } else {
609- // os_printf ("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
644+ // async_ws_log_w ("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
610645 // what should we do?
611646 break ;
612647 }
0 commit comments