mod common; use std::net::Ipv6Addr; use common::test_helper::{ indent_block, payloads_to_string, test_report, v4_origin, v6_origin, }; use rpki::rtr::cache::{CacheAvailability, Delta, SessionIds, Snapshot}; use rpki::rtr::payload::Payload; use rpki::rtr::store::RtrStore; fn snapshot_to_string(snapshot: &Snapshot) -> String { let payloads = snapshot.payloads_for_rtr(); payloads_to_string(&payloads) } fn delta_to_string(delta: &Delta) -> String { format!( "serial: {}\nannounced:\n{}withdrawn:\n{}", delta.serial(), indent_block(&payloads_to_string(delta.announced()), 2), indent_block(&payloads_to_string(delta.withdrawn()), 2), ) } #[test] fn store_db_save_and_get_snapshot() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let input_payloads = vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), Payload::RouteOrigin(v6_origin( Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32, 48, 64497, )), ]; let snapshot = Snapshot::from_payloads(input_payloads.clone()); store.save_snapshot(&snapshot).unwrap(); let loaded = store.get_snapshot().unwrap().expect("snapshot should exist"); let input = format!( "db_path: {}\nsnapshot:\n{}", dir.path().display(), indent_block(&payloads_to_string(&input_payloads), 2), ); let output = format!( "loaded snapshot:\n{}same_content: {}\n", indent_block(&snapshot_to_string(&loaded), 2), snapshot.same_content(&loaded), ); test_report( "store_db_save_and_get_snapshot", "验证 save_snapshot() 后可以通过 get_snapshot() 正确读回 Snapshot。", &input, &output, ); assert!(snapshot.same_content(&loaded)); } #[test] fn store_db_set_and_get_meta_fields() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let session_ids = SessionIds::from_array([40, 41, 42]); store.set_session_ids(&session_ids).unwrap(); store.set_serial(100).unwrap(); store.set_delta_window(101, 110).unwrap(); let loaded_session_ids = store.get_session_ids().unwrap(); let serial = store.get_serial().unwrap(); let window = store.get_delta_window().unwrap(); let input = format!( "db_path: {}\nset_session_ids={:?}\nset_serial=100\nset_delta_window=(101, 110)\n", dir.path().display(), session_ids, ); let output = format!( "get_session_ids: {:?}\nget_serial: {:?}\nget_delta_window: {:?}\n", loaded_session_ids, serial, window, ); test_report( "store_db_set_and_get_meta_fields", "验证 session_ids / serial / delta_window 能正确写入并读回。", &input, &output, ); assert_eq!(loaded_session_ids, Some(session_ids)); assert_eq!(serial, Some(100)); assert_eq!(window, Some((101, 110))); } #[test] fn store_db_clear_delta_window_removes_both_bounds() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); store.set_delta_window(101, 110).unwrap(); assert_eq!(store.get_delta_window().unwrap(), Some((101, 110))); store.clear_delta_window().unwrap(); assert_eq!(store.get_delta_window().unwrap(), None); } #[test] fn store_db_save_and_get_delta() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let delta = Delta::new( 101, vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))], vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))], ); store.save_delta(&delta).unwrap(); let loaded = store.get_delta(101).unwrap().expect("delta should exist"); let input = format!( "db_path: {}\ndelta:\n{}", dir.path().display(), indent_block(&delta_to_string(&delta), 2), ); let output = format!( "loaded delta:\n{}", indent_block(&delta_to_string(&loaded), 2), ); test_report( "store_db_save_and_get_delta", "验证 save_delta() 后可以通过 get_delta(serial) 正确读回 Delta。", &input, &output, ); assert_eq!(loaded.serial(), 101); assert_eq!(loaded.announced().len(), 1); assert_eq!(loaded.withdrawn().len(), 1); } #[test] fn store_db_load_deltas_since_returns_only_newer_deltas_in_order() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let d101 = Delta::new( 101, vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))], vec![], ); let d102 = Delta::new( 102, vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))], vec![], ); let d103 = Delta::new( 103, vec![Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498))], vec![], ); store.save_delta(&d101).unwrap(); store.save_delta(&d102).unwrap(); store.save_delta(&d103).unwrap(); let loaded = store.load_deltas_since(101).unwrap(); let input = format!( "db_path: {}\nsaved delta serials: [101, 102, 103]\nload_deltas_since(101)\n", dir.path().display(), ); let output = { let mut s = String::new(); for (idx, d) in loaded.iter().enumerate() { s.push_str(&format!("loaded[{}]:\n", idx)); s.push_str(&indent_block(&delta_to_string(d), 2)); } s }; test_report( "store_db_load_deltas_since_returns_only_newer_deltas_in_order", "验证 load_deltas_since(x) 只返回 serial > x 的 Delta,且顺序正确。", &input, &output, ); assert_eq!(loaded.len(), 2); assert_eq!(loaded[0].serial(), 102); assert_eq!(loaded[1].serial(), 103); } #[test] fn store_db_save_snapshot_and_meta_writes_all_fields() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let session_ids = SessionIds::from_array([40, 41, 42]); let snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497)), ]); store .save_snapshot_and_meta(&snapshot, &session_ids, 100) .unwrap(); let loaded_snapshot = store.get_snapshot().unwrap().expect("snapshot should exist"); let loaded_session_ids = store.get_session_ids().unwrap(); let loaded_serial = store.get_serial().unwrap(); let input = format!( "db_path: {}\nsnapshot:\n{}session_ids={:?}\nserial=100\n", dir.path().display(), indent_block(&snapshot_to_string(&snapshot), 2), session_ids, ); let output = format!( "loaded_snapshot:\n{}loaded_session_ids: {:?}\nloaded_serial: {:?}\n", indent_block(&snapshot_to_string(&loaded_snapshot), 2), loaded_session_ids, loaded_serial, ); test_report( "store_db_save_snapshot_and_meta_writes_all_fields", "验证 save_snapshot_and_meta() 会同时写入 snapshot、session_ids 和 serial。", &input, &output, ); assert!(snapshot.same_content(&loaded_snapshot)); assert_eq!(loaded_session_ids, Some(session_ids)); assert_eq!(loaded_serial, Some(100)); } #[test] fn store_db_save_cache_state_writes_delta_snapshot_meta_and_window_together() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let session_ids = SessionIds::from_array([40, 41, 42]); let snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497)), ]); let delta = Delta::new( 101, vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))], vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))], ); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, 101, Some(&delta), Some((101, 101)), false, ) .unwrap(); let loaded_snapshot = store.get_snapshot().unwrap().expect("snapshot should exist"); let loaded_session_ids = store.get_session_ids().unwrap(); let loaded_serial = store.get_serial().unwrap(); let loaded_availability = store.get_availability().unwrap(); let loaded_delta = store.get_delta(101).unwrap().expect("delta should exist"); let loaded_window = store.get_delta_window().unwrap(); assert!(snapshot.same_content(&loaded_snapshot)); assert_eq!(loaded_session_ids, Some(session_ids)); assert_eq!(loaded_serial, Some(101)); assert_eq!(loaded_availability, Some(CacheAvailability::Ready)); assert_eq!(loaded_delta.serial(), 101); assert_eq!(loaded_window, Some((101, 101))); } #[test] fn store_db_save_cache_state_prunes_deltas_older_than_window_min() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let session_ids = SessionIds::from_array([40, 41, 42]); let snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), ]); let d101 = Delta::new( 101, vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))], vec![], ); let d102 = Delta::new( 102, vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))], vec![], ); let d103 = Delta::new( 103, vec![Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498))], vec![], ); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, 101, Some(&d101), Some((101, 101)), false, ) .unwrap(); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, 102, Some(&d102), Some((101, 102)), false, ) .unwrap(); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, 103, Some(&d103), Some((103, 103)), false, ) .unwrap(); assert!(store.get_delta(101).unwrap().is_none()); assert!(store.get_delta(102).unwrap().is_none()); assert_eq!(store.get_delta(103).unwrap().map(|d| d.serial()), Some(103)); assert_eq!(store.get_delta_window().unwrap(), Some((103, 103))); } #[test] fn store_db_load_delta_window_restores_wraparound_window_in_serial_order() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let session_ids = SessionIds::from_array([40, 41, 42]); let snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), ]); let d_max = Delta::new( u32::MAX, vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))], vec![], ); let d_zero = Delta::new( 0, vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))], vec![], ); let d_one = Delta::new( 1, vec![Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498))], vec![], ); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, u32::MAX, Some(&d_max), Some((u32::MAX, u32::MAX)), false, ) .unwrap(); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, 0, Some(&d_zero), Some((u32::MAX, 0)), false, ) .unwrap(); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, 1, Some(&d_one), Some((u32::MAX, 1)), false, ) .unwrap(); let loaded = store.load_delta_window(u32::MAX, 1).unwrap(); assert_eq!(loaded.iter().map(Delta::serial).collect::>(), vec![u32::MAX, 0, 1]); } #[test] fn store_db_load_snapshot_and_serial_returns_consistent_pair() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498)), ]); store.save_snapshot_and_serial(&snapshot, 200).unwrap(); let loaded = store .load_snapshot_and_serial() .unwrap() .expect("snapshot+serial should exist"); let input = format!( "db_path: {}\nsnapshot:\n{}serial=200\n", dir.path().display(), indent_block(&snapshot_to_string(&snapshot), 2), ); let output = format!( "loaded_snapshot:\n{}loaded_serial: {}\n", indent_block(&snapshot_to_string(&loaded.0), 2), loaded.1, ); test_report( "store_db_load_snapshot_and_serial_returns_consistent_pair", "验证 load_snapshot_and_serial() 能正确返回一致的 snapshot 与 serial。", &input, &output, ); assert!(snapshot.same_content(&loaded.0)); assert_eq!(loaded.1, 200); } #[test] fn store_db_delete_snapshot_delta_and_serial_removes_data() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), ]); let delta = Delta::new( 101, vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))], vec![], ); store.save_snapshot(&snapshot).unwrap(); store.save_delta(&delta).unwrap(); store.set_serial(100).unwrap(); store.delete_snapshot().unwrap(); store.delete_delta(101).unwrap(); store.delete_serial().unwrap(); let loaded_snapshot = store.get_snapshot().unwrap(); let loaded_delta = store.get_delta(101).unwrap(); let loaded_serial = store.get_serial().unwrap(); let input = format!( "db_path: {}\nsave snapshot + delta(101) + serial(100), then delete all three.\n", dir.path().display(), ); let output = format!( "get_snapshot: {:?}\nget_delta(101): {:?}\nget_serial: {:?}\n", loaded_snapshot.as_ref().map(|_| "Some(snapshot)"), loaded_delta.as_ref().map(|_| "Some(delta)"), loaded_serial, ); test_report( "store_db_delete_snapshot_delta_and_serial_removes_data", "验证 delete_snapshot()/delete_delta()/delete_serial() 后,对应数据不再可读。", &input, &output, ); assert!(loaded_snapshot.is_none()); assert!(loaded_delta.is_none()); assert!(loaded_serial.is_none()); } #[test] fn store_db_load_snapshot_and_serial_errors_on_inconsistent_state() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), ]); store.save_snapshot(&snapshot).unwrap(); // 故意不写 serial,制造不一致状态 let result = store.load_snapshot_and_serial(); let input = format!( "db_path: {}\n仅保存 snapshot,不保存 serial。\n", dir.path().display(), ); let output = format!("load_snapshot_and_serial result: {:?}\n", result); test_report( "store_db_load_snapshot_and_serial_errors_on_inconsistent_state", "验证当 snapshot 和 serial 状态不一致时,load_snapshot_and_serial() 返回错误。", &input, &output, ); assert!(result.is_err()); }