Skip to content

Commit 742aeb4

Browse files
committed
test: cover Noise relay codec boundaries
Co-authored-by: Codex noreply@openai.com
1 parent c8e987a commit 742aeb4

5 files changed

Lines changed: 114 additions & 0 deletions

File tree

codex-rs/exec-server/src/noise_relay/message_framing.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,7 @@ impl JsonRpcMessageDecoder {
8686
Ok(Some(message_len))
8787
}
8888
}
89+
90+
#[cfg(test)]
91+
#[path = "message_framing_tests.rs"]
92+
mod tests;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use codex_app_server_protocol::JSONRPCMessage;
2+
use codex_app_server_protocol::JSONRPCNotification;
3+
use pretty_assertions::assert_eq;
4+
5+
use super::JsonRpcMessageDecoder;
6+
use super::MAX_NOISE_JSONRPC_MESSAGE_LEN;
7+
use super::NOISE_RECORD_PLAINTEXT_LEN;
8+
use super::frame_jsonrpc_message;
9+
use crate::ExecServerError;
10+
11+
#[test]
12+
fn fragments_and_reassembles_large_jsonrpc_message() {
13+
let message = JSONRPCMessage::Notification(JSONRPCNotification {
14+
method: "large/test".to_string(),
15+
params: Some(serde_json::json!({
16+
"data": "x".repeat(128 * 1024),
17+
})),
18+
});
19+
let framed = frame_jsonrpc_message(&message).unwrap();
20+
assert!(framed.len() > 128 * 1024);
21+
22+
let mut decoder = JsonRpcMessageDecoder::default();
23+
let mut decoded = Vec::new();
24+
for record in framed.chunks(NOISE_RECORD_PLAINTEXT_LEN) {
25+
decoded.extend(decoder.push(record).unwrap());
26+
}
27+
28+
assert_eq!(decoded, vec![message]);
29+
}
30+
31+
#[test]
32+
fn rejects_declared_message_length_above_limit_without_payload() {
33+
let mut decoder = JsonRpcMessageDecoder::default();
34+
let declared_len = (MAX_NOISE_JSONRPC_MESSAGE_LEN as u32 + 1).to_be_bytes();
35+
36+
assert!(matches!(
37+
decoder.push(&declared_len),
38+
Err(ExecServerError::Protocol(message))
39+
if message == "Noise relay JSON-RPC message has invalid length"
40+
));
41+
}
42+
43+
#[test]
44+
fn rejects_oversized_plaintext_record() {
45+
let mut decoder = JsonRpcMessageDecoder::default();
46+
47+
assert!(matches!(
48+
decoder.push(&vec![0; NOISE_RECORD_PLAINTEXT_LEN + 1]),
49+
Err(ExecServerError::Protocol(message))
50+
if message == "Noise relay plaintext record exceeds maximum length"
51+
));
52+
}

codex-rs/exec-server/src/noise_relay/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
66

77
use crate::ExecServerError;
88

9+
pub(crate) use harness::noise_harness_connection_from_websocket;
10+
911
// This bounds allocation in tungstenite before protobuf and Noise record
1012
// validation run. It comfortably fits one maximum Noise record plus metadata.
1113
const MAX_NOISE_RELAY_WEBSOCKET_MESSAGE_SIZE: usize = 256 * 1024;

codex-rs/exec-server/src/noise_relay/ordered_ciphertext.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,7 @@ impl OrderedCiphertextFrames {
7979
Ok(())
8080
}
8181
}
82+
83+
#[cfg(test)]
84+
#[path = "ordered_ciphertext_tests.rs"]
85+
mod tests;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use pretty_assertions::assert_eq;
2+
3+
use super::MAX_PENDING_BYTES;
4+
use super::OrderedCiphertextFrames;
5+
6+
#[test]
7+
fn releases_ciphertexts_only_in_nonce_order() {
8+
let mut frames = OrderedCiphertextFrames::default();
9+
10+
assert_eq!(
11+
frames.push(/*seq*/ 1, b"second".to_vec()).unwrap(),
12+
Vec::<Vec<u8>>::new()
13+
);
14+
assert_eq!(
15+
frames.push(/*seq*/ 0, b"first".to_vec()).unwrap(),
16+
vec![b"first".to_vec(), b"second".to_vec()]
17+
);
18+
}
19+
20+
#[test]
21+
fn ignores_duplicate_ciphertexts_without_replacing_buffered_record() {
22+
let mut frames = OrderedCiphertextFrames::default();
23+
24+
assert_eq!(
25+
frames.push(/*seq*/ 1, b"first copy".to_vec()).unwrap(),
26+
Vec::<Vec<u8>>::new()
27+
);
28+
assert_eq!(
29+
frames.push(/*seq*/ 1, b"replacement".to_vec()).unwrap(),
30+
Vec::<Vec<u8>>::new()
31+
);
32+
assert_eq!(
33+
frames.push(/*seq*/ 0, b"zero".to_vec()).unwrap(),
34+
vec![b"zero".to_vec(), b"first copy".to_vec()]
35+
);
36+
assert_eq!(
37+
frames.push(/*seq*/ 0, b"duplicate".to_vec()).unwrap(),
38+
Vec::<Vec<u8>>::new()
39+
);
40+
}
41+
42+
#[test]
43+
fn rejects_unbounded_reordering() {
44+
let mut frames = OrderedCiphertextFrames::default();
45+
46+
assert!(frames.push(/*seq*/ 65, Vec::new()).is_err());
47+
assert!(
48+
frames
49+
.push(/*seq*/ 1, vec![0; MAX_PENDING_BYTES + 1])
50+
.is_err()
51+
);
52+
}

0 commit comments

Comments
 (0)