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>, } impl Fetcher for MapFetcher { fn fetch(&self, uri: &str) -> Result, 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(¬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); 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")); }