mirror of
https://github.com/openai/codex.git
synced 2026-09-05 15:18:41 +00:00
## Why Exec-server JSON-RPC payloads can contain decimals, exponent notation, and integers outside the native 64-bit range. Decoding these values should preserve their original JSON representation without weakening the existing message complexity limit. ## What changed - Enable `serde_json` arbitrary-precision number and raw-value support for the exec-server protocol. - Decode serde's number and raw-value wrappers in the bounded JSON visitor, charging nested raw values against the 256K-value limit. - Select the JSON-RPC envelope variant from its fields after bounded decoding so arbitrary-precision values survive message deserialization. ## Testing Add coverage for exact arbitrary-precision number round trips and for enforcing the value limit inside raw-value wrappers. GitOrigin-RevId: a6e0a3fba6f88e4e8a6ff414832f078beb2d2a70
147 lines
4.7 KiB
Rust
147 lines
4.7 KiB
Rust
use pretty_assertions::assert_eq;
|
|
use serde_json::Value;
|
|
use serde_json::json;
|
|
|
|
use super::JSONRPCError;
|
|
use super::JSONRPCErrorError;
|
|
use super::JSONRPCMessage;
|
|
use super::JSONRPCNotification;
|
|
use super::JSONRPCRequest;
|
|
use super::JSONRPCResponse;
|
|
use super::MAX_JSONRPC_VALUE_NODES;
|
|
use super::RequestId;
|
|
use super::SERDE_JSON_RAW_VALUE_TOKEN;
|
|
|
|
#[test]
|
|
fn round_trips_every_jsonrpc_message_variant() -> serde_json::Result<()> {
|
|
let messages = [
|
|
JSONRPCMessage::Request(JSONRPCRequest {
|
|
id: RequestId::Integer(1),
|
|
method: "request".to_string(),
|
|
params: Some(json!({"items": [1, 2, 3]})),
|
|
trace: None,
|
|
}),
|
|
JSONRPCMessage::Notification(JSONRPCNotification {
|
|
method: "notification".to_string(),
|
|
params: Some(json!({"enabled": true})),
|
|
}),
|
|
JSONRPCMessage::Response(JSONRPCResponse {
|
|
id: RequestId::String("response".to_string()),
|
|
result: json!({"value": "ok"}),
|
|
}),
|
|
JSONRPCMessage::Error(JSONRPCError {
|
|
error: JSONRPCErrorError {
|
|
code: -32603,
|
|
data: Some(json!({"retryable": false})),
|
|
message: "failed".to_string(),
|
|
},
|
|
id: RequestId::Integer(2),
|
|
}),
|
|
];
|
|
|
|
for expected in messages {
|
|
let encoded = serde_json::to_string(&expected)?;
|
|
let actual = serde_json::from_str::<JSONRPCMessage>(&encoded)?;
|
|
assert_eq!(actual, expected);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn round_trips_arbitrary_precision_numbers() -> serde_json::Result<()> {
|
|
let encoded = r#"{"method":"numbers","params":{"decimal":1.5,"exponent":1e100,"largeInteger":18446744073709551616}}"#;
|
|
let expected = serde_json::from_str::<Value>(encoded)?;
|
|
|
|
let message = serde_json::from_str::<JSONRPCMessage>(encoded)?;
|
|
let actual = serde_json::to_value(message)?;
|
|
|
|
assert_eq!(actual, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn applies_value_limit_to_raw_value_wrapper() -> serde_json::Result<()> {
|
|
let encoded =
|
|
format!(r#"{{"method":"raw","params":{{"{SERDE_JSON_RAW_VALUE_TOKEN}":"[0,1]"}}}}"#);
|
|
let actual = serde_json::from_str::<JSONRPCMessage>(&encoded)?;
|
|
let expected = JSONRPCMessage::Notification(JSONRPCNotification {
|
|
method: "raw".to_string(),
|
|
params: Some(json!([0, 1])),
|
|
});
|
|
assert_eq!(actual, expected);
|
|
|
|
let mut wrapped = String::with_capacity(2 * MAX_JSONRPC_VALUE_NODES + 1);
|
|
wrapped.push('[');
|
|
for index in 0..MAX_JSONRPC_VALUE_NODES {
|
|
if index != 0 {
|
|
wrapped.push(',');
|
|
}
|
|
wrapped.push('0');
|
|
}
|
|
wrapped.push(']');
|
|
let encoded =
|
|
format!(r#"{{"method":"raw","params":{{"{SERDE_JSON_RAW_VALUE_TOKEN}":"{wrapped}"}}}}"#);
|
|
|
|
let error = serde_json::from_str::<JSONRPCMessage>(&encoded)
|
|
.expect_err("raw value wrapper should not bypass the JSON value limit");
|
|
let expected_error = format!("exceeds the limit of {MAX_JSONRPC_VALUE_NODES} JSON values");
|
|
assert!(
|
|
error.to_string().contains(&expected_error),
|
|
"unexpected error: {error}"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn accepts_large_scalar_payload() -> serde_json::Result<()> {
|
|
let expected = JSONRPCMessage::Notification(JSONRPCNotification {
|
|
method: "large".to_string(),
|
|
params: Some(json!({"data": "x".repeat(MAX_JSONRPC_VALUE_NODES + 1)})),
|
|
});
|
|
|
|
let encoded = serde_json::to_string(&expected)?;
|
|
let actual = serde_json::from_str::<JSONRPCMessage>(&encoded)?;
|
|
|
|
assert_eq!(actual, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_duplicate_object_keys() {
|
|
let error = serde_json::from_str::<JSONRPCMessage>(r#"{"method":"safe","method":"dangerous"}"#)
|
|
.expect_err("duplicate JSON object keys should be rejected");
|
|
|
|
assert!(
|
|
error
|
|
.to_string()
|
|
.contains("duplicate JSON object key `method`"),
|
|
"unexpected error: {error}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_compact_array_heap_amplification() {
|
|
const REPRO_VALUE_COUNT: usize = 2_097_137;
|
|
const REPRO_MESSAGE_BYTES: usize = 4_194_303;
|
|
|
|
let mut encoded = String::with_capacity(REPRO_MESSAGE_BYTES);
|
|
encoded.push_str(r#"{"method":"probe","params":["#);
|
|
for index in 0..REPRO_VALUE_COUNT {
|
|
if index != 0 {
|
|
encoded.push(',');
|
|
}
|
|
encoded.push('0');
|
|
}
|
|
encoded.push_str("]}");
|
|
assert_eq!(encoded.len(), REPRO_MESSAGE_BYTES);
|
|
|
|
let error = serde_json::from_str::<JSONRPCMessage>(&encoded)
|
|
.expect_err("amplification payload should exceed the JSON value limit");
|
|
let expected_error = format!("exceeds the limit of {MAX_JSONRPC_VALUE_NODES} JSON values");
|
|
assert!(
|
|
error.to_string().contains(&expected_error),
|
|
"unexpected error: {error}"
|
|
);
|
|
}
|