Skip to content

ws: Binary/Text events discard payload_len/payload_offset/FIN — frames larger than buffer_size can't be reassembled #666

Description

@L-jasmine

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

  1. Server sends a single binary WebSocket frame whose payload exceeds the client buffer_size (e.g. an >8 KiB msgpack message).
  2. Client uses EspWebSocketClientConfig { buffer_size: 8192, .. } and a WebSocketEventType::Binary(data) handler that logs data.len() and runs rmp_serde::from_slice::<T>(&data).
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions