545 lines
16 KiB
Rust
545 lines
16 KiB
Rust
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<_>>(), 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());
|
||
}
|