Receive Invalid Json Error with permessage-deflate window_bits Set to 15 (but when it set to 9-14 get no error)

I encountered an issue with the binance futures websocket server when using the permessage-deflate extension with window_bits set to 15.
The server responds with an error indicating an invalid JSON, as it appears the JSON payload is missing the last two bytes. However, changing the window_bits to a value from 9 to 14 resolves the issue and everything works as expected.
This behavior seems specific to when both permessage-deflate is set to use WindowBit::Fifteen and a correct listenKey is provided.
It is worth noting that a similar setup works without issue on the OKX exchange WebSocket(which also support deflate).

detail

I am using Rust to subscribe to the bookTicker stream combined with a listenKey via WebSocket. The relevant part of the code is demonstrated below:

client_max_window_bits: WindowBit::Fifteen

wss://fstream.binance.com/stream

{
    "id":1,
    "method":"SUBSCRIBE",
    "params":[
        "btcusdt@bookTicker",
        // listen_key
        "1234"
    ]
}

The error response I receive when window_bits is set to 15 is as follows:

{“error”:{“code”:3,“msg”:“Invalid JSON: EOF while parsing a list at line 1 column 66”}}

This indicates that the JSON payload is truncated, missing ]} at the end.

Interestingly, if I set window_bits to any value between 9 and 14, or if I provide an incorrect listenKey, no error occurs and the connection performs as expected.

Here is an example of the request/response headers when the error occurs:

GET /stream HTTP/1.1
Host: fstream.binance.com
Upgrade: websocket
Connection: Upgrade
Sec-Websocket-Key: [redacted]
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=15; server_max_window_bits=15

resposne:

"date": "Fri, 24 Nov 2023 02:03:45 GMT",
"connection": "upgrade",
"upgrade": "websocket",
"sec-websocket-accept": "[redacted]",
"sec-websocket-extensions": "permessage-deflate; server_no_context_takeover; server_max_window_bits=15; client_max_window_bits=15"

code to reproduce

The dependency versions used are outlined in Cargo.toml:

[dependencies]
ws-tool = "0.10.2"
rustls-connector = { version = "0.16", features = ["webpki-roots-certs"] }
tracing-subscriber = "0.3.18"
tracing = "0.1.40"
serde_json = "1.0.108"

main.rs

use ws_tool::frame::OpCode;
pub type WsStream = ws_tool::codec::DeflateCodec<
    rustls_connector::rustls::StreamOwned<
        rustls_connector::rustls::ClientConnection,
        std::net::TcpStream,
    >,
>;
fn ws_extension_header() -> String {
    use ws_tool::codec::WindowBit;
    use ws_tool::ClientConfig;
    ws_tool::codec::PMDConfig {
        server_no_context_takeover: ClientConfig::default().context_take_over,
        client_no_context_takeover: ClientConfig::default().context_take_over,
        // okx support WindowBit 9-15, why binnance 9-14 is ok but 15 would get error {"error":{"code":3,"msg":"Invalid JSON: EOF while parsing a list at line 1 c
        // replace below two WindowBit to WindowBit::Fourteen would request ok, only WindowBit::Fifteen request error
        server_max_window_bits: WindowBit::Fifteen,
        client_max_window_bits: WindowBit::Fifteen,
        // server_max_window_bits: WindowBit::Fourteen,
        // client_max_window_bits: WindowBit::Fourteen,
    }
    .ext_string()
}
pub fn ws_connect(
    url: &ws_tool::http::Uri,
    permessage_deflate: bool,
) -> WsStream  {
    let host = url.host().unwrap();
    let schema = url.scheme_str().unwrap();
    let port = if let Some(port) = url.port() {
        port.as_u16()
    } else if schema == "wss" {
        443
    } else if schema == "ws" {
        80
    } else {
        unreachable!()
    };

    let stream = std::net::TcpStream::connect((host, port)).unwrap();
    // let stream = socks::Socks5Stream::connect(("192.168.110.123", 10808), (host, port)).unwrap().into_inner();
    stream
        .set_read_timeout(Some(std::time::Duration::from_secs(5)))
        .unwrap();

    let stream = ws_tool::connector::wrap_rustls(stream, host, Vec::new()).unwrap();
    let mut builder = ws_tool::ClientBuilder::new();
    if permessage_deflate {
        builder = builder.extension(ws_extension_header());
    }
    builder.with_stream(url.clone(), stream, ws_tool::codec::DeflateCodec::check_fn).unwrap()
}
fn main() {
    let subscriber = tracing_subscriber::FmtSubscriber::builder()
        .with_max_level(tracing::Level::DEBUG)
        .with_file(true)
        .with_line_number(true)
        .finish();
    tracing::subscriber::set_global_default(subscriber).expect("Failed to set tracing subscriber");

    // let url = "wss://ws.okx.com:8443/ws/v5/public";
    let url = "wss://fstream.binance.com/stream";
    let mut stream = ws_connect(&url.parse().unwrap(), true);
    if url.contains("okx") {
        stream.send(OpCode::Text, br#"{
            "op": "subscribe",
            "args": [{
                "channel": "tickers",
                "instId": "BTC-USDT-SWAP"
            }]
        }"#).unwrap();
    } else {
        // let listen_key = std::env::var("LISTEN_KEY").expect("env LISTEN_KEY not set");
        // assert!(listen_key.len() == 64);
        let json = serde_json::to_string(&serde_json::json!({
            "id":1,
            "method":"SUBSCRIBE",
            "params":[
                "btcusdt@bookTicker",
                // listen_key
                "1234"
            ]
        })).unwrap();
        stream.send(OpCode::Text, json.as_bytes()).unwrap();
    }
    for _ in 0..10 {
        let (_code, data) = stream.receive().unwrap();
        println!("{}", std::str::from_utf8(data).unwrap());
    }
}

Additional Context

I have verified that the ws-tool WebSocket client library works correctly with the OKX exchange WebSocket server when using WindowBit::Fifteen. This leads me to believe that the issue may lie with the Binance WebSocket server’s handling of permessage-deflate with window_bits set to the maximum value of 15.

I would appreciate any insights or suggestions on how to resolve this issue.

Will it work if you just subscribe to the listenkey?

still get same error if only subscribe listenkey with WindowBit::Fifteen

I confirmed that this is my websocket client lib ws-tool’s bug, here is the patch, not a biniance server bug, so close this issue
https://github.com/PrivateRookie/ws-tool/commit/73c6906bb87cef8f46fa98b7042fe7a9b3fe7d15