743 lines
23 KiB
Rust
743 lines
23 KiB
Rust
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(¤t_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(¤t_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 + 1,delta 中只有 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(), 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(¤t_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 + 1,delta 中只有 withdrawn,announced 为空。",
|
||
&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(¤t_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 + 1,delta 中 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"),
|
||
}
|
||
} |