20260720 规范化 CCR Manifest location 导出
This commit is contained in:
139
tests/test_ccr_manifest_location_interop.rs
Normal file
139
tests/test_ccr_manifest_location_interop.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use rpki::ccr::{CcrAccumulator, build_roa_payload_state};
|
||||
use rpki::data_model::roa::{IpPrefix, RoaAfi};
|
||||
use rpki::storage::{PackTime, VcirCcrManifestProjection};
|
||||
use rpki::validation::objects::Vrp;
|
||||
|
||||
const MANIFEST_URI: &str = "rsync://example.test/repo/current.mft";
|
||||
const OID_AD_SIGNED_OBJECT: &[u8] = &[0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x0b];
|
||||
const OID_AD_RPKI_NOTIFY: &[u8] = &[0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x0d];
|
||||
|
||||
fn der_length(len: usize) -> Vec<u8> {
|
||||
if len < 0x80 {
|
||||
return vec![len as u8];
|
||||
}
|
||||
let mut bytes = len.to_be_bytes().to_vec();
|
||||
let first = bytes
|
||||
.iter()
|
||||
.position(|byte| *byte != 0)
|
||||
.expect("non-zero length");
|
||||
bytes.drain(..first);
|
||||
let mut out = vec![0x80 | bytes.len() as u8];
|
||||
out.extend_from_slice(&bytes);
|
||||
out
|
||||
}
|
||||
|
||||
fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
|
||||
let mut out = vec![tag];
|
||||
out.extend(der_length(value.len()));
|
||||
out.extend_from_slice(value);
|
||||
out
|
||||
}
|
||||
|
||||
fn access_description_der(access_method_oid: &[u8], uri: &str) -> Vec<u8> {
|
||||
let oid = der_tlv(0x06, access_method_oid);
|
||||
let location = der_tlv(0x86, uri.as_bytes());
|
||||
let mut body = oid;
|
||||
body.extend(location);
|
||||
der_tlv(0x30, &body)
|
||||
}
|
||||
|
||||
fn projection(locations: Vec<Vec<u8>>) -> VcirCcrManifestProjection {
|
||||
VcirCcrManifestProjection {
|
||||
manifest_rsync_uri: MANIFEST_URI.to_string(),
|
||||
manifest_sha256: vec![0x11; 32],
|
||||
manifest_size: 1000,
|
||||
manifest_ee_aki: vec![0x22; 20],
|
||||
manifest_number_be: vec![1],
|
||||
manifest_this_update: PackTime::from_utc_offset_datetime(time::OffsetDateTime::now_utc()),
|
||||
manifest_sia_locations_der: locations,
|
||||
subordinate_skis: vec![vec![0x33; 20]],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn historical_projection_normalizes_locations_and_accounts_memory() {
|
||||
let signed_object = access_description_der(OID_AD_SIGNED_OBJECT, MANIFEST_URI);
|
||||
let notify = access_description_der(
|
||||
OID_AD_RPKI_NOTIFY,
|
||||
"https://rrdp.example.test/notification.xml",
|
||||
);
|
||||
let projection = projection(vec![notify, signed_object]);
|
||||
|
||||
let mut accumulator = CcrAccumulator::new(Vec::new());
|
||||
accumulator
|
||||
.append_manifest_projection(&projection)
|
||||
.expect("append normalized historical projection");
|
||||
accumulator
|
||||
.append_manifest_projection(&projection)
|
||||
.expect("duplicate matching projection is idempotent");
|
||||
|
||||
assert_eq!(accumulator.manifest_count(), 1);
|
||||
let stats = accumulator.memory_stats();
|
||||
assert_eq!(stats.manifest_count, 1);
|
||||
assert_eq!(stats.locations_der_count, 1);
|
||||
assert_eq!(stats.subordinate_ski_count, 1);
|
||||
assert!(stats.estimated_heap_bytes > 0);
|
||||
assert!(stats.btree_entry_shallow_bytes > 0);
|
||||
|
||||
let mut conflicting = projection;
|
||||
conflicting.subordinate_skis.push(vec![0x44; 20]);
|
||||
let error = accumulator
|
||||
.append_manifest_projection(&conflicting)
|
||||
.expect_err("same hash with different projection must fail");
|
||||
assert!(error.contains("duplicate manifest hash"), "{error}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn historical_projection_rejects_invalid_location_sets() {
|
||||
let mut accumulator = CcrAccumulator::new(Vec::new());
|
||||
|
||||
let empty_error = accumulator
|
||||
.append_manifest_projection(&projection(Vec::new()))
|
||||
.expect_err("empty location set must fail");
|
||||
assert!(empty_error.contains("contains 0"), "{empty_error}");
|
||||
|
||||
let signed_object = access_description_der(OID_AD_SIGNED_OBJECT, MANIFEST_URI);
|
||||
let duplicate_error = accumulator
|
||||
.append_manifest_projection(&projection(vec![signed_object.clone(), signed_object]))
|
||||
.expect_err("duplicate matching locations must fail");
|
||||
assert!(duplicate_error.contains("contains 2"), "{duplicate_error}");
|
||||
|
||||
let malformed_error = accumulator
|
||||
.append_manifest_projection(&projection(vec![vec![0x30, 0x01, 0x06]]))
|
||||
.expect_err("malformed location DER must fail");
|
||||
assert!(
|
||||
malformed_error.contains("truncated DER"),
|
||||
"{malformed_error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roa_payload_builder_encodes_large_non_byte_aligned_family() {
|
||||
let mut vrps = Vec::new();
|
||||
for octet in 0..=255 {
|
||||
vrps.push(Vrp {
|
||||
asn: 64_496,
|
||||
prefix: IpPrefix {
|
||||
afi: RoaAfi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: [10, octet, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
max_length: 24,
|
||||
});
|
||||
}
|
||||
vrps.push(Vrp {
|
||||
asn: 64_496,
|
||||
prefix: IpPrefix {
|
||||
afi: RoaAfi::Ipv4,
|
||||
prefix_len: 13,
|
||||
addr: [10, 0x1f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
max_length: 24,
|
||||
});
|
||||
|
||||
let state = build_roa_payload_state(&vrps).expect("build ROA payload state");
|
||||
assert_eq!(state.rps.len(), 1);
|
||||
assert_eq!(state.rps[0].as_id, 64_496);
|
||||
assert_eq!(state.rps[0].ip_addr_blocks.len(), 1);
|
||||
assert!(state.rps[0].ip_addr_blocks[0].len() > 128);
|
||||
}
|
||||
Reference in New Issue
Block a user