rpki/tests/test_rrdp_sync_m5.rs
2026-02-09 19:35:54 +08:00

170 lines
5.6 KiB
Rust

use std::collections::HashMap;
use rpki::storage::RocksStore;
use rpki::sync::rrdp::{Fetcher, parse_notification_snapshot, sync_from_notification_snapshot};
use sha2::Digest;
struct MapFetcher {
by_uri: HashMap<String, Vec<u8>>,
}
impl Fetcher for MapFetcher {
fn fetch(&self, uri: &str) -> Result<Vec<u8>, String> {
self.by_uri
.get(uri)
.cloned()
.ok_or_else(|| format!("not found: {uri}"))
}
}
#[test]
fn notification_parses_and_snapshot_is_applied_and_state_written() {
let notification_xml =
std::fs::read("tests/fixtures/rrdp/notification.xml").expect("read notification");
let snapshot_xml = std::fs::read("tests/fixtures/rrdp/snapshot.xml").expect("read snapshot");
let notif = parse_notification_snapshot(&notification_xml).expect("parse notification");
assert_eq!(notif.serial, 1);
assert_eq!(notif.snapshot_uri, "https://example.net/rrdp/snapshot.xml");
let mut fetcher = MapFetcher {
by_uri: HashMap::new(),
};
fetcher.by_uri.insert(
"https://example.net/rrdp/snapshot.xml".to_string(),
snapshot_xml,
);
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
let published = sync_from_notification_snapshot(
&store,
"https://example.net/rrdp/notification.xml",
&notification_xml,
&fetcher,
)
.expect("sync");
assert_eq!(published, 2);
let obj1 = store
.get_raw("rsync://example.net/repo/obj1.cer")
.expect("get obj1")
.expect("obj1 exists");
assert_eq!(obj1, b"abc");
let obj2 = store
.get_raw("rsync://example.net/repo/obj2.crl")
.expect("get obj2")
.expect("obj2 exists");
assert_eq!(obj2, b"def");
let state_bytes = store
.get_rrdp_state("https://example.net/rrdp/notification.xml")
.expect("get state")
.expect("state exists");
let state = rpki::sync::rrdp::RrdpState::decode(&state_bytes).expect("decode state");
assert_eq!(
state.session_id,
"9df4b597-af9e-4dca-bdda-719cce2c4e28".to_string()
);
assert_eq!(state.serial, 1);
}
#[test]
fn snapshot_hash_mismatch_is_rejected() {
let mut notification_xml =
String::from_utf8(std::fs::read("tests/fixtures/rrdp/notification.xml").unwrap()).unwrap();
notification_xml = notification_xml.replace(
"dcb1ce91401d568d7ddf7a4c9f70c65d8428c3a5e7135f82db99c4de30413551",
"0000000000000000000000000000000000000000000000000000000000000000",
);
let snapshot_xml = std::fs::read("tests/fixtures/rrdp/snapshot.xml").expect("read snapshot");
let fetcher = MapFetcher {
by_uri: HashMap::from([(
"https://example.net/rrdp/snapshot.xml".to_string(),
snapshot_xml,
)]),
};
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
let err = sync_from_notification_snapshot(
&store,
"https://example.net/rrdp/notification.xml",
notification_xml.as_bytes(),
&fetcher,
)
.expect_err("hash mismatch rejected");
assert!(err.to_string().contains("hash mismatch"));
}
#[test]
fn session_id_mismatch_is_rejected() {
let notification_xml =
std::fs::read("tests/fixtures/rrdp/notification.xml").expect("read notification");
let mut snapshot_xml =
String::from_utf8(std::fs::read("tests/fixtures/rrdp/snapshot.xml").unwrap()).unwrap();
snapshot_xml = snapshot_xml.replace(
"9df4b597-af9e-4dca-bdda-719cce2c4e28",
"00000000-0000-4000-8000-000000000000",
);
let snapshot_xml = snapshot_xml.into_bytes();
// Recompute snapshot hash and patch notification to keep hash correct, so we test
// the session_id mismatch check.
let mut notif_str = String::from_utf8(notification_xml).unwrap();
let digest = sha2::Sha256::digest(&snapshot_xml);
let hex_hash = hex::encode(digest);
notif_str = notif_str.replace(
"dcb1ce91401d568d7ddf7a4c9f70c65d8428c3a5e7135f82db99c4de30413551",
&hex_hash,
);
let fetcher = MapFetcher {
by_uri: HashMap::from([(
"https://example.net/rrdp/snapshot.xml".to_string(),
snapshot_xml,
)]),
};
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
let err = sync_from_notification_snapshot(
&store,
"https://example.net/rrdp/notification.xml",
notif_str.as_bytes(),
&fetcher,
)
.expect_err("session_id mismatch rejected");
assert!(err.to_string().contains("session_id mismatch"));
}
#[test]
fn serial_zero_is_rejected() {
let notification_xml =
String::from_utf8(std::fs::read("tests/fixtures/rrdp/notification.xml").unwrap()).unwrap();
let notification_xml = notification_xml.replace("serial=\"1\"", "serial=\"0\"");
let snapshot_xml = std::fs::read("tests/fixtures/rrdp/snapshot.xml").expect("read snapshot");
let fetcher = MapFetcher {
by_uri: HashMap::from([(
"https://example.net/rrdp/snapshot.xml".to_string(),
snapshot_xml,
)]),
};
let temp = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(temp.path()).expect("open rocksdb");
let err = sync_from_notification_snapshot(
&store,
"https://example.net/rrdp/notification.xml",
notification_xml.as_bytes(),
&fetcher,
)
.expect_err("serial=0 rejected");
assert!(err.to_string().contains("serial invalid"));
}