rpki/tests/test_cache.rs

743 lines
23 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

mod common;
use std::collections::VecDeque;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::sync::Arc;
use common::test_helper::{
as_route_origin, as_v4_route_origin, indent_block, payloads_to_string,
serial_result_detail_to_string, snapshot_hashes_to_string, test_report, v4_origin, v6_origin,
};
use rpki::rtr::cache::{Delta, RtrCacheBuilder, SerialResult, Snapshot};
use rpki::rtr::payload::{Payload, Timing};
use rpki::rtr::store_db::RtrStore;
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),
)
}
fn deltas_window_to_string(deltas: &VecDeque<Arc<Delta>>) -> String {
if deltas.is_empty() {
return " <empty>\n".to_string();
}
let mut out = String::new();
for (idx, delta) in deltas.iter().enumerate() {
out.push_str(&format!("delta[{}]:\n", idx));
out.push_str(&indent_block(&delta_to_string(delta), 2));
}
out
}
fn get_deltas_since_input_to_string(
cache_session_id: u16,
cache_serial: u32,
client_session: u16,
client_serial: u32,
) -> String {
format!(
"cache.session_id: {}\ncache.serial: {}\nclient_session: {}\nclient_serial: {}\n",
cache_session_id, cache_serial, client_session, client_serial
)
}
fn snapshot_hashes_and_sorted_view_to_string(snapshot: &Snapshot) -> String {
let payloads = snapshot.payloads_for_rtr();
format!(
"hashes:\n{}sorted payloads_for_rtr:\n{}",
indent_block(&snapshot_hashes_to_string(snapshot), 2),
indent_block(&payloads_to_string(&payloads), 2),
)
}
#[test]
fn snapshot_hash_is_stable_for_same_content_with_different_input_order() {
let a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
let b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
let c = v6_origin(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
32,
48,
64498,
);
let s1_input = vec![
Payload::RouteOrigin(a.clone()),
Payload::RouteOrigin(b.clone()),
Payload::RouteOrigin(c.clone()),
];
let s2_input = vec![
Payload::RouteOrigin(c),
Payload::RouteOrigin(a),
Payload::RouteOrigin(b),
];
let s1 = Snapshot::from_payloads(s1_input.clone());
let s2 = Snapshot::from_payloads(s2_input.clone());
let input = format!(
"s1 原始输入 payloads:\n{}\ns2 原始输入 payloads:\n{}",
indent_block(&payloads_to_string(&s1_input), 2),
indent_block(&payloads_to_string(&s2_input), 2),
);
let output = format!(
"s1:\n{}\ns2:\n{}\n结论:\n same_content: {}\n same_origins: {}\n snapshot_hash 相同: {}\n origins_hash 相同: {}\n",
indent_block(&snapshot_hashes_and_sorted_view_to_string(&s1), 2),
indent_block(&snapshot_hashes_and_sorted_view_to_string(&s2), 2),
s1.same_content(&s2),
s1.same_origins(&s2),
s1.snapshot_hash() == s2.snapshot_hash(),
s1.origins_hash() == s2.origins_hash(),
);
test_report(
"snapshot_hash_is_stable_for_same_content_with_different_input_order",
"验证相同语义内容即使原始输入顺序不同Snapshot 的 hash 仍然稳定一致。",
&input,
&output,
);
assert!(s1.same_content(&s2));
assert!(s1.same_origins(&s2));
assert_eq!(s1.snapshot_hash(), s2.snapshot_hash());
assert_eq!(s1.origins_hash(), s2.origins_hash());
}
#[test]
fn snapshot_diff_reports_announced_and_withdrawn_correctly() {
let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
let new_c = v6_origin(
Ipv6Addr::new(0x2001, 0xdb8, 0, 1, 0, 0, 0, 0),
48,
48,
64499,
);
let old_input = vec![
Payload::RouteOrigin(old_a.clone()),
Payload::RouteOrigin(old_b.clone()),
];
let new_input = vec![
Payload::RouteOrigin(old_b),
Payload::RouteOrigin(new_c.clone()),
];
let old_snapshot = Snapshot::from_payloads(old_input.clone());
let new_snapshot = Snapshot::from_payloads(new_input.clone());
let (announced, withdrawn) = old_snapshot.diff(&new_snapshot);
let input = format!(
"old_snapshot 原始输入:\n{}\nnew_snapshot 原始输入:\n{}",
indent_block(&payloads_to_string(&old_input), 2),
indent_block(&payloads_to_string(&new_input), 2),
);
let output = format!(
"announced:\n{}withdrawn:\n{}",
indent_block(&payloads_to_string(&announced), 2),
indent_block(&payloads_to_string(&withdrawn), 2),
);
test_report(
"snapshot_diff_reports_announced_and_withdrawn_correctly",
"验证 diff() 能正确找出 announced 和 withdrawn 的 payload。",
&input,
&output,
);
assert_eq!(announced.len(), 1);
assert_eq!(withdrawn.len(), 1);
match &announced[0] {
Payload::RouteOrigin(ro) => assert_eq!(ro, &new_c),
_ => panic!("expected announced RouteOrigin"),
}
match &withdrawn[0] {
Payload::RouteOrigin(ro) => assert_eq!(ro, &old_a),
_ => panic!("expected withdrawn RouteOrigin"),
}
}
#[test]
fn snapshot_payloads_for_rtr_sorts_ipv4_before_ipv6_and_ipv4_announcements_descending() {
let v4_low = v4_origin(192, 0, 2, 0, 24, 24, 64496);
let v4_high = v4_origin(198, 51, 100, 0, 24, 24, 64497);
let v6 = v6_origin(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
32,
48,
64498,
);
let input_payloads = vec![
Payload::RouteOrigin(v6.clone()),
Payload::RouteOrigin(v4_low.clone()),
Payload::RouteOrigin(v4_high.clone()),
];
let snapshot = Snapshot::from_payloads(input_payloads.clone());
let output_payloads = snapshot.payloads_for_rtr();
let input = format!(
"原始输入 payloads构造 Snapshot 前):\n{}",
indent_block(&payloads_to_string(&input_payloads), 2),
);
let output = format!(
"排序后 payloads_for_rtr:\n{}",
indent_block(&payloads_to_string(&output_payloads), 2),
);
test_report(
"snapshot_payloads_for_rtr_sorts_ipv4_before_ipv6_and_ipv4_announcements_descending",
"验证 Snapshot::payloads_for_rtr() 会按 RTR 规则排序IPv4 在 IPv6 前,且 IPv4 announcement 按地址降序。",
&input,
&output,
);
assert_eq!(output_payloads.len(), 3);
let first = as_v4_route_origin(&output_payloads[0]);
let second = as_v4_route_origin(&output_payloads[1]);
assert_eq!(
first.prefix().address.to_ipv4(),
Some(Ipv4Addr::new(198, 51, 100, 0))
);
assert_eq!(
second.prefix().address.to_ipv4(),
Some(Ipv4Addr::new(192, 0, 2, 0))
);
let third = as_route_origin(&output_payloads[2]);
assert!(third.prefix().address.is_ipv6());
assert_eq!(
third.prefix().address.to_ipv6(),
Some(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0))
);
}
#[test]
fn delta_new_sorts_announced_descending_and_withdrawn_ascending() {
let announced_low = v4_origin(192, 0, 2, 0, 24, 24, 64496);
let announced_high = v4_origin(198, 51, 100, 0, 24, 24, 64497);
let withdrawn_high = v4_origin(203, 0, 113, 0, 24, 24, 64501);
let withdrawn_low = v4_origin(10, 0, 0, 0, 24, 24, 64500);
let input_announced = vec![
Payload::RouteOrigin(announced_low),
Payload::RouteOrigin(announced_high),
];
let input_withdrawn = vec![
Payload::RouteOrigin(withdrawn_high),
Payload::RouteOrigin(withdrawn_low),
];
let delta = Delta::new(101, input_announced.clone(), input_withdrawn.clone());
let input = format!(
"announced构造前:\n{}withdrawn构造前:\n{}",
indent_block(&payloads_to_string(&input_announced), 2),
indent_block(&payloads_to_string(&input_withdrawn), 2),
);
let output = indent_block(&delta_to_string(&delta), 2);
test_report(
"delta_new_sorts_announced_descending_and_withdrawn_ascending",
"验证 Delta::new() 会自动排序announced 按 RTR announcement 规则withdrawn 按 RTR withdrawal 规则。",
&input,
&output,
);
assert_eq!(delta.serial(), 101);
assert_eq!(delta.announced().len(), 2);
assert_eq!(delta.withdrawn().len(), 2);
let a0 = as_v4_route_origin(&delta.announced()[0]);
let a1 = as_v4_route_origin(&delta.announced()[1]);
assert_eq!(
a0.prefix().address.to_ipv4(),
Some(Ipv4Addr::new(198, 51, 100, 0))
);
assert_eq!(
a1.prefix().address.to_ipv4(),
Some(Ipv4Addr::new(192, 0, 2, 0))
);
let w0 = as_v4_route_origin(&delta.withdrawn()[0]);
let w1 = as_v4_route_origin(&delta.withdrawn()[1]);
assert_eq!(w0.prefix().address.to_ipv4(), Some(Ipv4Addr::new(10, 0, 0, 0)));
assert_eq!(
w1.prefix().address.to_ipv4(),
Some(Ipv4Addr::new(203, 0, 113, 0))
);
}
#[test]
fn get_deltas_since_returns_up_to_date_when_client_serial_matches_current() {
let cache = RtrCacheBuilder::new()
.session_id(42)
.serial(100)
.timing(Timing::default())
.build();
let result = cache.get_deltas_since(42, 100);
let input = get_deltas_since_input_to_string(cache.session_id(), cache.serial(), 42, 100);
let output = serial_result_detail_to_string(&result);
test_report(
"get_deltas_since_returns_up_to_date_when_client_serial_matches_current",
"验证当客户端 serial 与缓存当前 serial 相同,返回 UpToDate。",
&input,
&output,
);
match result {
SerialResult::UpToDate => {}
_ => panic!("expected UpToDate"),
}
}
#[test]
fn get_deltas_since_returns_reset_required_on_session_mismatch() {
let cache = RtrCacheBuilder::new()
.session_id(42)
.serial(100)
.timing(Timing::default())
.build();
let result = cache.get_deltas_since(999, 100);
let input = get_deltas_since_input_to_string(cache.session_id(), cache.serial(), 999, 100);
let output = serial_result_detail_to_string(&result);
test_report(
"get_deltas_since_returns_reset_required_on_session_mismatch",
"验证当客户端 session_id 与缓存 session_id 不一致时,返回 ResetRequired。",
&input,
&output,
);
match result {
SerialResult::ResetRequired => {}
_ => panic!("expected ResetRequired"),
}
}
#[test]
fn get_deltas_since_returns_reset_required_when_client_serial_is_too_old() {
let d1 = Arc::new(Delta::new(
101,
vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))],
vec![],
));
let d2 = Arc::new(Delta::new(
102,
vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))],
vec![],
));
let mut deltas = VecDeque::new();
deltas.push_back(d1);
deltas.push_back(d2);
let cache = RtrCacheBuilder::new()
.session_id(42)
.serial(102)
.timing(Timing::default())
.deltas(deltas.clone())
.build();
let result = cache.get_deltas_since(42, 99);
let input = format!(
"{}delta_window:\n{}",
get_deltas_since_input_to_string(cache.session_id(), cache.serial(), 42, 99),
indent_block(&deltas_window_to_string(&deltas), 2),
);
let output = serial_result_detail_to_string(&result);
test_report(
"get_deltas_since_returns_reset_required_when_client_serial_is_too_old",
"验证当客户端 serial 太旧,已超出 delta window 覆盖范围时,返回 ResetRequired。",
&input,
&output,
);
match result {
SerialResult::ResetRequired => {}
_ => panic!("expected ResetRequired"),
}
}
#[test]
fn get_deltas_since_returns_applicable_deltas_in_order() {
let d1 = Arc::new(Delta::new(
101,
vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))],
vec![],
));
let d2 = Arc::new(Delta::new(
102,
vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))],
vec![],
));
let d3 = Arc::new(Delta::new(
103,
vec![Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498))],
vec![],
));
let mut deltas = VecDeque::new();
deltas.push_back(d1);
deltas.push_back(d2);
deltas.push_back(d3);
let cache = RtrCacheBuilder::new()
.session_id(42)
.serial(103)
.timing(Timing::default())
.deltas(deltas.clone())
.build();
let result = cache.get_deltas_since(42, 101);
let input = format!(
"{}delta_window:\n{}",
get_deltas_since_input_to_string(cache.session_id(), cache.serial(), 42, 101),
indent_block(&deltas_window_to_string(&deltas), 2),
);
let output = serial_result_detail_to_string(&result);
test_report(
"get_deltas_since_returns_applicable_deltas_in_order",
"验证当客户端 serial 在 delta window 内时,返回正确且有序的 deltas。",
&input,
&output,
);
match result {
SerialResult::Deltas(result) => {
assert_eq!(result.len(), 2);
assert_eq!(result[0].serial(), 102);
assert_eq!(result[1].serial(), 103);
}
_ => panic!("expected Deltas"),
}
}
#[test]
fn get_deltas_since_returns_reset_required_when_client_serial_is_in_future() {
let cache = RtrCacheBuilder::new()
.session_id(42)
.serial(100)
.timing(Timing::default())
.build();
let result = cache.get_deltas_since(42, 101);
let input = get_deltas_since_input_to_string(cache.session_id(), cache.serial(), 42, 101);
let output = serial_result_detail_to_string(&result);
test_report(
"get_deltas_since_returns_reset_required_when_client_serial_is_in_future",
"验证当客户端 serial 比缓存当前 serial 还大时,返回 ResetRequired。",
&input,
&output,
);
match result {
SerialResult::ResetRequired => {}
_ => panic!("expected ResetRequired"),
}
}
#[tokio::test]
async fn update_no_change_keeps_serial_and_produces_no_delta() {
let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
let old_input = vec![
Payload::RouteOrigin(old_a.clone()),
Payload::RouteOrigin(old_b.clone()),
];
let snapshot = Snapshot::from_payloads(old_input.clone());
let mut cache = RtrCacheBuilder::new()
.session_id(42)
.serial(100)
.timing(Timing::default())
.snapshot(snapshot.clone())
.build();
let dir = tempfile::tempdir().unwrap();
let store = RtrStore::open(dir.path()).unwrap();
let new_payloads = vec![
Payload::RouteOrigin(old_b),
Payload::RouteOrigin(old_a),
];
cache.update(new_payloads.clone(), &store).unwrap();
let current_snapshot = cache.snapshot();
let result = cache.get_deltas_since(42, 100);
let input = format!(
"old_snapshot 原始输入:\n{}new_payloads 原始输入:\n{}",
indent_block(&payloads_to_string(&old_input), 2),
indent_block(&payloads_to_string(&new_payloads), 2),
);
let output = format!(
"cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(42, 100):\n{}",
cache.serial(),
indent_block(&snapshot_hashes_and_sorted_view_to_string(&current_snapshot), 2),
indent_block(&serial_result_detail_to_string(&result), 2),
);
test_report(
"update_no_change_keeps_serial_and_produces_no_delta",
"验证 update() 在新旧内容完全相同时serial 不变、snapshot 不变、不会产生新的 delta。",
&input,
&output,
);
assert_eq!(cache.serial(), 100);
assert!(cache.snapshot().same_content(&snapshot));
match result {
SerialResult::UpToDate => {}
_ => panic!("expected UpToDate"),
}
}
#[tokio::test]
async fn update_add_only_increments_serial_and_generates_announced_delta() {
let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
let new_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
let old_input = vec![Payload::RouteOrigin(old_a.clone())];
let old_snapshot = Snapshot::from_payloads(old_input.clone());
let mut cache = RtrCacheBuilder::new()
.session_id(42)
.serial(100)
.timing(Timing::default())
.snapshot(old_snapshot.clone())
.build();
let dir = tempfile::tempdir().unwrap();
let store = RtrStore::open(dir.path()).unwrap();
let new_payloads = vec![
Payload::RouteOrigin(old_a.clone()),
Payload::RouteOrigin(new_b.clone()),
];
cache.update(new_payloads.clone(), &store).unwrap();
let current_snapshot = cache.snapshot();
let result = cache.get_deltas_since(42, 100);
let input = format!(
"old_snapshot 原始输入:\n{}new_payloads 原始输入:\n{}",
indent_block(&payloads_to_string(&old_input), 2),
indent_block(&payloads_to_string(&new_payloads), 2),
);
let output = format!(
"cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(42, 100):\n{}",
cache.serial(),
indent_block(&snapshot_hashes_and_sorted_view_to_string(&current_snapshot), 2),
indent_block(&serial_result_detail_to_string(&result), 2),
);
test_report(
"update_add_only_increments_serial_and_generates_announced_delta",
"验证 update() 在只新增 payload 时serial + 1delta 中只有 announcedwithdrawn 为空。",
&input,
&output,
);
assert_eq!(cache.serial(), 101);
match result {
SerialResult::Deltas(deltas) => {
assert_eq!(deltas.len(), 1);
let delta = &deltas[0];
assert_eq!(delta.serial(), 101);
assert_eq!(delta.announced().len(), 1);
assert_eq!(delta.withdrawn().len(), 0);
match &delta.announced()[0] {
Payload::RouteOrigin(ro) => assert_eq!(ro, &new_b),
_ => panic!("expected announced RouteOrigin"),
}
}
_ => panic!("expected Deltas"),
}
}
#[tokio::test]
async fn update_remove_only_increments_serial_and_generates_withdrawn_delta() {
let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
let old_input = vec![
Payload::RouteOrigin(old_a.clone()),
Payload::RouteOrigin(old_b.clone()),
];
let old_snapshot = Snapshot::from_payloads(old_input.clone());
let mut cache = RtrCacheBuilder::new()
.session_id(42)
.serial(100)
.timing(Timing::default())
.snapshot(old_snapshot.clone())
.build();
let dir = tempfile::tempdir().unwrap();
let store = RtrStore::open(dir.path()).unwrap();
let new_payloads = vec![Payload::RouteOrigin(old_b.clone())];
cache.update(new_payloads.clone(), &store).unwrap();
let current_snapshot = cache.snapshot();
let result = cache.get_deltas_since(42, 100);
let input = format!(
"old_snapshot 原始输入:\n{}new_payloads 原始输入:\n{}",
indent_block(&payloads_to_string(&old_input), 2),
indent_block(&payloads_to_string(&new_payloads), 2),
);
let output = format!(
"cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(42, 100):\n{}",
cache.serial(),
indent_block(&snapshot_hashes_and_sorted_view_to_string(&current_snapshot), 2),
indent_block(&serial_result_detail_to_string(&result), 2),
);
test_report(
"update_remove_only_increments_serial_and_generates_withdrawn_delta",
"验证 update() 在只删除 payload 时serial + 1delta 中只有 withdrawnannounced 为空。",
&input,
&output,
);
assert_eq!(cache.serial(), 101);
match result {
SerialResult::Deltas(deltas) => {
assert_eq!(deltas.len(), 1);
let delta = &deltas[0];
assert_eq!(delta.serial(), 101);
assert_eq!(delta.announced().len(), 0);
assert_eq!(delta.withdrawn().len(), 1);
match &delta.withdrawn()[0] {
Payload::RouteOrigin(ro) => assert_eq!(ro, &old_a),
_ => panic!("expected withdrawn RouteOrigin"),
}
}
_ => panic!("expected Deltas"),
}
}
#[tokio::test]
async fn update_add_and_remove_increments_serial_and_generates_both_sides() {
let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
let new_c = v6_origin(
Ipv6Addr::new(0x2001, 0xdb8, 0, 1, 0, 0, 0, 0),
48,
48,
64499,
);
let old_input = vec![
Payload::RouteOrigin(old_a.clone()),
Payload::RouteOrigin(old_b.clone()),
];
let old_snapshot = Snapshot::from_payloads(old_input.clone());
let mut cache = RtrCacheBuilder::new()
.session_id(42)
.serial(100)
.timing(Timing::default())
.snapshot(old_snapshot.clone())
.build();
let dir = tempfile::tempdir().unwrap();
let store = RtrStore::open(dir.path()).unwrap();
let new_payloads = vec![
Payload::RouteOrigin(old_b.clone()),
Payload::RouteOrigin(new_c.clone()),
];
cache.update(new_payloads.clone(), &store).unwrap();
let current_snapshot = cache.snapshot();
let result = cache.get_deltas_since(42, 100);
let input = format!(
"old_snapshot 原始输入:\n{}new_payloads 原始输入:\n{}",
indent_block(&payloads_to_string(&old_input), 2),
indent_block(&payloads_to_string(&new_payloads), 2),
);
let output = format!(
"cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(42, 100):\n{}",
cache.serial(),
indent_block(&snapshot_hashes_and_sorted_view_to_string(&current_snapshot), 2),
indent_block(&serial_result_detail_to_string(&result), 2),
);
test_report(
"update_add_and_remove_increments_serial_and_generates_both_sides",
"验证 update() 在同时新增和删除 payload 时serial + 1delta 中 announced 和 withdrawn 都正确。",
&input,
&output,
);
assert_eq!(cache.serial(), 101);
match result {
SerialResult::Deltas(deltas) => {
assert_eq!(deltas.len(), 1);
let delta = &deltas[0];
assert_eq!(delta.serial(), 101);
assert_eq!(delta.announced().len(), 1);
assert_eq!(delta.withdrawn().len(), 1);
match &delta.announced()[0] {
Payload::RouteOrigin(ro) => assert_eq!(ro, &new_c),
_ => panic!("expected announced RouteOrigin"),
}
match &delta.withdrawn()[0] {
Payload::RouteOrigin(ro) => assert_eq!(ro, &old_a),
_ => panic!("expected withdrawn RouteOrigin"),
}
}
_ => panic!("expected Deltas"),
}
}