169 lines
5.6 KiB
Rust
169 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}"))
|
|
}
|
|
}
|
|
|
|
fn assert_current_object(store: &RocksStore, uri: &str, expected: &[u8]) {
|
|
assert_eq!(
|
|
store
|
|
.load_current_object_bytes_by_uri(uri)
|
|
.expect("load current object"),
|
|
Some(expected.to_vec())
|
|
);
|
|
}
|
|
|
|
#[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(¬ification_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",
|
|
¬ification_xml,
|
|
&fetcher,
|
|
)
|
|
.expect("sync");
|
|
assert_eq!(published, 2);
|
|
|
|
assert_current_object(&store, "rsync://example.net/repo/obj1.cer", b"abc");
|
|
assert_current_object(&store, "rsync://example.net/repo/obj2.crl", b"def");
|
|
|
|
let source = store
|
|
.get_rrdp_source_record("https://example.net/rrdp/notification.xml")
|
|
.expect("get source")
|
|
.expect("source exists");
|
|
assert_eq!(
|
|
source.last_session_id.expect("session id"),
|
|
"9df4b597-af9e-4dca-bdda-719cce2c4e28".to_string()
|
|
);
|
|
assert_eq!(source.last_serial, Some(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"));
|
|
}
|