mirror of
https://github.com/openai/codex.git
synced 2026-09-06 15:29:32 +00:00
## What changed - Add a `--listen` option that accepts `stdio`, `stdio://`, or a `ws://IP:PORT` endpoint, while retaining stdio as the default. - Serve the existing length-prefixed protocol in binary WebSocket messages, with isolated connections, shared host limits, and a `/readyz` endpoint. - Reject browser-origin handshakes and contain malformed frames to the affected connection. ## Testing - Cover listen URL parsing and complete-frame encoding and decoding. - Exercise readiness, cell execution, tool callbacks, large frames, concurrent connections, malformed frames, and origin rejection through the WebSocket listener. GitOrigin-RevId: 01c8be4c6256b8ce4a3a0002440dcb3294e5f887
138 lines
4.3 KiB
Rust
138 lines
4.3 KiB
Rust
use pretty_assertions::assert_eq;
|
|
use serde_json::json;
|
|
use tokio::io::AsyncReadExt;
|
|
use tokio::io::AsyncWriteExt;
|
|
|
|
use super::EncodedFrame;
|
|
use super::FramedReader;
|
|
use super::FramedWriter;
|
|
use super::MAX_FRAME_BYTES;
|
|
|
|
#[test]
|
|
fn complete_frame_round_trips_without_a_byte_stream() {
|
|
let value = json!({"type": "session/open", "sessionId": "session-1"});
|
|
let bytes = EncodedFrame::encode(&value)
|
|
.expect("encode frame")
|
|
.into_framed_bytes();
|
|
|
|
assert_eq!(
|
|
EncodedFrame::decode_framed::<serde_json::Value>(&bytes).expect("decode frame"),
|
|
value
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn complete_frame_rejects_truncated_and_trailing_payloads() {
|
|
let value = json!({"value": 1});
|
|
let bytes = EncodedFrame::encode(&value)
|
|
.expect("encode frame")
|
|
.into_framed_bytes();
|
|
|
|
let truncated = &bytes[..bytes.len() - 1];
|
|
let truncated_error = EncodedFrame::decode_framed::<serde_json::Value>(truncated)
|
|
.expect_err("truncated frame should fail");
|
|
assert_eq!(truncated_error.kind(), std::io::ErrorKind::InvalidData);
|
|
|
|
let mut trailing = bytes;
|
|
trailing.push(0);
|
|
let trailing_error = EncodedFrame::decode_framed::<serde_json::Value>(&trailing)
|
|
.expect_err("frame with trailing bytes should fail");
|
|
assert_eq!(trailing_error.kind(), std::io::ErrorKind::InvalidData);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn frame_wire_format_is_little_endian_length_prefixed_json() {
|
|
let (writer, mut reader) = tokio::io::duplex(/*max_buf_size*/ 128);
|
|
let write = tokio::spawn(async move {
|
|
FramedWriter::new(writer)
|
|
.write(&json!({"value": 1}))
|
|
.await
|
|
.expect("write frame");
|
|
});
|
|
|
|
let mut bytes = Vec::new();
|
|
reader.read_to_end(&mut bytes).await.expect("read bytes");
|
|
write.await.expect("writer task");
|
|
|
|
let payload = br#"{"value":1}"#;
|
|
let mut expected = (payload.len() as u32).to_le_bytes().to_vec();
|
|
expected.extend_from_slice(payload);
|
|
assert_eq!(bytes, expected);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn fragmented_frame_round_trips() {
|
|
let value = json!({"type": "session/open", "sessionId": "session-1"});
|
|
let payload = serde_json::to_vec(&value).expect("serialize");
|
|
let mut bytes = (payload.len() as u32).to_le_bytes().to_vec();
|
|
bytes.extend(payload);
|
|
|
|
let (mut writer, reader) = tokio::io::duplex(/*max_buf_size*/ 128);
|
|
let write = tokio::spawn(async move {
|
|
for byte in bytes {
|
|
writer.write_all(&[byte]).await.expect("write byte");
|
|
tokio::task::yield_now().await;
|
|
}
|
|
});
|
|
|
|
assert_eq!(
|
|
FramedReader::new(reader)
|
|
.read::<serde_json::Value>()
|
|
.await
|
|
.expect("read frame"),
|
|
Some(value)
|
|
);
|
|
write.await.expect("writer task");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn eof_is_clean_only_at_a_frame_boundary() {
|
|
let (writer, reader) = tokio::io::duplex(/*max_buf_size*/ 16);
|
|
drop(writer);
|
|
assert_eq!(
|
|
FramedReader::new(reader)
|
|
.read::<serde_json::Value>()
|
|
.await
|
|
.expect("clean eof"),
|
|
None
|
|
);
|
|
|
|
let (mut writer, reader) = tokio::io::duplex(/*max_buf_size*/ 16);
|
|
writer
|
|
.write_all(&[1, 0])
|
|
.await
|
|
.expect("write partial header");
|
|
drop(writer);
|
|
let err = FramedReader::new(reader)
|
|
.read::<serde_json::Value>()
|
|
.await
|
|
.expect_err("truncated header");
|
|
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn oversized_and_malformed_frames_are_rejected() {
|
|
let (mut writer, reader) = tokio::io::duplex(/*max_buf_size*/ 16);
|
|
writer
|
|
.write_all(&((MAX_FRAME_BYTES as u32) + 1).to_le_bytes())
|
|
.await
|
|
.expect("write oversized header");
|
|
let err = FramedReader::new(reader)
|
|
.read::<serde_json::Value>()
|
|
.await
|
|
.expect_err("oversized frame");
|
|
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
|
|
|
|
let (mut writer, reader) = tokio::io::duplex(/*max_buf_size*/ 16);
|
|
writer
|
|
.write_all(&(1_u32).to_le_bytes())
|
|
.await
|
|
.expect("write length");
|
|
writer.write_all(b"{").await.expect("write malformed json");
|
|
let err = FramedReader::new(reader)
|
|
.read::<serde_json::Value>()
|
|
.await
|
|
.expect_err("malformed frame");
|
|
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
|
|
}
|