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, } impl RtrDebugDumper { pub fn new() -> Self { Self { entries: Vec::new() } } pub fn push(&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 = 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(" \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 }