rpki/tests/common/test_helper.rs

332 lines
9.2 KiB
Rust

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
}