140 lines
4.8 KiB
Rust
140 lines
4.8 KiB
Rust
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);
|
|
}
|