Bug description
When a received WebSocket binary/text frame is larger than EspWebSocketClientConfig::buffer_size, the underlying ESP-IDF C client (esp_websocket_client) delivers it as multiple WEBSOCKET_EVENT_DATA callbacks — one per buffer_size-sized chunk. The C event struct esp_websocket_event_data_t carries everything needed to reassemble a fragmented frame:
data_len — bytes delivered in this callback
payload_len — total payload length of the whole frame
payload_offset — running byte offset within the frame
…and client->last_fin is read too.
esp-idf-svc's WebSocketEventType::Binary(&[u8]) / Text(&str) only retain data_len (the slice length) and discard payload_len, payload_offset, and the FIN flag.
Consequence: the application receives N partial Binary/Text events with no way to know (a) whether an event is a complete message or a fragment, or (b) where the message ends. Any per-message deserializer (e.g. rmp_serde::from_slice, JSON from_slice) fails on every fragment. The same symptom was reported (then closed as incomplete) in #602.
Reproduced on master: src/ws/client.rs still builds the event from data_len only —
// master src/ws/client.rs:123
Binary(&'a [u8]),
// master src/ws/client.rs:151-156 (data_len only; payload_len/payload_offset dropped)
2 => Ok(Self::Binary(unsafe {
core::slice::from_raw_parts(
event_data.data_ptr as *const u8,
event_data.data_len as usize,
)
})),
The C side delivers the chunked events and already computes all three fields (esp_websocket_client.c, recv loop, ~L1066-1101):
client->payload_offset = 0;
do {
rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, ...);
client->payload_len = esp_transport_ws_get_read_payload_len(client->transport);
...
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen); // event.data_len = rlen
client->payload_offset += rlen;
} while (client->payload_offset < client->payload_len);
and dispatch (~L218-222) sets event_data.data_len = data_len; event_data.payload_len = client->payload_len; event_data.payload_offset = client->payload_offset;.
- Would you like to work on a fix? [n]
To Reproduce
- Server sends a single binary WebSocket frame whose payload exceeds the client
buffer_size (e.g. an >8 KiB msgpack message).
- Client uses
EspWebSocketClientConfig { buffer_size: 8192, .. } and a WebSocketEventType::Binary(data) handler that logs data.len() and runs rmp_serde::from_slice::<T>(&data).
- Observe multiple
Binary events, each ≤ 8192 bytes, and the deserializer failing on every fragment:
WebSocketEventType::Binary(data) => {
log::info!("recv binary size: {}", data.len()); // prints e.g. 2844, then 2844, ... never the full length
let _msg: ServerMsg = rmp_serde::from_slice(data)?; // fails: incomplete
}
Expected behavior
The Rust API should let the application reassemble fragmented frames. Either:
- Expose
payload_len, payload_offset (and/or a "final chunk / FIN" flag) on Binary/Text events — e.g. via new fields/variant; or
- Reassemble internally and emit a single
Binary/Text event per complete message (what most consumers expect, matching e.g. tokio-tungstenite).
Either resolves the issue; today there is no correct way to receive a frame larger than buffer_size.
Environment
- Crate (
esp-idf-svc) version: 0.52.1 (reproduced; master identical at src/ws/client.rs:123,151-156)
- ESP-IDF branch or tag: v5.x (managed component
espressif/esp_websocket_client ^1.1.0)
- Target device (MCU): esp32s3
- OS: Linux (WSL2)
Workaround
Set buffer_size to ≥ the largest expected single inbound frame. This works but requires knowing frame sizes up front, wastes memory, and silently breaks if any frame ever exceeds it.
Bug description
When a received WebSocket binary/text frame is larger than
EspWebSocketClientConfig::buffer_size, the underlying ESP-IDF C client (esp_websocket_client) delivers it as multipleWEBSOCKET_EVENT_DATAcallbacks — one perbuffer_size-sized chunk. The C event structesp_websocket_event_data_tcarries everything needed to reassemble a fragmented frame:data_len— bytes delivered in this callbackpayload_len— total payload length of the whole framepayload_offset— running byte offset within the frame…and
client->last_finis read too.esp-idf-svc'sWebSocketEventType::Binary(&[u8])/Text(&str)only retaindata_len(the slice length) and discardpayload_len,payload_offset, and the FIN flag.Consequence: the application receives N partial
Binary/Textevents with no way to know (a) whether an event is a complete message or a fragment, or (b) where the message ends. Any per-message deserializer (e.g.rmp_serde::from_slice, JSONfrom_slice) fails on every fragment. The same symptom was reported (then closed as incomplete) in #602.Reproduced on
master:src/ws/client.rsstill builds the event fromdata_lenonly —The C side delivers the chunked events and already computes all three fields (
esp_websocket_client.c, recv loop, ~L1066-1101):and dispatch (~L218-222) sets
event_data.data_len = data_len; event_data.payload_len = client->payload_len; event_data.payload_offset = client->payload_offset;.To Reproduce
buffer_size(e.g. an >8 KiB msgpack message).EspWebSocketClientConfig { buffer_size: 8192, .. }and aWebSocketEventType::Binary(data)handler that logsdata.len()and runsrmp_serde::from_slice::<T>(&data).Binaryevents, each ≤ 8192 bytes, and the deserializer failing on every fragment:Expected behavior
The Rust API should let the application reassemble fragmented frames. Either:
payload_len,payload_offset(and/or a "final chunk / FIN" flag) onBinary/Textevents — e.g. via new fields/variant; orBinary/Textevent per complete message (what most consumers expect, matching e.g.tokio-tungstenite).Either resolves the issue; today there is no correct way to receive a frame larger than
buffer_size.Environment
esp-idf-svc) version:0.52.1(reproduced;masteridentical atsrc/ws/client.rs:123,151-156)espressif/esp_websocket_client^1.1.0)Workaround
Set
buffer_sizeto ≥ the largest expected single inbound frame. This works but requires knowing frame sizes up front, wastes memory, and silently breaks if any frame ever exceeds it.