RTR server以及client的开发、test的补充
This commit is contained in:
1
tests/common/mod.rs
Normal file
1
tests/common/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod test_helper;
|
||||
332
tests/common/test_helper.rs
Normal file
332
tests/common/test_helper.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::fmt::Write;
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
|
||||
use rpki::rtr::payload::{Payload, RouteOrigin};
|
||||
use rpki::rtr::pdu::{CacheResponse, EndOfDataV1, IPv4Prefix, IPv6Prefix};
|
||||
use rpki::rtr::cache::SerialResult;
|
||||
|
||||
pub struct RtrDebugDumper {
|
||||
entries: Vec<Value>,
|
||||
}
|
||||
|
||||
impl RtrDebugDumper {
|
||||
pub fn new() -> Self {
|
||||
Self { entries: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push<T: serde::Serialize>(&mut self, pdu: u8, body: &T) {
|
||||
self.entries.push(json!({
|
||||
"pdu": pdu,
|
||||
"pdu_name": pdu_type_name(pdu),
|
||||
"body": body
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn push_value(&mut self, pdu: u8, body: Value) {
|
||||
self.entries.push(json!({
|
||||
"pdu": pdu,
|
||||
"pdu_name": pdu_type_name(pdu),
|
||||
"body": body
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn print_pretty(&self, test_name: &str) {
|
||||
println!(
|
||||
"\n===== RTR Debug Dump: {} =====\n{}\n",
|
||||
test_name,
|
||||
serde_json::to_string_pretty(&self.entries).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pdu_type_name(pdu: u8) -> &'static str {
|
||||
match pdu {
|
||||
0 => "Serial Notify",
|
||||
1 => "Serial Query",
|
||||
2 => "Reset Query",
|
||||
3 => "Cache Response",
|
||||
4 => "IPv4 Prefix",
|
||||
6 => "IPv6 Prefix",
|
||||
7 => "End of Data",
|
||||
8 => "Cache Reset",
|
||||
9 => "Router Key",
|
||||
10 => "Error Report",
|
||||
11 => "ASPA",
|
||||
255 => "Reserved",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump_cache_response(resp: &CacheResponse) -> Value {
|
||||
json!({
|
||||
"header": {
|
||||
"version": resp.version(),
|
||||
"pdu": resp.pdu(),
|
||||
"session_id": resp.session_id(),
|
||||
"length": 8
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dump_ipv4_prefix(p: &IPv4Prefix) -> Value {
|
||||
json!({
|
||||
"header": {
|
||||
"version": p.version(),
|
||||
"pdu": p.pdu(),
|
||||
"session_id": 0,
|
||||
"length": 20
|
||||
},
|
||||
"flags": {
|
||||
"raw": if p.flag().is_announce() { 1 } else { 0 },
|
||||
"announce": p.flag().is_announce()
|
||||
},
|
||||
"prefix": p.prefix().to_string(),
|
||||
"prefix_len": p.prefix_len(),
|
||||
"max_len": p.max_len(),
|
||||
"asn": p.asn().into_u32()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dump_ipv6_prefix(p: &IPv6Prefix) -> Value {
|
||||
json!({
|
||||
"header": {
|
||||
"version": p.version(),
|
||||
"pdu": p.pdu(),
|
||||
"session_id": 0,
|
||||
"length": 32
|
||||
},
|
||||
"flags": {
|
||||
"raw": if p.flag().is_announce() { 1 } else { 0 },
|
||||
"announce": p.flag().is_announce()
|
||||
},
|
||||
"prefix": p.prefix().to_string(),
|
||||
"prefix_len": p.prefix_len(),
|
||||
"max_len": p.max_len(),
|
||||
"asn": p.asn().into_u32()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dump_eod_v1(eod: &EndOfDataV1) -> Value {
|
||||
let timing = eod.timing();
|
||||
json!({
|
||||
"header": {
|
||||
"version": eod.version(),
|
||||
"pdu": eod.pdu(),
|
||||
"session_id": eod.session_id(),
|
||||
"length": 24
|
||||
},
|
||||
"serial_number": eod.serial_number(),
|
||||
"refresh_interval": timing.refresh,
|
||||
"retry_interval": timing.retry,
|
||||
"expire_interval": timing.expire
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dump_cache_reset(version: u8, pdu: u8) -> Value {
|
||||
json!({
|
||||
"header": {
|
||||
"version": version,
|
||||
"pdu": pdu,
|
||||
"session_id": 0,
|
||||
"length": 8
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn v4_prefix(a: u8, b: u8, c: u8, d: u8, prefix_len: u8) -> IPAddressPrefix {
|
||||
IPAddressPrefix {
|
||||
address: IPAddress::from_ipv4(Ipv4Addr::new(a, b, c, d)),
|
||||
prefix_length: prefix_len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn v6_prefix(addr: Ipv6Addr, prefix_len: u8) -> IPAddressPrefix {
|
||||
IPAddressPrefix {
|
||||
address: IPAddress::from_ipv6(addr),
|
||||
prefix_length: prefix_len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn v4_origin(
|
||||
a: u8,
|
||||
b: u8,
|
||||
c: u8,
|
||||
d: u8,
|
||||
prefix_len: u8,
|
||||
max_len: u8,
|
||||
asn: u32,
|
||||
) -> RouteOrigin {
|
||||
let prefix = v4_prefix(a, b, c, d, prefix_len);
|
||||
RouteOrigin::new(prefix, max_len, asn.into())
|
||||
}
|
||||
|
||||
pub fn v6_origin(
|
||||
addr: Ipv6Addr,
|
||||
prefix_len: u8,
|
||||
max_len: u8,
|
||||
asn: u32,
|
||||
) -> RouteOrigin {
|
||||
let prefix = v6_prefix(addr, prefix_len);
|
||||
RouteOrigin::new(prefix, max_len, asn.into())
|
||||
}
|
||||
|
||||
pub fn as_route_origin(payload: &Payload) -> &RouteOrigin {
|
||||
match payload {
|
||||
Payload::RouteOrigin(ro) => ro,
|
||||
_ => panic!("expected RouteOrigin payload"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_v4_route_origin(payload: &Payload) -> &RouteOrigin {
|
||||
let ro = as_route_origin(payload);
|
||||
assert!(ro.prefix().address.is_ipv4(), "expected IPv4 RouteOrigin");
|
||||
ro
|
||||
}
|
||||
|
||||
pub fn as_v6_route_origin(payload: &Payload) -> &RouteOrigin {
|
||||
let ro = as_route_origin(payload);
|
||||
assert!(ro.prefix().address.is_ipv6(), "expected IPv6 RouteOrigin");
|
||||
ro
|
||||
}
|
||||
|
||||
pub fn route_origin_to_string(ro: &RouteOrigin) -> String {
|
||||
let prefix = ro.prefix();
|
||||
let addr = match prefix.address {
|
||||
IPAddress::V4(v4) => v4.to_string(),
|
||||
IPAddress::V6(v6) => v6.to_string(),
|
||||
};
|
||||
|
||||
format!(
|
||||
"{}/{}-{} AS{}",
|
||||
addr,
|
||||
prefix.prefix_length,
|
||||
ro.max_length(),
|
||||
ro.asn().into_u32()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn payload_to_string(payload: &Payload) -> String {
|
||||
match payload {
|
||||
Payload::RouteOrigin(ro) => format!("RouteOrigin({})", route_origin_to_string(ro)),
|
||||
Payload::RouterKey(_) => "RouterKey(...)".to_string(),
|
||||
Payload::Aspa(_) => "Aspa(...)".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payloads_to_pretty_lines(payloads: &[Payload]) -> String {
|
||||
let mut out = String::new();
|
||||
for (idx, payload) in payloads.iter().enumerate() {
|
||||
let _ = writeln!(&mut out, " [{}] {}", idx, payload_to_string(payload));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn print_payloads(label: &str, payloads: &[Payload]) {
|
||||
println!(
|
||||
"\n===== {} =====\n{}",
|
||||
label,
|
||||
payloads_to_pretty_lines(payloads)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn serial_result_to_string(result: &SerialResult) -> String {
|
||||
match result {
|
||||
SerialResult::UpToDate => "UpToDate".to_string(),
|
||||
SerialResult::ResetRequired => "ResetRequired".to_string(),
|
||||
SerialResult::Deltas(deltas) => {
|
||||
let serials: Vec<u32> = deltas.iter().map(|d| d.serial()).collect();
|
||||
format!("Deltas {:?}", serials)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_serial_result(label: &str, result: &SerialResult) {
|
||||
println!("\n===== {} =====\n{}\n", label, serial_result_to_string(result));
|
||||
}
|
||||
|
||||
pub fn bytes_to_hex(bytes: &[u8]) -> String {
|
||||
let mut out = String::with_capacity(bytes.len() * 2);
|
||||
for b in bytes {
|
||||
let _ = write!(&mut out, "{:02x}", b);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn print_snapshot_hashes(label: &str, snapshot: &rpki::rtr::cache::Snapshot) {
|
||||
println!(
|
||||
"\n===== {} =====\norigins_hash={}\nrouter_keys_hash={}\naspas_hash={}\nsnapshot_hash={}\n",
|
||||
label,
|
||||
bytes_to_hex(&snapshot.origins_hash()),
|
||||
bytes_to_hex(&snapshot.router_keys_hash()),
|
||||
bytes_to_hex(&snapshot.aspas_hash()),
|
||||
bytes_to_hex(&snapshot.snapshot_hash()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn test_report(
|
||||
name: &str,
|
||||
purpose: &str,
|
||||
input: &str,
|
||||
output: &str,
|
||||
) {
|
||||
println!(
|
||||
"\n==================== TEST REPORT ====================\n测试名称: {}\n测试目的: {}\n\n【输入】\n{}\n【输出】\n{}\n====================================================\n",
|
||||
name, purpose, input, output
|
||||
);
|
||||
}
|
||||
|
||||
pub fn payloads_to_string(payloads: &[Payload]) -> String {
|
||||
let mut out = String::new();
|
||||
for (idx, payload) in payloads.iter().enumerate() {
|
||||
let _ = writeln!(&mut out, " [{}] {}", idx, payload_to_string(payload));
|
||||
}
|
||||
if out.is_empty() {
|
||||
out.push_str(" <empty>\n");
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn snapshot_hashes_to_string(snapshot: &rpki::rtr::cache::Snapshot) -> String {
|
||||
format!(
|
||||
" origins_hash: {}\n router_keys_hash: {}\n aspas_hash: {}\n snapshot_hash: {}\n",
|
||||
bytes_to_hex(&snapshot.origins_hash()),
|
||||
bytes_to_hex(&snapshot.router_keys_hash()),
|
||||
bytes_to_hex(&snapshot.aspas_hash()),
|
||||
bytes_to_hex(&snapshot.snapshot_hash()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serial_result_detail_to_string(result: &rpki::rtr::cache::SerialResult) -> String {
|
||||
match result {
|
||||
rpki::rtr::cache::SerialResult::UpToDate => {
|
||||
" result: UpToDate\n".to_string()
|
||||
}
|
||||
rpki::rtr::cache::SerialResult::ResetRequired => {
|
||||
" result: ResetRequired\n".to_string()
|
||||
}
|
||||
rpki::rtr::cache::SerialResult::Deltas(deltas) => {
|
||||
let mut out = String::new();
|
||||
let _ = writeln!(&mut out, " result: Deltas");
|
||||
for (idx, delta) in deltas.iter().enumerate() {
|
||||
let _ = writeln!(&mut out, " delta[{}].serial: {}", idx, delta.serial());
|
||||
let _ = writeln!(&mut out, " delta[{}].announced:", idx);
|
||||
out.push_str(&indent_block(&payloads_to_string(delta.announced()), 4));
|
||||
let _ = writeln!(&mut out, " delta[{}].withdrawn:", idx);
|
||||
out.push_str(&indent_block(&payloads_to_string(delta.withdrawn()), 4));
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn indent_block(text: &str, spaces: usize) -> String {
|
||||
let pad = " ".repeat(spaces);
|
||||
let mut out = String::new();
|
||||
for line in text.lines() {
|
||||
let _ = writeln!(&mut out, "{}{}", pad, line);
|
||||
}
|
||||
out
|
||||
}
|
||||
743
tests/test_cache.rs
Normal file
743
tests/test_cache.rs
Normal file
@@ -0,0 +1,743 @@
|
||||
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"),
|
||||
}
|
||||
}
|
||||
492
tests/test_session.rs
Normal file
492
tests/test_session.rs
Normal file
@@ -0,0 +1,492 @@
|
||||
mod common;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::{broadcast, watch};
|
||||
|
||||
use common::test_helper::{
|
||||
dump_cache_reset, dump_cache_response, dump_eod_v1, dump_ipv4_prefix, dump_ipv6_prefix,
|
||||
RtrDebugDumper,
|
||||
};
|
||||
|
||||
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
|
||||
use rpki::rtr::cache::{Delta, SharedRtrCache, RtrCacheBuilder, Snapshot};
|
||||
use rpki::rtr::payload::{Payload, RouteOrigin, Timing};
|
||||
use rpki::rtr::pdu::{
|
||||
CacheResponse, CacheReset, EndOfDataV1, IPv4Prefix, IPv6Prefix, ResetQuery, SerialQuery,
|
||||
};
|
||||
use rpki::rtr::session::RtrSession;
|
||||
|
||||
fn shared_cache(cache: rpki::rtr::cache::RtrCache) -> SharedRtrCache {
|
||||
Arc::new(RwLock::new(cache))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reset_query_returns_snapshot_and_end_of_data() {
|
||||
let prefix = IPAddressPrefix {
|
||||
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
||||
prefix_length: 24,
|
||||
};
|
||||
let origin = RouteOrigin::new(prefix, 24, 64496u32.into());
|
||||
|
||||
let snapshot = Snapshot::from_payloads(vec![Payload::RouteOrigin(origin)]);
|
||||
let cache = RtrCacheBuilder::new()
|
||||
.session_id(42)
|
||||
.serial(100)
|
||||
.timing(Timing::new(600, 600, 7200))
|
||||
.snapshot(snapshot)
|
||||
.build();
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server_cache = shared_cache(cache);
|
||||
let (_notify_tx, notify_rx) = broadcast::channel(16);
|
||||
let (_shutdown_tx, shutdown_rx) = watch::channel(false);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let session = RtrSession::new(server_cache, stream, notify_rx, shutdown_rx);
|
||||
session.run().await.unwrap();
|
||||
});
|
||||
|
||||
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
ResetQuery::new(1).write(&mut client).await.unwrap();
|
||||
|
||||
let mut dump = RtrDebugDumper::new();
|
||||
|
||||
let response = CacheResponse::read(&mut client).await.unwrap();
|
||||
dump.push_value(response.pdu(), dump_cache_response(&response));
|
||||
assert_eq!(response.pdu(), 3);
|
||||
assert_eq!(response.version(), 1);
|
||||
assert_eq!(response.session_id(), 42);
|
||||
|
||||
let prefix = IPv4Prefix::read(&mut client).await.unwrap();
|
||||
dump.push_value(prefix.pdu(), dump_ipv4_prefix(&prefix));
|
||||
assert_eq!(prefix.pdu(), 4);
|
||||
assert_eq!(prefix.version(), 1);
|
||||
assert!(prefix.flag().is_announce());
|
||||
assert_eq!(prefix.prefix_len(), 24);
|
||||
assert_eq!(prefix.max_len(), 24);
|
||||
assert_eq!(prefix.prefix(), Ipv4Addr::new(192, 0, 2, 0));
|
||||
assert_eq!(prefix.asn(), 64496u32.into());
|
||||
|
||||
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
||||
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
||||
assert_eq!(eod.pdu(), 7);
|
||||
assert_eq!(eod.version(), 1);
|
||||
assert_eq!(eod.session_id(), 42);
|
||||
assert_eq!(eod.serial_number(), 100);
|
||||
let timing = eod.timing();
|
||||
assert_eq!(timing.refresh, 600);
|
||||
assert_eq!(timing.retry, 600);
|
||||
assert_eq!(timing.expire, 7200);
|
||||
|
||||
dump.print_pretty("reset_query_returns_snapshot_and_end_of_data");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn serial_query_returns_end_of_data_when_up_to_date() {
|
||||
let cache = RtrCacheBuilder::new()
|
||||
.session_id(42)
|
||||
.serial(100)
|
||||
.timing(Timing {
|
||||
refresh: 600,
|
||||
retry: 600,
|
||||
expire: 7200,
|
||||
})
|
||||
.build();
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server_cache = shared_cache(cache);
|
||||
let (_notify_tx, notify_rx) = broadcast::channel(16);
|
||||
let (_shutdown_tx, shutdown_rx) = watch::channel(false);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let session = RtrSession::new(server_cache, stream, notify_rx, shutdown_rx);
|
||||
session.run().await.unwrap();
|
||||
});
|
||||
|
||||
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
SerialQuery::new(1, 42, 100).write(&mut client).await.unwrap();
|
||||
|
||||
let mut dump = RtrDebugDumper::new();
|
||||
|
||||
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
||||
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
||||
assert_eq!(eod.pdu(), 7);
|
||||
assert_eq!(eod.version(), 1);
|
||||
assert_eq!(eod.session_id(), 42);
|
||||
assert_eq!(eod.serial_number(), 100);
|
||||
|
||||
let timing = eod.timing();
|
||||
assert_eq!(timing.refresh, 600);
|
||||
assert_eq!(timing.retry, 600);
|
||||
assert_eq!(timing.expire, 7200);
|
||||
|
||||
dump.print_pretty("serial_query_returns_end_of_data_when_up_to_date");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn serial_query_returns_cache_reset_when_session_id_mismatch() {
|
||||
let cache = RtrCacheBuilder::new()
|
||||
.session_id(42)
|
||||
.serial(100)
|
||||
.timing(Timing {
|
||||
refresh: 600,
|
||||
retry: 600,
|
||||
expire: 7200,
|
||||
})
|
||||
.build();
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server_cache = shared_cache(cache);
|
||||
let (_notify_tx, notify_rx) = broadcast::channel(16);
|
||||
let (_shutdown_tx, shutdown_rx) = watch::channel(false);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let session = RtrSession::new(server_cache, stream, notify_rx, shutdown_rx);
|
||||
session.run().await.unwrap();
|
||||
});
|
||||
|
||||
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
SerialQuery::new(1, 999, 100).write(&mut client).await.unwrap();
|
||||
|
||||
let mut dump = RtrDebugDumper::new();
|
||||
|
||||
let reset = CacheReset::read(&mut client).await.unwrap();
|
||||
dump.push_value(reset.pdu(), dump_cache_reset(reset.version(), reset.pdu()));
|
||||
assert_eq!(reset.pdu(), 8);
|
||||
assert_eq!(reset.version(), 1);
|
||||
|
||||
dump.print_pretty("serial_query_returns_cache_reset_when_session_id_mismatch");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn serial_query_returns_deltas_when_incremental_update_available() {
|
||||
let prefix = IPAddressPrefix {
|
||||
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
||||
prefix_length: 24,
|
||||
};
|
||||
let origin = RouteOrigin::new(prefix, 24, 64496u32.into());
|
||||
|
||||
let delta = Arc::new(Delta::new(
|
||||
101,
|
||||
vec![Payload::RouteOrigin(origin)],
|
||||
vec![],
|
||||
));
|
||||
|
||||
let mut deltas = VecDeque::new();
|
||||
deltas.push_back(delta);
|
||||
|
||||
let cache = RtrCacheBuilder::new()
|
||||
.session_id(42)
|
||||
.serial(101)
|
||||
.timing(Timing {
|
||||
refresh: 600,
|
||||
retry: 600,
|
||||
expire: 7200,
|
||||
})
|
||||
.deltas(deltas)
|
||||
.build();
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server_cache = shared_cache(cache);
|
||||
let (_notify_tx, notify_rx) = broadcast::channel(16);
|
||||
let (_shutdown_tx, shutdown_rx) = watch::channel(false);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let session = RtrSession::new(server_cache, stream, notify_rx, shutdown_rx);
|
||||
session.run().await.unwrap();
|
||||
});
|
||||
|
||||
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
SerialQuery::new(1, 42, 100).write(&mut client).await.unwrap();
|
||||
|
||||
let mut dump = RtrDebugDumper::new();
|
||||
|
||||
let response = CacheResponse::read(&mut client).await.unwrap();
|
||||
dump.push_value(response.pdu(), dump_cache_response(&response));
|
||||
assert_eq!(response.pdu(), 3);
|
||||
assert_eq!(response.version(), 1);
|
||||
assert_eq!(response.session_id(), 42);
|
||||
|
||||
let prefix = IPv4Prefix::read(&mut client).await.unwrap();
|
||||
dump.push_value(prefix.pdu(), dump_ipv4_prefix(&prefix));
|
||||
assert_eq!(prefix.pdu(), 4);
|
||||
assert_eq!(prefix.version(), 1);
|
||||
assert!(prefix.flag().is_announce());
|
||||
assert_eq!(prefix.prefix_len(), 24);
|
||||
assert_eq!(prefix.max_len(), 24);
|
||||
assert_eq!(prefix.prefix(), Ipv4Addr::new(192, 0, 2, 0));
|
||||
assert_eq!(prefix.asn(), 64496u32.into());
|
||||
|
||||
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
||||
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
||||
assert_eq!(eod.pdu(), 7);
|
||||
assert_eq!(eod.version(), 1);
|
||||
assert_eq!(eod.session_id(), 42);
|
||||
assert_eq!(eod.serial_number(), 101);
|
||||
|
||||
let timing = eod.timing();
|
||||
assert_eq!(timing.refresh, 600);
|
||||
assert_eq!(timing.retry, 600);
|
||||
assert_eq!(timing.expire, 7200);
|
||||
|
||||
dump.print_pretty("serial_query_returns_deltas_when_incremental_update_available");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reset_query_returns_payloads_in_rtr_order() {
|
||||
let v4_low_prefix = IPAddressPrefix {
|
||||
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
||||
prefix_length: 24,
|
||||
};
|
||||
let v4_low_origin = RouteOrigin::new(v4_low_prefix, 24, 64496u32.into());
|
||||
|
||||
let v4_high_prefix = IPAddressPrefix {
|
||||
address: IPAddress::from_ipv4(Ipv4Addr::new(198, 51, 100, 0)),
|
||||
prefix_length: 24,
|
||||
};
|
||||
let v4_high_origin = RouteOrigin::new(v4_high_prefix, 24, 64497u32.into());
|
||||
|
||||
let v6_prefix = IPAddressPrefix {
|
||||
address: IPAddress::from_ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)),
|
||||
prefix_length: 32,
|
||||
};
|
||||
let v6_origin = RouteOrigin::new(v6_prefix, 48, 64498u32.into());
|
||||
|
||||
let snapshot = Snapshot::from_payloads(vec![
|
||||
Payload::RouteOrigin(v6_origin),
|
||||
Payload::RouteOrigin(v4_low_origin),
|
||||
Payload::RouteOrigin(v4_high_origin),
|
||||
]);
|
||||
|
||||
let cache = RtrCacheBuilder::new()
|
||||
.session_id(42)
|
||||
.serial(100)
|
||||
.timing(Timing::new(600, 600, 7200))
|
||||
.snapshot(snapshot)
|
||||
.build();
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server_cache = shared_cache(cache);
|
||||
let (_notify_tx, notify_rx) = broadcast::channel(16);
|
||||
let (_shutdown_tx, shutdown_rx) = watch::channel(false);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let session = RtrSession::new(server_cache, stream, notify_rx, shutdown_rx);
|
||||
session.run().await.unwrap();
|
||||
});
|
||||
|
||||
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
ResetQuery::new(1).write(&mut client).await.unwrap();
|
||||
|
||||
let mut dump = RtrDebugDumper::new();
|
||||
|
||||
let response = CacheResponse::read(&mut client).await.unwrap();
|
||||
dump.push_value(response.pdu(), dump_cache_response(&response));
|
||||
assert_eq!(response.pdu(), 3);
|
||||
assert_eq!(response.version(), 1);
|
||||
assert_eq!(response.session_id(), 42);
|
||||
|
||||
let first = IPv4Prefix::read(&mut client).await.unwrap();
|
||||
dump.push_value(first.pdu(), dump_ipv4_prefix(&first));
|
||||
assert_eq!(first.pdu(), 4);
|
||||
assert_eq!(first.version(), 1);
|
||||
assert!(first.flag().is_announce());
|
||||
assert_eq!(first.prefix(), Ipv4Addr::new(198, 51, 100, 0));
|
||||
assert_eq!(first.prefix_len(), 24);
|
||||
assert_eq!(first.max_len(), 24);
|
||||
assert_eq!(first.asn(), 64497u32.into());
|
||||
|
||||
let second = IPv4Prefix::read(&mut client).await.unwrap();
|
||||
dump.push_value(second.pdu(), dump_ipv4_prefix(&second));
|
||||
assert_eq!(second.pdu(), 4);
|
||||
assert_eq!(second.version(), 1);
|
||||
assert!(second.flag().is_announce());
|
||||
assert_eq!(second.prefix(), Ipv4Addr::new(192, 0, 2, 0));
|
||||
assert_eq!(second.prefix_len(), 24);
|
||||
assert_eq!(second.max_len(), 24);
|
||||
assert_eq!(second.asn(), 64496u32.into());
|
||||
|
||||
assert!(u32::from(first.prefix()) > u32::from(second.prefix()));
|
||||
|
||||
let third = IPv6Prefix::read(&mut client).await.unwrap();
|
||||
dump.push_value(third.pdu(), dump_ipv6_prefix(&third));
|
||||
assert_eq!(third.pdu(), 6);
|
||||
assert_eq!(third.version(), 1);
|
||||
assert!(third.flag().is_announce());
|
||||
assert_eq!(third.prefix(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
|
||||
assert_eq!(third.prefix_len(), 32);
|
||||
assert_eq!(third.max_len(), 48);
|
||||
assert_eq!(third.asn(), 64498u32.into());
|
||||
|
||||
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
||||
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
||||
assert_eq!(eod.pdu(), 7);
|
||||
assert_eq!(eod.version(), 1);
|
||||
assert_eq!(eod.session_id(), 42);
|
||||
assert_eq!(eod.serial_number(), 100);
|
||||
|
||||
let timing = eod.timing();
|
||||
assert_eq!(timing.refresh, 600);
|
||||
assert_eq!(timing.retry, 600);
|
||||
assert_eq!(timing.expire, 7200);
|
||||
|
||||
dump.print_pretty("reset_query_returns_payloads_in_rtr_order");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn serial_query_returns_announcements_before_withdrawals() {
|
||||
let announced_low_prefix = IPAddressPrefix {
|
||||
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
||||
prefix_length: 24,
|
||||
};
|
||||
let announced_low_origin = RouteOrigin::new(announced_low_prefix, 24, 64496u32.into());
|
||||
|
||||
let announced_high_prefix = IPAddressPrefix {
|
||||
address: IPAddress::from_ipv4(Ipv4Addr::new(198, 51, 100, 0)),
|
||||
prefix_length: 24,
|
||||
};
|
||||
let announced_high_origin = RouteOrigin::new(announced_high_prefix, 24, 64497u32.into());
|
||||
|
||||
let withdrawn_low_prefix = IPAddressPrefix {
|
||||
address: IPAddress::from_ipv4(Ipv4Addr::new(10, 0, 0, 0)),
|
||||
prefix_length: 24,
|
||||
};
|
||||
let withdrawn_low_origin = RouteOrigin::new(withdrawn_low_prefix, 24, 64500u32.into());
|
||||
|
||||
let withdrawn_high_prefix = IPAddressPrefix {
|
||||
address: IPAddress::from_ipv4(Ipv4Addr::new(203, 0, 113, 0)),
|
||||
prefix_length: 24,
|
||||
};
|
||||
let withdrawn_high_origin = RouteOrigin::new(withdrawn_high_prefix, 24, 64501u32.into());
|
||||
|
||||
let delta = Arc::new(Delta::new(
|
||||
101,
|
||||
vec![
|
||||
Payload::RouteOrigin(announced_low_origin),
|
||||
Payload::RouteOrigin(announced_high_origin),
|
||||
],
|
||||
vec![
|
||||
Payload::RouteOrigin(withdrawn_high_origin),
|
||||
Payload::RouteOrigin(withdrawn_low_origin),
|
||||
],
|
||||
));
|
||||
|
||||
let mut deltas = VecDeque::new();
|
||||
deltas.push_back(delta);
|
||||
|
||||
let cache = RtrCacheBuilder::new()
|
||||
.session_id(42)
|
||||
.serial(101)
|
||||
.timing(Timing {
|
||||
refresh: 600,
|
||||
retry: 600,
|
||||
expire: 7200,
|
||||
})
|
||||
.deltas(deltas)
|
||||
.build();
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server_cache = shared_cache(cache);
|
||||
let (_notify_tx, notify_rx) = broadcast::channel(16);
|
||||
let (_shutdown_tx, shutdown_rx) = watch::channel(false);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let session = RtrSession::new(server_cache, stream, notify_rx, shutdown_rx);
|
||||
session.run().await.unwrap();
|
||||
});
|
||||
|
||||
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
SerialQuery::new(1, 42, 100).write(&mut client).await.unwrap();
|
||||
|
||||
let mut dump = RtrDebugDumper::new();
|
||||
|
||||
let response = CacheResponse::read(&mut client).await.unwrap();
|
||||
dump.push_value(response.pdu(), dump_cache_response(&response));
|
||||
assert_eq!(response.pdu(), 3);
|
||||
assert_eq!(response.version(), 1);
|
||||
assert_eq!(response.session_id(), 42);
|
||||
|
||||
let first = IPv4Prefix::read(&mut client).await.unwrap();
|
||||
dump.push_value(first.pdu(), dump_ipv4_prefix(&first));
|
||||
assert_eq!(first.pdu(), 4);
|
||||
assert_eq!(first.version(), 1);
|
||||
assert!(first.flag().is_announce());
|
||||
assert_eq!(first.prefix(), Ipv4Addr::new(198, 51, 100, 0));
|
||||
assert_eq!(first.prefix_len(), 24);
|
||||
assert_eq!(first.max_len(), 24);
|
||||
assert_eq!(first.asn(), 64497u32.into());
|
||||
|
||||
let second = IPv4Prefix::read(&mut client).await.unwrap();
|
||||
dump.push_value(second.pdu(), dump_ipv4_prefix(&second));
|
||||
assert_eq!(second.pdu(), 4);
|
||||
assert_eq!(second.version(), 1);
|
||||
assert!(second.flag().is_announce());
|
||||
assert_eq!(second.prefix(), Ipv4Addr::new(192, 0, 2, 0));
|
||||
assert_eq!(second.prefix_len(), 24);
|
||||
assert_eq!(second.max_len(), 24);
|
||||
assert_eq!(second.asn(), 64496u32.into());
|
||||
|
||||
assert!(u32::from(first.prefix()) > u32::from(second.prefix()));
|
||||
|
||||
let third = IPv4Prefix::read(&mut client).await.unwrap();
|
||||
dump.push_value(third.pdu(), dump_ipv4_prefix(&third));
|
||||
assert_eq!(third.pdu(), 4);
|
||||
assert_eq!(third.version(), 1);
|
||||
assert!(!third.flag().is_announce());
|
||||
assert_eq!(third.prefix(), Ipv4Addr::new(10, 0, 0, 0));
|
||||
assert_eq!(third.prefix_len(), 24);
|
||||
assert_eq!(third.max_len(), 24);
|
||||
assert_eq!(third.asn(), 64500u32.into());
|
||||
|
||||
let fourth = IPv4Prefix::read(&mut client).await.unwrap();
|
||||
dump.push_value(fourth.pdu(), dump_ipv4_prefix(&fourth));
|
||||
assert_eq!(fourth.pdu(), 4);
|
||||
assert_eq!(fourth.version(), 1);
|
||||
assert!(!fourth.flag().is_announce());
|
||||
assert_eq!(fourth.prefix(), Ipv4Addr::new(203, 0, 113, 0));
|
||||
assert_eq!(fourth.prefix_len(), 24);
|
||||
assert_eq!(fourth.max_len(), 24);
|
||||
assert_eq!(fourth.asn(), 64501u32.into());
|
||||
|
||||
assert!(u32::from(third.prefix()) < u32::from(fourth.prefix()));
|
||||
assert!(first.flag().is_announce());
|
||||
assert!(second.flag().is_announce());
|
||||
assert!(!third.flag().is_announce());
|
||||
assert!(!fourth.flag().is_announce());
|
||||
|
||||
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
||||
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
||||
assert_eq!(eod.pdu(), 7);
|
||||
assert_eq!(eod.version(), 1);
|
||||
assert_eq!(eod.session_id(), 42);
|
||||
assert_eq!(eod.serial_number(), 101);
|
||||
|
||||
let timing = eod.timing();
|
||||
assert_eq!(timing.refresh, 600);
|
||||
assert_eq!(timing.retry, 600);
|
||||
assert_eq!(timing.expire, 7200);
|
||||
|
||||
dump.print_pretty("serial_query_returns_announcements_before_withdrawals");
|
||||
}
|
||||
352
tests/test_store_db.rs
Normal file
352
tests/test_store_db.rs
Normal file
@@ -0,0 +1,352 @@
|
||||
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::{Delta, Snapshot};
|
||||
use rpki::rtr::payload::Payload;
|
||||
use rpki::rtr::store_db::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();
|
||||
|
||||
store.set_session_id(42).unwrap();
|
||||
store.set_serial(100).unwrap();
|
||||
store.set_delta_window(101, 110).unwrap();
|
||||
|
||||
let session_id = store.get_session_id().unwrap();
|
||||
let serial = store.get_serial().unwrap();
|
||||
let window = store.get_delta_window().unwrap();
|
||||
|
||||
let input = format!(
|
||||
"db_path: {}\nset_session_id=42\nset_serial=100\nset_delta_window=(101, 110)\n",
|
||||
dir.path().display(),
|
||||
);
|
||||
|
||||
let output = format!(
|
||||
"get_session_id: {:?}\nget_serial: {:?}\nget_delta_window: {:?}\n",
|
||||
session_id, serial, window,
|
||||
);
|
||||
|
||||
test_report(
|
||||
"store_db_set_and_get_meta_fields",
|
||||
"验证 session_id / serial / delta_window 能正确写入并读回。",
|
||||
&input,
|
||||
&output,
|
||||
);
|
||||
|
||||
assert_eq!(session_id, Some(42));
|
||||
assert_eq!(serial, Some(100));
|
||||
assert_eq!(window, Some((101, 110)));
|
||||
}
|
||||
|
||||
#[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 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, 42, 100).unwrap();
|
||||
|
||||
let loaded_snapshot = store.get_snapshot().unwrap().expect("snapshot should exist");
|
||||
let loaded_session = store.get_session_id().unwrap();
|
||||
let loaded_serial = store.get_serial().unwrap();
|
||||
|
||||
let input = format!(
|
||||
"db_path: {}\nsnapshot:\n{}session_id=42\nserial=100\n",
|
||||
dir.path().display(),
|
||||
indent_block(&snapshot_to_string(&snapshot), 2),
|
||||
);
|
||||
|
||||
let output = format!(
|
||||
"loaded_snapshot:\n{}loaded_session_id: {:?}\nloaded_serial: {:?}\n",
|
||||
indent_block(&snapshot_to_string(&loaded_snapshot), 2),
|
||||
loaded_session,
|
||||
loaded_serial,
|
||||
);
|
||||
|
||||
test_report(
|
||||
"store_db_save_snapshot_and_meta_writes_all_fields",
|
||||
"验证 save_snapshot_and_meta() 会同时写入 snapshot、session_id 和 serial。",
|
||||
&input,
|
||||
&output,
|
||||
);
|
||||
|
||||
assert!(snapshot.same_content(&loaded_snapshot));
|
||||
assert_eq!(loaded_session, Some(42));
|
||||
assert_eq!(loaded_serial, Some(100));
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
Reference in New Issue
Block a user