fix(core): harden wire validation and queues
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use versevdi_core::input::{decode_feedback, decode_input, encode_feedback, encode_input};
|
||||
use versevdi_core::input::{
|
||||
decode_feedback, decode_input, encode_feedback, encode_input, FecStatus, FeedbackEvent,
|
||||
};
|
||||
use versevdi_core::media::{MediaFragment, Reassembler};
|
||||
use versevdi_core::wire::{
|
||||
CapabilityProfile, ClientSessionAuthority, ConnectionManifest, NativeTunnelCredential,
|
||||
@@ -150,6 +152,23 @@ fn rc5_manifest_credential_and_authority_enforce_bounds_and_bindings() {
|
||||
assert!(ClientSessionAuthority::decode(provider_authority).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_tunnel_credential_rejects_private_key_pem_in_certificate_fields() {
|
||||
for field in ["certificate_chain_pem", "trust_bundle_pem"] {
|
||||
let credential = r#"{"client_device_id":"device","device_key_id":"key","certificate_chain_pem":"certificate","trust_bundle_pem":"root","expires_at":"2099-01-01T00:00:00Z"}"#
|
||||
.to_owned()
|
||||
.replacen(
|
||||
&format!(r#""{field}":"{}""#, if field == "certificate_chain_pem" { "certificate" } else { "root" }),
|
||||
&format!(r#""{field}":"-----BEGIN PRIVATE KEY-----\nsecret\n-----END PRIVATE KEY-----""#),
|
||||
1,
|
||||
);
|
||||
assert!(
|
||||
NativeTunnelCredential::decode(credential.as_bytes()).is_err(),
|
||||
"private key armor accepted in {field}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expiry_mismatch_and_capability_escalation_are_rejected() {
|
||||
let manifest = ConnectionManifest::decode(valid_manifest()).expect("valid manifest");
|
||||
@@ -210,6 +229,24 @@ fn admission_transcript_matches_rc5_literal() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admission_rejects_non_raw_base64url_nonce_and_signature() {
|
||||
for (nonce, signature) in [
|
||||
("!".repeat(16), "A".repeat(86)),
|
||||
("A".repeat(17), "A".repeat(86)),
|
||||
("A".repeat(16), "!".repeat(86)),
|
||||
("A".repeat(16), format!("{}B", "A".repeat(85))),
|
||||
] {
|
||||
let request = format!(
|
||||
r#"{{"version":"1","session_id":"session","gateway_id":"gateway","audience":"audience","grant":"{}","reconnect_sequence":0,"client_nonce":"{}","device_signature":"{}","capabilities":{{"transport":"quic-tls13","framing":"datagram-v2","media":"encoded","audio":"encoded","source_rate_control":"server","client_decode":["h264-opus"]}}}}"#,
|
||||
"g".repeat(43),
|
||||
nonce,
|
||||
signature,
|
||||
);
|
||||
assert!(TunnelAdmissionRequest::decode(request.as_bytes()).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_hex(value: &str) -> Vec<u8> {
|
||||
value
|
||||
.as_bytes()
|
||||
@@ -371,3 +408,70 @@ fn input_features_and_feedback_booleans_fail_closed() {
|
||||
"length"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fec_feedback_enforces_rc5_go_field_invariants_on_decode_and_encode() {
|
||||
for invalid in [
|
||||
"56474631000200150000002a0005000300020000000200080002140001",
|
||||
"56474631000200150000002a000500030002000a0002000b0002140001",
|
||||
"56474631000200150000002a000500030002000a000200080003140001",
|
||||
"56474631000200150000002a000500030002000a000200080002650001",
|
||||
"56474631000200150000002a000500030002000a000200080002140000",
|
||||
"56474631000200150000002a000500030002000a000200080002140101",
|
||||
] {
|
||||
assert_eq!(
|
||||
decode_feedback(&decode_hex(invalid))
|
||||
.expect_err("invalid FEC status")
|
||||
.code(),
|
||||
"field"
|
||||
);
|
||||
}
|
||||
|
||||
let valid = FecStatus {
|
||||
frame_index: 42,
|
||||
highest_received_sequence: 5,
|
||||
next_contiguous_sequence: 3,
|
||||
missing_before_highest: 2,
|
||||
total_data_packets: 10,
|
||||
total_parity_packets: 2,
|
||||
received_data_packets: 8,
|
||||
received_parity_packets: 2,
|
||||
fec_percentage: 20,
|
||||
multi_fec_block_index: 0,
|
||||
multi_fec_block_count: 1,
|
||||
};
|
||||
let invalid = [
|
||||
FecStatus {
|
||||
total_data_packets: 0,
|
||||
..valid.clone()
|
||||
},
|
||||
FecStatus {
|
||||
received_data_packets: 11,
|
||||
..valid.clone()
|
||||
},
|
||||
FecStatus {
|
||||
received_parity_packets: 3,
|
||||
..valid.clone()
|
||||
},
|
||||
FecStatus {
|
||||
fec_percentage: 101,
|
||||
..valid.clone()
|
||||
},
|
||||
FecStatus {
|
||||
multi_fec_block_count: 0,
|
||||
..valid.clone()
|
||||
},
|
||||
FecStatus {
|
||||
multi_fec_block_index: 1,
|
||||
..valid
|
||||
},
|
||||
];
|
||||
for status in invalid {
|
||||
assert_eq!(
|
||||
encode_feedback(&FeedbackEvent::Fec(status))
|
||||
.expect_err("invalid FEC status")
|
||||
.code(),
|
||||
"field"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user