Files
codex/codex-rs/app-server-transport/src/websocket_compression_tests.rs
T
thorvald-oai 8d9f6466ff Add negotiated zstd compression to app-server WebSockets (#54930)
## What changed

- Negotiate compression over TCP and local sockets with `x-codex-app-server-compression: zstd-stream-v1`. Keep JSON text messages when compression is not negotiated.
- Send compressed JSON as binary messages, retaining a separate zstd stream per direction across messages. Export reusable negotiation and codec helpers.
- Limit decompressed messages to 64 MiB and compression windows to 1 MiB. Close compressed connections on decoding errors or unexpected text messages.
- Add audit and deny ignores for the unmaintained `zvariant`, `zbus_names`, and `zvariant_derive` versions pulled in through `zbus`.

## Testing

Add codec tests for retained history, fallback, size limits, malformed data, and oversized compression windows. Add TCP and local-socket round-trip tests covering negotiation, persistent streams, and ping/pong handling, plus TCP fallback for absent or unsupported offers.

GitOrigin-RevId: 50d3861e8fe4a9c523599a5ab773bec7cb883bf2
2026-10-17 20:54:03 +00:00

74 lines
2.3 KiB
Rust

//! Verify persistent history, peer fallback, and bounded decompression.
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn response_header_selects_compression_or_plain_fallback() -> io::Result<()> {
assert_eq!(
WebSocketCompression::from_response_header(/*value*/ None)?,
WebSocketCompression::Plain
);
assert_eq!(
WebSocketCompression::from_response_header(Some(ZSTD_STREAM_PROTOCOL))?,
WebSocketCompression::Zstd
);
assert!(WebSocketCompression::from_response_header(Some("unknown")).is_err());
Ok(())
}
#[test]
fn messages_keep_history_and_have_independent_size_limits() -> io::Result<()> {
let text = serde_json::to_string(
&(0..1_000)
.map(|index| format!("thread-{index}: individual message body {index}"))
.collect::<Vec<_>>(),
)?;
let mut encoder = ZstdEncoder::new()?;
let first = encoder.encode(&text)?;
let second = encoder.encode(&text)?;
let mut decoder = ZstdDecoder::new(text.len())?;
assert_eq!(decoder.decode(&first)?, text);
assert_eq!(decoder.decode(&second)?, text);
assert!(second.len() < first.len());
assert!(ZstdDecoder::new(text.len())?.decode(&second).is_err());
Ok(())
}
#[test]
fn decoded_size_limit_covers_output_larger_than_scratch_buffer() -> io::Result<()> {
let text = "abcdefgh".repeat(BUFFER_SIZE);
let encoded = ZstdEncoder::new()?.encode(&text)?;
assert_eq!(ZstdDecoder::new(text.len())?.decode(&encoded)?, text);
let error = ZstdDecoder::new(text.len() - 1)?
.decode(&encoded)
.expect_err("oversize message must fail");
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
Ok(())
}
#[test]
fn malformed_compressed_data_fails() -> io::Result<()> {
assert!(
ZstdDecoder::new(/*max_message_size*/ 1024)?
.decode(b"not zstd")
.is_err()
);
Ok(())
}
#[test]
fn larger_compression_window_is_rejected() -> io::Result<()> {
let mut encoder = ZstdEncoder::new()?;
encoder
.encoder
.set_parameter(CParameter::WindowLog(WINDOW_LOG + 1))?;
let encoded = encoder.encode("a message with an unsupported compression window")?;
assert!(
ZstdDecoder::new(/*max_message_size*/ 1024)?
.decode(&encoded)
.is_err()
);
Ok(())
}