rpki/tests/test_store_db.rs
2026-06-23 17:04:00 +08:00

435 lines
12 KiB
Rust

mod common;
use std::net::{Ipv4Addr, Ipv6Addr};
use common::test_helper::{v4_origin, v6_origin};
use rpki::data_model::resources::as_resources::Asn;
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
use rpki::rtr::cache::{CacheAvailability, Delta, Snapshot};
use rpki::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Ski};
use rpki::rtr::store::RtrStore;
#[test]
fn store_db_versioned_state_persists_and_restores_all_versions() {
let dir = tempfile::tempdir().unwrap();
let store = RtrStore::open(dir.path()).unwrap();
let source_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)),
Payload::RouteOrigin(v6_origin(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
32,
48,
64498,
)),
]);
let snapshots =
std::array::from_fn(|version| source_snapshot.project_for_version(version as u8));
let session_ids = [410u16, 411u16, 412u16];
let serials = [100u32, 200u32, 300u32];
let d0 = Delta::new(
100,
vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))],
vec![],
);
let d2 = Delta::new(
300,
vec![Payload::RouteOrigin(v6_origin(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
32,
48,
64498,
))],
vec![],
);
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[Some(&d0), None, Some(&d2)],
&[Some((100, 100)), None, Some((300, 300))],
&[false, false, false],
)
.unwrap();
assert_eq!(
store.get_availability().unwrap(),
Some(CacheAvailability::Ready)
);
for version in 0u8..=2 {
let idx = version as usize;
let loaded_snapshot = store
.get_snapshot_for_version(version)
.unwrap()
.expect("snapshot should exist");
let loaded_session_id = store
.get_session_id_for_version(version)
.unwrap()
.expect("session_id should exist");
let loaded_serial = store
.get_serial_for_version(version)
.unwrap()
.expect("serial should exist");
assert!(snapshots[idx].same_content(&loaded_snapshot));
assert_eq!(loaded_session_id, session_ids[idx]);
assert_eq!(loaded_serial, serials[idx]);
}
assert_eq!(
store.get_delta_window_for_version(0).unwrap(),
Some((100, 100))
);
assert_eq!(store.get_delta_window_for_version(1).unwrap(), None);
assert_eq!(
store.get_delta_window_for_version(2).unwrap(),
Some((300, 300))
);
assert_eq!(
store
.get_delta_for_version(0, 100)
.unwrap()
.map(|d| d.serial()),
Some(100)
);
assert!(store.get_delta_for_version(1, 200).unwrap().is_none());
assert_eq!(
store
.get_delta_for_version(2, 300)
.unwrap()
.map(|d| d.serial()),
Some(300)
);
}
#[test]
fn save_cache_state_persists_single_canonical_snapshot_for_all_versions() {
let temp = tempfile::tempdir().unwrap();
let store = RtrStore::open(temp.path()).unwrap();
let source = mixed_snapshot();
let snapshots = std::array::from_fn(|version| source.project_for_version(version as u8));
let deltas: [Option<&Delta>; 3] = [None, None, None];
let windows: [Option<(u32, u32)>; 3] = [None, None, None];
let clear = [true, true, true];
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&[1, 2, 3],
&[10, 11, 12],
&deltas,
&windows,
&clear,
)
.unwrap();
let restored_v0 = store.get_snapshot_for_version(0).unwrap().unwrap();
assert_eq!(restored_v0.origins().len(), 1);
assert_eq!(restored_v0.router_keys().len(), 0);
assert_eq!(restored_v0.aspas().len(), 0);
let restored_v1 = store.get_snapshot_for_version(1).unwrap().unwrap();
assert_eq!(restored_v1.origins().len(), 1);
assert_eq!(restored_v1.router_keys().len(), 1);
assert_eq!(restored_v1.aspas().len(), 0);
let restored_v2 = store.get_snapshot_for_version(2).unwrap().unwrap();
assert_eq!(restored_v2.origins().len(), 1);
assert_eq!(restored_v2.router_keys().len(), 1);
assert_eq!(restored_v2.aspas().len(), 1);
}
fn mixed_snapshot() -> Snapshot {
Snapshot::from_payloads(vec![
Payload::RouteOrigin(RouteOrigin::new(
IPAddressPrefix::new(IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)), 24),
24,
Asn::from(64496),
)),
Payload::RouterKey(RouterKey::new(
Ski::default(),
Asn::from(64497),
vec![1, 2, 3],
)),
Payload::Aspa(Aspa::new(Asn::from(64498), vec![Asn::from(64499)])),
])
}
#[test]
fn store_db_versioned_delta_window_wraparound_is_isolated_by_version() {
let dir = tempfile::tempdir().unwrap();
let store = RtrStore::open(dir.path()).unwrap();
let snapshots = std::array::from_fn(|_| Snapshot::empty());
let session_ids = [600u16, 601u16, 602u16];
let serials = [0u32, 0u32, 0u32];
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![],
);
let d_v1_only = Delta::new(
0,
vec![Payload::RouteOrigin(v4_origin(10, 0, 0, 0, 24, 24, 64500))],
vec![],
);
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[None, None, Some(&d_max)],
&[None, None, None],
&[false, false, false],
)
.unwrap();
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[None, None, Some(&d_zero)],
&[None, None, None],
&[false, false, false],
)
.unwrap();
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[None, None, Some(&d_one)],
&[None, None, None],
&[false, false, false],
)
.unwrap();
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[None, Some(&d_v1_only), None],
&[None, None, None],
&[false, false, false],
)
.unwrap();
let v2_loaded = store.load_delta_window_for_version(2, u32::MAX, 1).unwrap();
assert_eq!(
v2_loaded.iter().map(Delta::serial).collect::<Vec<_>>(),
vec![u32::MAX, 0, 1]
);
let v1_loaded = store.load_delta_window_for_version(1, 0, 0).unwrap();
assert_eq!(
v1_loaded.iter().map(Delta::serial).collect::<Vec<_>>(),
vec![0]
);
assert_eq!(v1_loaded[0].announced().len(), 1);
}
#[test]
fn store_db_versioned_clear_window_affects_only_target_version() {
let dir = tempfile::tempdir().unwrap();
let store = RtrStore::open(dir.path()).unwrap();
let snapshots = [
Snapshot::from_payloads(vec![Payload::RouteOrigin(v4_origin(
192, 0, 2, 0, 24, 24, 64496,
))]),
Snapshot::from_payloads(vec![Payload::RouteOrigin(v4_origin(
198, 51, 100, 0, 24, 24, 64497,
))]),
Snapshot::from_payloads(vec![Payload::RouteOrigin(v4_origin(
203, 0, 113, 0, 24, 24, 64498,
))]),
];
let session_ids = [420u16, 421u16, 422u16];
let serials = [10u32, 20u32, 30u32];
let d0 = Delta::new(
10,
vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))],
vec![],
);
let d2 = Delta::new(
30,
vec![Payload::RouteOrigin(v4_origin(
203, 0, 113, 0, 24, 24, 64498,
))],
vec![],
);
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[Some(&d0), None, Some(&d2)],
&[Some((10, 10)), None, Some((30, 30))],
&[false, false, false],
)
.unwrap();
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[None, None, None],
&[None, None, None],
&[true, false, false],
)
.unwrap();
assert_eq!(store.get_delta_window_for_version(0).unwrap(), None);
assert!(store.get_delta_for_version(0, 10).unwrap().is_none());
assert_eq!(
store.get_delta_window_for_version(2).unwrap(),
Some((30, 30))
);
assert_eq!(
store
.get_delta_for_version(2, 30)
.unwrap()
.map(|d| d.serial()),
Some(30)
);
}
#[test]
fn store_db_versioned_prunes_outside_window() {
let dir = tempfile::tempdir().unwrap();
let store = RtrStore::open(dir.path()).unwrap();
let snapshots = std::array::from_fn(|_| {
Snapshot::from_payloads(vec![Payload::RouteOrigin(v4_origin(
192, 0, 2, 0, 24, 24, 64496,
))])
});
let session_ids = [500u16, 501u16, 502u16];
let serials = [102u32, 0u32, 0u32];
let d100 = Delta::new(
100,
vec![Payload::RouteOrigin(v4_origin(10, 0, 0, 0, 24, 24, 65001))],
vec![],
);
let d101 = Delta::new(
101,
vec![Payload::RouteOrigin(v4_origin(10, 0, 1, 0, 24, 24, 65002))],
vec![],
);
let d102 = Delta::new(
102,
vec![Payload::RouteOrigin(v4_origin(10, 0, 2, 0, 24, 24, 65003))],
vec![],
);
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[Some(&d100), None, None],
&[Some((100, 100)), None, None],
&[false, false, false],
)
.unwrap();
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[Some(&d101), None, None],
&[Some((100, 101)), None, None],
&[false, false, false],
)
.unwrap();
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[Some(&d102), None, None],
&[Some((102, 102)), None, None],
&[false, false, false],
)
.unwrap();
assert!(store.get_delta_for_version(0, 100).unwrap().is_none());
assert!(store.get_delta_for_version(0, 101).unwrap().is_none());
assert_eq!(
store
.get_delta_for_version(0, 102)
.unwrap()
.map(|d| d.serial()),
Some(102)
);
}
#[test]
fn store_db_versioned_load_delta_window_requires_complete_range() {
let dir = tempfile::tempdir().unwrap();
let store = RtrStore::open(dir.path()).unwrap();
let snapshots = std::array::from_fn(|_| Snapshot::empty());
let session_ids = [700u16, 701u16, 702u16];
let serials = [0u32, 0u32, 0u32];
let d11 = Delta::new(
11,
vec![Payload::RouteOrigin(v4_origin(
198, 51, 100, 0, 24, 24, 64497,
))],
vec![],
);
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&session_ids,
&serials,
&[None, Some(&d11), None],
&[None, None, None],
&[false, false, false],
)
.unwrap();
let err = store.load_delta_window_for_version(1, 10, 11).unwrap_err();
assert!(
err.to_string()
.contains("delta window starts at 10, but first persisted delta is Some(11)")
);
}