增加rtr一些必要的api接口

This commit is contained in:
xiuting.xu
2026-06-23 17:04:00 +08:00
parent 303bdffd97
commit b105b3b7d0
41 changed files with 3078 additions and 1735 deletions

View File

@@ -1,7 +1,7 @@
use std::fmt::Write;
use std::net::{Ipv4Addr, Ipv6Addr};
use serde_json::{json, Value};
use serde_json::{Value, json};
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
use rpki::rtr::cache::SerialResult;

View File

@@ -79,17 +79,10 @@ fn version_report_stats_separate_snapshot_and_delta_payload_types() {
Asn::from(64496u32),
vec![1, 2, 3],
));
let aspa = Payload::Aspa(Aspa::new(
Asn::from(64496u32),
vec![Asn::from(64497u32)],
));
let aspa = Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32)]));
let snapshot = Snapshot::from_payloads(vec![vrp.clone(), router_key.clone(), aspa.clone()]);
let mut deltas = VecDeque::new();
deltas.push_back(Arc::new(Delta::new(
101,
vec![vrp, aspa],
vec![router_key],
)));
deltas.push_back(Arc::new(Delta::new(101, vec![vrp, aspa], vec![router_key])));
let cache = RtrCacheBuilder::new()
.session_ids(SessionIds::from_array([40, 41, 42]))
.serials([99, 100, 101])
@@ -568,8 +561,11 @@ fn get_deltas_since_returns_up_to_date_when_client_serial_matches_current() {
let result = cache.get_deltas_since_for_version(2, 100);
let input =
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100);
let input = get_deltas_since_input_to_string(
cache.session_id_for_version(1),
cache.serial_for_version(2),
100,
);
let output = serial_result_detail_to_string(&result);
test_report(
@@ -615,7 +611,11 @@ fn get_deltas_since_returns_reset_required_when_client_serial_is_too_old() {
let input = format!(
"{}delta_window:\n{}",
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 99),
get_deltas_since_input_to_string(
cache.session_id_for_version(1),
cache.serial_for_version(2),
99
),
indent_block(&deltas_window_to_string(&deltas), 2),
);
let output = serial_result_detail_to_string(&result);
@@ -680,7 +680,11 @@ fn get_deltas_since_returns_minimal_merged_delta() {
let input = format!(
"{}delta_window:\n{}",
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 101),
get_deltas_since_input_to_string(
cache.session_id_for_version(1),
cache.serial_for_version(2),
101
),
indent_block(&deltas_window_to_string(&deltas), 2),
);
let output = serial_result_detail_to_string(&result);
@@ -726,8 +730,11 @@ fn get_deltas_since_returns_reset_required_when_client_serial_is_in_future() {
let result = cache.get_deltas_since_for_version(2, 101);
let input =
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 101);
let input = get_deltas_since_input_to_string(
cache.session_id_for_version(1),
cache.serial_for_version(2),
101,
);
let output = serial_result_detail_to_string(&result);
test_report(
@@ -888,8 +895,11 @@ fn get_deltas_since_returns_reset_required_when_client_serial_is_in_future_acros
let result = cache.get_deltas_since_for_version(2, 0);
let input =
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 0);
let input = get_deltas_since_input_to_string(
cache.session_id_for_version(1),
cache.serial_for_version(2),
0,
);
let output = serial_result_detail_to_string(&result);
test_report(
@@ -1227,7 +1237,11 @@ fn get_deltas_since_cancels_announce_then_withdraw_for_same_prefix() {
let input = format!(
"{}delta_window:\n{}",
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100),
get_deltas_since_input_to_string(
cache.session_id_for_version(1),
cache.serial_for_version(2),
100
),
indent_block(&deltas_window_to_string(&deltas), 2),
);
let output = serial_result_detail_to_string(&result);
@@ -1281,7 +1295,11 @@ fn get_deltas_since_cancels_withdraw_then_announce_for_same_prefix() {
let input = format!(
"{}delta_window:\n{}",
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100),
get_deltas_since_input_to_string(
cache.session_id_for_version(1),
cache.serial_for_version(2),
100
),
indent_block(&deltas_window_to_string(&deltas), 2),
);
let output = serial_result_detail_to_string(&result);
@@ -1335,7 +1353,11 @@ fn get_deltas_since_merges_replacement_into_withdraw_and_announce() {
let input = format!(
"{}delta_window:\n{}",
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100),
get_deltas_since_input_to_string(
cache.session_id_for_version(1),
cache.serial_for_version(2),
100
),
indent_block(&deltas_window_to_string(&deltas), 2),
);
let output = serial_result_detail_to_string(&result);
@@ -1414,7 +1436,11 @@ fn get_deltas_since_merges_multiple_deltas_to_final_minimal_view() {
let input = format!(
"{}delta_window:\n{}",
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100),
get_deltas_since_input_to_string(
cache.session_id_for_version(1),
cache.serial_for_version(2),
100
),
indent_block(&deltas_window_to_string(&deltas), 2),
);
let output = serial_result_detail_to_string(&result);
@@ -1501,7 +1527,9 @@ fn get_deltas_since_merges_aspa_replacement_into_single_announcement() {
.session_ids(SessionIds::from_array([42, 42, 42]))
.serials(serials_all(102))
.timing(Timing::default())
.snapshots(snapshots_all(Snapshot::from_payloads(vec![Payload::Aspa(new.clone())])))
.snapshots(snapshots_all(Snapshot::from_payloads(vec![Payload::Aspa(
new.clone(),
)])))
.deltas_by_version(deltas_all(deltas))
.build();

View File

@@ -1,14 +1,16 @@
use std::fs;
use std::path::PathBuf;
use tempfile::tempdir;
use rpki::source::ccr::{
ParsedAspa, ParsedCcrSnapshot, ParsedVrp, find_latest_ccr_file, load_ccr_snapshot_from_file,
snapshot_to_payloads_with_options,
};
use tempfile::tempdir;
fn fixture_path(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("data").join(name)
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("data")
.join(name)
}
#[test]
@@ -17,8 +19,7 @@ fn ccr_loader_smoke() {
// let path = "./mini_data/20260403T000001Z-mini-a.ccr";
// let path = "20260403T000101Z-mini-b.ccr";
let path = "20260403T000201Z-mini-c.ccr";
let snapshot = load_ccr_snapshot_from_file(fixture_path(path))
.expect("load CCR snapshot");
let snapshot = load_ccr_snapshot_from_file(fixture_path(path)).expect("load CCR snapshot");
println!("content_type_oid: {}", snapshot.content_type_oid);
println!("produced_at : {:?}", snapshot.produced_at);
@@ -115,7 +116,15 @@ fn generated_mini_ccr_files_are_parseable() {
for (name, expect_vrps, expect_vaps) in cases {
let snapshot = load_ccr_snapshot_from_file(fixture_path(name))
.unwrap_or_else(|e| panic!("failed to parse {}: {:?}", name, e));
assert_eq!(snapshot.vrps.len(), expect_vrps, "vrp count mismatch for {name}");
assert_eq!(snapshot.vaps.len(), expect_vaps, "vap count mismatch for {name}");
assert_eq!(
snapshot.vrps.len(),
expect_vrps,
"vrp count mismatch for {name}"
);
assert_eq!(
snapshot.vaps.len(),
expect_vaps,
"vap count mismatch for {name}"
);
}
}

View File

@@ -1,7 +1,7 @@
use std::path::PathBuf;
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::crl::Asn1TimeEncoding;
use rpki::data_model::crl::RpkixCrl;
#[test]
fn decode_and_validate_crl_fixture() {
@@ -42,9 +42,8 @@ fn crl_signature_verification_succeeds_with_issuer_cert() {
#[test]
fn decode_crl_with_revoked_entries() {
let der =
std::fs::read("tests/fixtures/0099DEAB073EFD74C250C0A382B25012B5082AEE.crl")
.expect("read CRL fixture with revoked entries");
let der = std::fs::read("tests/fixtures/0099DEAB073EFD74C250C0A382B25012B5082AEE.crl")
.expect("read CRL fixture with revoked entries");
let crl = RpkixCrl::decode_der(&der).expect("decode CRL");

View File

@@ -1,13 +1,13 @@
use std::net::Ipv4Addr;
use tokio::io::{duplex, AsyncWriteExt};
use tokio::io::{AsyncWriteExt, duplex};
use rpki::data_model::resources::as_resources::Asn;
use rpki::rtr::error_type::ErrorCode;
use rpki::rtr::payload::{Aspa as PayloadAspa, Ski, Timing};
use rpki::rtr::pdu::{
Aspa, EndOfDataV1, ErrorReport, Flags, Header, IPv4Prefix, RouterKey, SerialNotify,
END_OF_DATA_V1_LEN, MAX_PDU_LEN,
Aspa, END_OF_DATA_V1_LEN, EndOfDataV1, ErrorReport, Flags, Header, IPv4Prefix, MAX_PDU_LEN,
RouterKey, SerialNotify,
};
const ERROR_REPORT_FIXED_PART_LEN: usize = 16;
@@ -271,7 +271,9 @@ async fn end_of_data_v1_read_payload_rejects_invalid_timing() {
client.write_all(&600u32.to_be_bytes()).await.unwrap();
client.write_all(&600u32.to_be_bytes()).await.unwrap();
let err = EndOfDataV1::read_payload(header, &mut server).await.unwrap_err();
let err = EndOfDataV1::read_payload(header, &mut server)
.await
.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
assert!(err.to_string().contains("expire interval"));
}

13
tests/test_rtr_admin.rs Normal file
View File

@@ -0,0 +1,13 @@
use rpki::rtr::admin::tail_log_lines;
#[test]
fn tail_log_lines_returns_requested_suffix() {
let output = tail_log_lines(b"one\ntwo\nthree\nfour\n".to_vec(), 2);
assert_eq!(output, b"three\nfour\n");
}
#[test]
fn tail_log_lines_returns_whole_buffer_when_shorter() {
let output = tail_log_lines(b"one\ntwo\n".to_vec(), 5);
assert_eq!(output, b"one\ntwo\n");
}

87
tests/test_rtr_config.rs Normal file
View File

@@ -0,0 +1,87 @@
use rpki::rtr::config::{AppConfig, RuntimeConfigPatch, RuntimeTimingConfigPatch, parse_timezone};
#[test]
fn parse_timezone_accepts_iana_names_and_common_aliases() {
assert_eq!(
parse_timezone("Asia/Shanghai", "TEST").unwrap(),
chrono_tz::Asia::Shanghai
);
assert_eq!(
parse_timezone("shanghai", "TEST").unwrap(),
chrono_tz::Asia::Shanghai
);
assert_eq!(
parse_timezone("Europe/London", "TEST").unwrap(),
chrono_tz::Europe::London
);
assert_eq!(
parse_timezone("America/New_York", "TEST").unwrap(),
chrono_tz::America::New_York
);
assert_eq!(parse_timezone("UTC", "TEST").unwrap(), chrono_tz::UTC);
assert_eq!(parse_timezone("Z", "TEST").unwrap(), chrono_tz::UTC);
}
#[test]
fn parse_timezone_rejects_offset_and_invalid_values() {
assert!(parse_timezone("+08:00", "TEST").is_err());
assert!(parse_timezone("+0800", "TEST").is_err());
assert!(parse_timezone("Mars/Base", "TEST").is_err());
}
#[test]
fn runtime_config_patch_validates_and_preserves_unspecified_values() {
let current = AppConfig::default().runtime_config();
let next = current
.apply_patch(RuntimeConfigPatch {
max_delta: Some(8),
prune_delta_by_snapshot_size: Some(true),
source_refresh_interval_seconds: Some(60),
runtime_report_interval_seconds: Some(120),
report_history_limit: Some(20),
strict_ccr_validation: Some(true),
timezone: Some("UTC".to_string()),
timing: Some(RuntimeTimingConfigPatch {
refresh: Some(1800),
retry: None,
expire: Some(7200),
}),
})
.unwrap();
assert_eq!(next.max_delta, 8);
assert!(next.prune_delta_by_snapshot_size);
assert_eq!(next.source_refresh_interval_seconds, 60);
assert_eq!(next.runtime_report_interval_seconds, 120);
assert_eq!(next.report_history_limit, 20);
assert!(next.strict_ccr_validation);
assert_eq!(next.timezone, "UTC");
assert_eq!(next.timing.refresh, 1800);
assert_eq!(next.timing.retry, current.timing.retry);
assert_eq!(next.timing.expire, 7200);
assert!(
current
.apply_patch(RuntimeConfigPatch {
max_delta: Some(0),
..RuntimeConfigPatch::default()
})
.is_err()
);
assert!(
current
.apply_patch(RuntimeConfigPatch {
runtime_report_interval_seconds: Some(0),
..RuntimeConfigPatch::default()
})
.is_err()
);
assert!(
current
.apply_patch(RuntimeConfigPatch {
timezone: Some("+08:00".to_string()),
..RuntimeConfigPatch::default()
})
.is_err()
);
}

290
tests/test_rtr_report.rs Normal file
View File

@@ -0,0 +1,290 @@
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use arc_swap::ArcSwap;
use chrono::Utc;
use rpki::rtr::cache::RtrCache;
use rpki::rtr::report::{ReportConfiguration, ReportContext};
use rpki::rtr::server::RtrService;
use rpki::source::pipeline::{
DataQualityReport, FileFingerprint, PayloadTypeCounts, SlurmRuleCounts, SourceFingerprint,
SourceLoadReport,
};
use serde_json::Value;
fn test_configuration(report_history_limit: usize) -> ReportConfiguration {
ReportConfiguration::new(
300,
300,
report_history_limit,
100,
false,
false,
chrono_tz::Asia::Shanghai,
(3600, 600, 7200),
)
}
#[test]
fn write_report_creates_parseable_json() {
let temp = tempfile::tempdir().unwrap();
let report_dir = temp.path().join("report");
let shared_cache = Arc::new(ArcSwap::from_pointee(RtrCache::default()));
let service = RtrService::new(shared_cache.clone());
let notifier = service.notifier();
let context = ReportContext::new(test_configuration(10));
context.write_or_warn(
&report_dir,
"test",
&shared_cache,
&notifier,
&service.stats(),
);
let source_report = read_single_report(&report_dir, "rtr-source");
assert_eq!(source_report["schema_version"], 2);
assert_eq!(source_report["phase"], "test");
assert_report_time_offset(&source_report["generated_at"]);
assert_report_time_offset(&source_report["cache"]["created_at"]);
assert_eq!(source_report["cache"]["availability"], "ready");
assert_eq!(source_report["refresh"]["status"], "not_attempted");
assert!(source_report["source"].is_null());
assert!(source_report["data_quality"].is_null());
let runtime_report = read_single_report(&report_dir, "rtr-runtime");
assert_report_time_offset(&runtime_report["service"]["started_at"]);
assert_eq!(
runtime_report["configuration"]["source_refresh_interval_seconds"],
300
);
assert_eq!(runtime_report["configuration"]["report_history_limit"], 10);
let clients_report = read_single_report(&report_dir, "rtr-clients");
assert_eq!(clients_report["service"]["max_connections"], 1024);
assert_eq!(clients_report["service"]["active_connections"], 0);
assert_eq!(
clients_report["service"]["connections_by_transport"]["tcp"],
0
);
assert_eq!(
clients_report["service"]["connections_by_transport"]["tls"],
0
);
assert_eq!(
clients_report["service"]["connections_by_transport"]["ssh"],
0
);
assert_eq!(
source_report["cache"]["versions"].as_array().unwrap().len(),
3
);
assert_eq!(
source_report["cache"]["versions"][2]["snapshot"]["total"],
0
);
assert_eq!(
source_report["cache"]["memory"]["delta_payload_counts"][2],
0
);
assert!(source_report["source_fingerprint"].is_null());
assert_no_temporary_reports(&report_dir);
}
#[test]
fn refresh_failure_preserves_last_successful_source_data() {
let temp = tempfile::tempdir().unwrap();
let report_dir = temp.path().join("report");
let shared_cache = Arc::new(ArcSwap::from_pointee(RtrCache::default()));
let service = RtrService::new(shared_cache.clone());
let notifier = service.notifier();
let context = ReportContext::new(test_configuration(10));
let source = SourceLoadReport {
ccr_file: "data/example.ccr".to_string(),
ccr_file_size_bytes: 123,
ccr_modified_at: Some(Utc::now()),
ccr_produced_at: Some("20260615000000Z".to_string()),
slurm_enabled: true,
slurm_file_count: 1,
slurm_files: vec!["policy.slurm".to_string()],
slurm_version: Some(2),
};
let quality = DataQualityReport {
ccr_input: PayloadTypeCounts {
total: 11,
vrp: 10,
router_key: 0,
aspa: 1,
},
invalid: PayloadTypeCounts::default(),
before_slurm: PayloadTypeCounts {
total: 11,
vrp: 10,
router_key: 0,
aspa: 1,
},
after_slurm: PayloadTypeCounts {
total: 10,
vrp: 9,
router_key: 0,
aspa: 1,
},
slurm_filters: SlurmRuleCounts {
prefix: 1,
router_key: 0,
aspa: 0,
},
slurm_assertions: SlurmRuleCounts::default(),
};
context.record_source_fingerprint(SourceFingerprint {
ccr: FileFingerprint {
path: "data/example.ccr".to_string(),
len: 123,
modified_unix_secs: 1_781_404_800,
},
slurm_files: vec![FileFingerprint {
path: "policy.slurm".to_string(),
len: 42,
modified_unix_secs: 1_781_408_400,
}],
});
context.record_refresh_success(Utc::now(), 12, true, source, quality);
context.record_refresh_failure(Utc::now(), 5, &anyhow::anyhow!("source unavailable"));
context.write_or_warn(
&report_dir,
"refresh_failed",
&shared_cache,
&notifier,
&service.stats(),
);
let report = read_single_report(&report_dir, "rtr-source");
assert_eq!(report["source"]["ccr_file"], "data/example.ccr");
assert_eq!(
report["source_fingerprint"]["ccr"]["path"],
"data/example.ccr"
);
assert_eq!(report["source_fingerprint"]["ccr"]["len"], 123);
assert_report_time_offset(&report["source_fingerprint"]["ccr"]["modified_at"]);
assert_eq!(
report["source_fingerprint"]["slurm_files"][0]["path"],
"policy.slurm"
);
assert_report_time_offset(&report["source"]["ccr_modified_at"]);
assert_eq!(report["data_quality"]["after_slurm"]["total"], 10);
assert_eq!(report["refresh"]["status"], "failed");
assert_eq!(report["refresh"]["consecutive_failures"], 1);
assert_eq!(report["refresh"]["last_error"], "source unavailable");
assert!(!report["refresh"]["last_success_at"].is_null());
assert_report_time_offset(&report["refresh"]["last_success_at"]);
}
#[test]
fn rolling_reports_keep_latest_files_per_category() {
let temp = tempfile::tempdir().unwrap();
let report_dir = temp.path().join("report");
let shared_cache = Arc::new(ArcSwap::from_pointee(RtrCache::default()));
let service = RtrService::new(shared_cache.clone());
let notifier = service.notifier();
let context = ReportContext::new(test_configuration(2));
for phase in ["one", "two", "three"] {
context.write_or_warn(
&report_dir,
phase,
&shared_cache,
&notifier,
&service.stats(),
);
}
assert_eq!(report_files(&report_dir, "rtr-source").len(), 2);
assert_eq!(report_files(&report_dir, "rtr-clients").len(), 2);
assert_eq!(report_files(&report_dir, "rtr-runtime").len(), 2);
assert_no_temporary_reports(&report_dir);
}
#[test]
fn category_writes_only_create_requested_report_type() {
let temp = tempfile::tempdir().unwrap();
let report_dir = temp.path().join("report");
let shared_cache = Arc::new(ArcSwap::from_pointee(RtrCache::default()));
let service = RtrService::new(shared_cache.clone());
let notifier = service.notifier();
let context = ReportContext::new(test_configuration(10));
context.write_source_or_warn(
&report_dir,
"source_only",
&shared_cache,
&notifier,
&service.stats(),
);
assert_eq!(report_files(&report_dir, "rtr-source").len(), 1);
assert_eq!(report_files(&report_dir, "rtr-clients").len(), 0);
assert_eq!(report_files(&report_dir, "rtr-runtime").len(), 0);
context.write_clients_or_warn(
&report_dir,
"clients_only",
&shared_cache,
&notifier,
&service.stats(),
);
assert_eq!(report_files(&report_dir, "rtr-source").len(), 1);
assert_eq!(report_files(&report_dir, "rtr-clients").len(), 1);
assert_eq!(report_files(&report_dir, "rtr-runtime").len(), 0);
context.write_runtime_or_warn(
&report_dir,
"runtime_only",
&shared_cache,
&notifier,
&service.stats(),
);
assert_eq!(report_files(&report_dir, "rtr-source").len(), 1);
assert_eq!(report_files(&report_dir, "rtr-clients").len(), 1);
assert_eq!(report_files(&report_dir, "rtr-runtime").len(), 1);
}
fn assert_report_time_offset(value: &Value) {
let value = value.as_str().expect("report time should be a string");
assert!(
value.ends_with("+08:00"),
"report time should use +08:00 offset, got {value}"
);
}
fn read_single_report(report_dir: &Path, prefix: &str) -> Value {
let files = report_files(report_dir, prefix);
assert_eq!(files.len(), 1, "expected one {prefix} report");
serde_json::from_slice(&fs::read(&files[0]).unwrap()).unwrap()
}
fn report_files(report_dir: &Path, prefix: &str) -> Vec<PathBuf> {
let start = format!("{prefix}-");
let mut files = fs::read_dir(report_dir)
.unwrap()
.map(|entry| entry.unwrap().path())
.filter(|path| {
path.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.starts_with(&start) && name.ends_with(".json"))
})
.collect::<Vec<_>>();
files.sort();
files
}
fn assert_no_temporary_reports(report_dir: &Path) {
let has_temporary = fs::read_dir(report_dir).unwrap().any(|entry| {
entry
.unwrap()
.file_name()
.to_str()
.is_some_and(|name| name.ends_with(".tmp"))
});
assert!(!has_temporary);
}

View File

@@ -16,8 +16,8 @@ use tokio_rustls::TlsConnector;
use rpki::rtr::cache::{RtrCacheBuilder, SessionIds, SharedRtrCache};
use rpki::rtr::payload::Timing;
use rpki::rtr::pdu::{CacheResponse, EndOfDataV1, ResetQuery};
use rpki::rtr::server::ssh::SshAuthMode;
use rpki::rtr::server::RtrService;
use rpki::rtr::server::ssh::SshAuthMode;
use russh::client;
use russh::keys;
use russh::keys::ssh_key::LineEnding;

View File

@@ -15,16 +15,16 @@ use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::{broadcast, watch};
use tokio::task::JoinHandle;
use tokio::time::{timeout, Duration};
use tokio::time::{Duration, timeout};
use tokio_rustls::{TlsAcceptor, TlsConnector};
use common::test_helper::{
dump_cache_reset, dump_cache_response, dump_eod_v1, dump_ipv4_prefix, dump_ipv6_prefix,
RtrDebugDumper,
RtrDebugDumper, dump_cache_reset, dump_cache_response, dump_eod_v1, dump_ipv4_prefix,
dump_ipv6_prefix,
};
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
use rpki::data_model::resources::as_resources::Asn;
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
use rpki::rtr::cache::{Delta, RtrCacheBuilder, SessionIds, SharedRtrCache, Snapshot};
use rpki::rtr::error_type::ErrorCode;
use rpki::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Ski, Timing};
@@ -32,10 +32,10 @@ use rpki::rtr::pdu::{
Aspa as AspaPdu, CacheReset, CacheResponse, EndOfDataV1, ErrorReport, Header, IPv4Prefix,
IPv6Prefix, ResetQuery, RouterKey as RouterKeyPdu, SerialNotify, SerialQuery,
};
use rpki::rtr::store::RtrStore;
use rpki::rtr::server::connection::handle_tls_connection;
use rpki::rtr::server::tls::load_rustls_server_config_with_options;
use rpki::rtr::session::RtrSession;
use rpki::rtr::store::RtrStore;
fn shared_cache(cache: rpki::rtr::cache::RtrCache) -> SharedRtrCache {
Arc::new(ArcSwap::from_pointee(cache))
@@ -119,11 +119,7 @@ async fn start_session_server_returning_result(
async fn start_tls_session_server(
cache: SharedRtrCache,
) -> (
SocketAddr,
watch::Sender<bool>,
JoinHandle<()>,
) {
) -> (SocketAddr, watch::Sender<bool>, JoinHandle<()>) {
start_tls_session_server_with_cert(cache, "server.crt", "server.key").await
}
@@ -131,11 +127,7 @@ async fn start_tls_session_server_with_cert(
cache: SharedRtrCache,
cert_name: &str,
key_name: &str,
) -> (
SocketAddr,
watch::Sender<bool>,
JoinHandle<()>,
) {
) -> (SocketAddr, watch::Sender<bool>, JoinHandle<()>) {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
@@ -181,11 +173,8 @@ async fn shutdown_server(
shutdown_io(&mut client, shutdown_tx, server_handle).await;
}
async fn shutdown_io<S>(
io: &mut S,
shutdown_tx: watch::Sender<bool>,
server_handle: JoinHandle<()>,
) where
async fn shutdown_io<S>(io: &mut S, shutdown_tx: watch::Sender<bool>, server_handle: JoinHandle<()>)
where
S: AsyncWrite + Unpin,
{
let _ = io.shutdown().await;
@@ -419,8 +408,8 @@ async fn restart_restores_versioned_state_and_serves_queries() {
};
let origin = Payload::RouteOrigin(RouteOrigin::new(prefix, 24, 64496u32.into()));
let valid_spki = vec![
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x01, 0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
];
let router_key = Payload::RouterKey(RouterKey::new(
Ski::default(),
@@ -464,7 +453,9 @@ async fn restart_restores_versioned_state_and_serves_queries() {
let expected_sid_v0 = shared.load_full().session_id_for_version(0);
assert_eq!(response.session_id(), expected_sid_v0);
let _v4 = IPv4Prefix::read(&mut client).await.unwrap();
let eod_v0 = rpki::rtr::pdu::EndOfDataV0::read(&mut client).await.unwrap();
let eod_v0 = rpki::rtr::pdu::EndOfDataV0::read(&mut client)
.await
.unwrap();
assert_eq!(eod_v0.serial_number(), 1);
shutdown_server(client, shutdown_tx, server_handle).await;
@@ -484,7 +475,10 @@ async fn restart_restores_versioned_state_and_serves_queries() {
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(shared.clone()).await;
let mut client = TcpStream::connect(addr).await.unwrap();
let sid_v2 = shared.load_full().session_id_for_version(2);
SerialQuery::new(2, sid_v2, 1).write(&mut client).await.unwrap();
SerialQuery::new(2, sid_v2, 1)
.write(&mut client)
.await
.unwrap();
let response = CacheResponse::read(&mut client).await.unwrap();
assert_eq!(response.version(), 2);
assert_eq!(response.session_id(), sid_v2);
@@ -511,7 +505,10 @@ async fn serial_query_returns_end_of_data_when_up_to_date() {
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
let mut client = TcpStream::connect(addr).await.unwrap();
SerialQuery::new(1, 42, 100).write(&mut client).await.unwrap();
SerialQuery::new(1, 42, 100)
.write(&mut client)
.await
.unwrap();
let mut dump = RtrDebugDumper::new();
@@ -554,7 +551,10 @@ async fn serial_query_returns_corrupt_data_when_session_id_mismatch() {
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
let mut client = TcpStream::connect(addr).await.unwrap();
SerialQuery::new(1, 999, 100).write(&mut client).await.unwrap();
SerialQuery::new(1, 999, 100)
.write(&mut client)
.await
.unwrap();
let mut dump = RtrDebugDumper::new();
@@ -610,7 +610,10 @@ async fn serial_query_returns_deltas_when_incremental_update_available() {
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
let mut client = TcpStream::connect(addr).await.unwrap();
SerialQuery::new(1, 42, 100).write(&mut client).await.unwrap();
SerialQuery::new(1, 42, 100)
.write(&mut client)
.await
.unwrap();
let mut dump = RtrDebugDumper::new();
@@ -823,7 +826,10 @@ async fn reset_query_returns_payloads_in_rtr_order() {
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(),
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());
@@ -901,7 +907,10 @@ async fn serial_query_returns_announcements_before_withdrawals() {
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
let mut client = TcpStream::connect(addr).await.unwrap();
SerialQuery::new(1, 42, 100).write(&mut client).await.unwrap();
SerialQuery::new(1, 42, 100)
.write(&mut client)
.await
.unwrap();
let mut dump = RtrDebugDumper::new();
@@ -1256,7 +1265,10 @@ async fn serial_query_returns_no_data_available_when_cache_is_unavailable() {
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
let mut client = TcpStream::connect(addr).await.unwrap();
SerialQuery::new(1, 42, 100).write(&mut client).await.unwrap();
SerialQuery::new(1, 42, 100)
.write(&mut client)
.await
.unwrap();
let report = ErrorReport::read(&mut client).await.unwrap();
assert_error_report_matches(
@@ -1266,7 +1278,10 @@ async fn serial_query_returns_no_data_available_when_cache_is_unavailable() {
SerialQuery::new(1, 42, 100).as_ref(),
);
SerialQuery::new(1, 42, 100).write(&mut client).await.unwrap();
SerialQuery::new(1, 42, 100)
.write(&mut client)
.await
.unwrap();
let second = ErrorReport::read(&mut client).await.unwrap();
assert_error_report_matches(
&second,
@@ -1327,7 +1342,11 @@ async fn first_pdu_with_invalid_length_returns_corrupt_data() {
);
assert_error_report_matches(&report, 1, ErrorCode::CorruptData, &request);
assert!(std::str::from_utf8(report.text()).unwrap().contains("invalid length"));
assert!(
std::str::from_utf8(report.text())
.unwrap()
.contains("invalid length")
);
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
.await
@@ -1442,9 +1461,11 @@ async fn established_session_invalid_header_returns_corrupt_data() {
}),
);
assert_error_report_matches(&report, 1, ErrorCode::CorruptData, invalid_header.as_ref());
assert!(std::str::from_utf8(report.text())
.unwrap()
.contains("invalid PDU length"));
assert!(
std::str::from_utf8(report.text())
.unwrap()
.contains("invalid PDU length")
);
let read_res = Header::read(&mut client).await;
assert!(read_res.is_err());
@@ -1506,7 +1527,12 @@ async fn established_session_unknown_pdu_returns_unsupported_pdu_type() {
}),
);
assert_error_report_matches(&report, 1, ErrorCode::UnsupportedPduType, unknown_pdu.as_ref());
assert_error_report_matches(
&report,
1,
ErrorCode::UnsupportedPduType,
unknown_pdu.as_ref(),
);
let read_res = Header::read(&mut client).await;
assert!(read_res.is_err());
@@ -1525,11 +1551,12 @@ async fn established_session_unknown_pdu_returns_unsupported_pdu_type() {
#[tokio::test]
async fn version_zero_does_not_send_router_key_or_aspa() {
let router_key = RouterKey::new(Ski::default(), Asn::from(64496u32), vec![1u8; 32]);
let aspa = Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32), Asn::from(64498u32)]);
let snapshot = Snapshot::from_payloads(vec![
Payload::RouterKey(router_key),
Payload::Aspa(aspa),
]);
let aspa = Aspa::new(
Asn::from(64496u32),
vec![Asn::from(64497u32), Asn::from(64498u32)],
);
let snapshot =
Snapshot::from_payloads(vec![Payload::RouterKey(router_key), Payload::Aspa(aspa)]);
let cache = RtrCacheBuilder::new()
.session_ids(SessionIds::from_array([42, 42, 42]))
@@ -1548,7 +1575,9 @@ async fn version_zero_does_not_send_router_key_or_aspa() {
let response = CacheResponse::read(&mut client).await.unwrap();
dump.push_value(response.pdu(), dump_cache_response(&response));
let eod = rpki::rtr::pdu::EndOfDataV0::read(&mut client).await.unwrap();
let eod = rpki::rtr::pdu::EndOfDataV0::read(&mut client)
.await
.unwrap();
dump.push_value(
eod.pdu(),
json!({
@@ -1561,7 +1590,10 @@ async fn version_zero_does_not_send_router_key_or_aspa() {
);
let res = timeout(Duration::from_millis(100), Header::read(&mut client)).await;
assert!(res.is_err(), "version 0 response should not contain RouterKey or ASPA PDUs");
assert!(
res.is_err(),
"version 0 response should not contain RouterKey or ASPA PDUs"
);
dump.print_pretty("version_zero_does_not_send_router_key_or_aspa");
shutdown_server(client, shutdown_tx, server_handle).await;
@@ -1569,7 +1601,10 @@ async fn version_zero_does_not_send_router_key_or_aspa() {
#[tokio::test]
async fn version_two_aspa_withdraw_has_empty_provider_list() {
let aspa = Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32), Asn::from(64498u32)]);
let aspa = Aspa::new(
Asn::from(64496u32),
vec![Asn::from(64497u32), Asn::from(64498u32)],
);
let delta = Arc::new(Delta::new(101, vec![], vec![Payload::Aspa(aspa)]));
let mut deltas = VecDeque::new();
deltas.push_back(delta);
@@ -1586,7 +1621,10 @@ async fn version_two_aspa_withdraw_has_empty_provider_list() {
let mut client = TcpStream::connect(addr).await.unwrap();
let mut dump = RtrDebugDumper::new();
SerialQuery::new(2, 42, 100).write(&mut client).await.unwrap();
SerialQuery::new(2, 42, 100)
.write(&mut client)
.await
.unwrap();
let response = CacheResponse::read(&mut client).await.unwrap();
dump.push_value(response.pdu(), dump_cache_response(&response));
@@ -1618,15 +1656,13 @@ async fn version_two_aspa_withdraw_has_empty_provider_list() {
#[tokio::test]
async fn version_one_sends_router_key_but_not_aspa() {
let valid_spki = vec![
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x01, 0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
];
let router_key = RouterKey::new(Ski::default(), Asn::from(64496u32), valid_spki);
let aspa = Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32)]);
let snapshot = Snapshot::from_payloads(vec![
Payload::RouterKey(router_key),
Payload::Aspa(aspa),
]);
let snapshot =
Snapshot::from_payloads(vec![Payload::RouterKey(router_key), Payload::Aspa(aspa)]);
let cache = RtrCacheBuilder::new()
.session_ids(SessionIds::from_array([42, 42, 42]))
@@ -1664,7 +1700,10 @@ async fn version_one_sends_router_key_but_not_aspa() {
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
let res = timeout(Duration::from_millis(100), Header::read(&mut client)).await;
assert!(res.is_err(), "version 1 response should not contain ASPA PDUs");
assert!(
res.is_err(),
"version 1 response should not contain ASPA PDUs"
);
dump.print_pretty("version_one_sends_router_key_but_not_aspa");
shutdown_server(client, shutdown_tx, server_handle).await;
@@ -1793,9 +1832,10 @@ async fn tls_server_dns_name_san_strict_mode_rejects_ip_only_certificate() {
)
.unwrap_err();
assert!(err
.to_string()
.contains("does not contain a subjectAltName dNSName entry"));
assert!(
err.to_string()
.contains("does not contain a subjectAltName dNSName entry")
);
let _ = cache;
}
@@ -1825,7 +1865,10 @@ async fn tls_client_with_mismatched_san_ip_is_rejected() {
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
.await
.expect("timed out waiting for TLS session close");
assert!(read_res.is_err(), "server should close TLS session when client SAN IP mismatches");
assert!(
read_res.is_err(),
"server should close TLS session when client SAN IP mismatches"
);
dump.push_value(
0,
json!({
@@ -1847,7 +1890,8 @@ async fn invalid_timing_prevents_end_of_data_response() {
.build();
let server_cache = shared_cache(cache);
let (addr, shutdown_tx, server_handle) = start_session_server_returning_result(server_cache).await;
let (addr, shutdown_tx, server_handle) =
start_session_server_returning_result(server_cache).await;
let mut client = TcpStream::connect(addr).await.unwrap();
ResetQuery::new(1).write(&mut client).await.unwrap();
@@ -1859,7 +1903,10 @@ async fn invalid_timing_prevents_end_of_data_response() {
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
.await
.expect("timed out waiting for server close");
assert!(read_res.is_err(), "server should close instead of sending invalid EndOfData");
assert!(
read_res.is_err(),
"server should close instead of sending invalid EndOfData"
);
let _ = shutdown_tx.send(true);
let join = timeout(Duration::from_secs(1), server_handle)
@@ -1872,10 +1919,8 @@ async fn invalid_timing_prevents_end_of_data_response() {
#[tokio::test]
async fn invalid_aspa_prevents_snapshot_response() {
let snapshot = Snapshot::from_payloads(vec![Payload::Aspa(Aspa::new(
Asn::from(64496u32),
vec![],
))]);
let snapshot =
Snapshot::from_payloads(vec![Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![]))]);
let cache = RtrCacheBuilder::new()
.session_ids(SessionIds::from_array([42, 42, 42]))
.serials(serials_all(100))
@@ -1884,7 +1929,8 @@ async fn invalid_aspa_prevents_snapshot_response() {
.build();
let server_cache = shared_cache(cache);
let (addr, shutdown_tx, server_handle) = start_session_server_returning_result(server_cache).await;
let (addr, shutdown_tx, server_handle) =
start_session_server_returning_result(server_cache).await;
let mut client = TcpStream::connect(addr).await.unwrap();
ResetQuery::new(2).write(&mut client).await.unwrap();
@@ -1896,7 +1942,10 @@ async fn invalid_aspa_prevents_snapshot_response() {
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
.await
.expect("timed out waiting for server close");
assert!(read_res.is_err(), "server should close instead of sending invalid ASPA");
assert!(
read_res.is_err(),
"server should close instead of sending invalid ASPA"
);
let _ = shutdown_tx.send(true);
let join = timeout(Duration::from_secs(1), server_handle)
@@ -1922,7 +1971,8 @@ async fn invalid_router_key_prevents_snapshot_response() {
.build();
let server_cache = shared_cache(cache);
let (addr, shutdown_tx, server_handle) = start_session_server_returning_result(server_cache).await;
let (addr, shutdown_tx, server_handle) =
start_session_server_returning_result(server_cache).await;
let mut client = TcpStream::connect(addr).await.unwrap();
ResetQuery::new(1).write(&mut client).await.unwrap();
@@ -1934,7 +1984,10 @@ async fn invalid_router_key_prevents_snapshot_response() {
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
.await
.expect("timed out waiting for server close");
assert!(read_res.is_err(), "server should close instead of sending invalid RouterKey");
assert!(
read_res.is_err(),
"server should close instead of sending invalid RouterKey"
);
let _ = shutdown_tx.send(true);
let join = timeout(Duration::from_secs(1), server_handle)

View File

@@ -676,7 +676,10 @@ fn applies_filters_before_assertions_and_excludes_duplicates() {
}}
}}"#
);
log_slurm_input("applies_filters_before_assertions_and_excludes_duplicates", &json);
log_slurm_input(
"applies_filters_before_assertions_and_excludes_duplicates",
&json,
);
let slurm = SlurmFile::from_slice(json.as_bytes()).unwrap();
log_slurm_ok(
"applies_filters_before_assertions_and_excludes_duplicates",
@@ -788,9 +791,11 @@ fn rejects_hex_encoded_ski_and_aspa_customer_in_providers() {
"rejects_hex_encoded_ski_and_aspa_customer_in_providers.invalid_aspa",
&aspa_err,
);
assert!(aspa_err
.to_string()
.contains("providerAsns must not contain customerAsn"));
assert!(
aspa_err
.to_string()
.contains("providerAsns must not contain customerAsn")
);
}
#[test]
@@ -840,7 +845,10 @@ fn merges_multiple_slurm_files_without_conflict() {
),
])
.unwrap();
log_slurm_ok("merges_multiple_slurm_files_without_conflict.merged", &merged);
log_slurm_ok(
"merges_multiple_slurm_files_without_conflict.merged",
&merged,
);
assert_eq!(merged.version(), SlurmVersion::V2);
assert_eq!(merged.locally_added_assertions().prefix_assertions.len(), 1);

109
tests/test_slurm_admin.rs Normal file
View File

@@ -0,0 +1,109 @@
use std::fs;
use rpki::slurm::admin::SlurmAdmin;
fn valid_slurm() -> &'static str {
r#"{
"slurmVersion": 1,
"validationOutputFilters": {
"prefixFilters": [],
"bgpsecFilters": []
},
"locallyAddedAssertions": {
"prefixAssertions": [],
"bgpsecAssertions": []
}
}"#
}
#[test]
fn slurm_admin_rejects_unsafe_file_names() {
let temp = tempfile::tempdir().unwrap();
let admin = SlurmAdmin::new(temp.path());
for name in ["", "../x.slurm", "a/b.slurm", "a\\b.slurm", "a.json"] {
assert!(
admin
.put_file(name, valid_slurm(), "create_or_update")
.is_err(),
"{name}"
);
}
assert!(
admin
.put_file("policy.slurm", valid_slurm(), "create_or_update")
.is_ok()
);
assert!(
admin
.put_file("policy.slurm.disabled", valid_slurm(), "create_or_update")
.is_ok()
);
}
#[test]
fn writes_backs_up_toggles_deletes_and_rolls_back() {
let temp = tempfile::tempdir().unwrap();
let admin = SlurmAdmin::new(temp.path());
let create = admin
.put_file("policy.slurm", valid_slurm(), "create_or_update")
.unwrap();
assert!(temp.path().join("policy.slurm").is_file());
assert!(create.result.backup.is_none());
let update = admin
.put_file("policy.slurm", valid_slurm(), "create_or_update")
.unwrap();
assert!(update.result.backup.is_some());
let disable = admin.disable_file("policy.slurm").unwrap();
assert!(!temp.path().join("policy.slurm").exists());
assert!(temp.path().join("policy.slurm.disabled").is_file());
disable.rollback().unwrap();
assert!(temp.path().join("policy.slurm").is_file());
assert!(!temp.path().join("policy.slurm.disabled").exists());
admin.disable_file("policy.slurm").unwrap();
let enable = admin.enable_file("policy.slurm.disabled").unwrap();
assert!(temp.path().join("policy.slurm").is_file());
assert!(!temp.path().join("policy.slurm.disabled").exists());
enable.rollback().unwrap();
assert!(!temp.path().join("policy.slurm").exists());
assert!(temp.path().join("policy.slurm.disabled").is_file());
admin.enable_file("policy.slurm.disabled").unwrap();
let delete = admin.delete_file("policy.slurm").unwrap();
assert!(!temp.path().join("policy.slurm").exists());
assert!(delete.result.backup.is_some());
delete.rollback().unwrap();
assert!(temp.path().join("policy.slurm").is_file());
}
#[test]
fn lists_and_reads_slurm_files() {
let temp = tempfile::tempdir().unwrap();
let admin = SlurmAdmin::new(temp.path());
admin
.put_file("enabled.slurm", valid_slurm(), "create_or_update")
.unwrap();
admin
.put_file("disabled.slurm.disabled", valid_slurm(), "create_or_update")
.unwrap();
fs::write(temp.path().join("ignore.txt"), "not slurm").unwrap();
let files = admin.list_files().unwrap();
assert_eq!(files.len(), 2);
assert_eq!(files[0].name, "disabled.slurm.disabled");
assert!(!files[0].enabled);
assert_eq!(files[1].name, "enabled.slurm");
assert!(files[1].enabled);
let file = admin.read_file("enabled.slurm").unwrap();
assert_eq!(file.name, "enabled.slurm");
assert!(file.enabled);
assert!(file.content.contains("\"slurmVersion\""));
assert!(admin.read_file("missing.slurm").is_err());
}

View File

@@ -1,11 +1,13 @@
mod common;
use std::net::Ipv6Addr;
use std::net::{Ipv4Addr, Ipv6Addr};
use common::test_helper::{v4_origin, v6_origin};
use rpki::data_model::resources::as_resources::Asn;
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
use rpki::rtr::cache::{CacheAvailability, Delta, Snapshot};
use rpki::rtr::payload::Payload;
use rpki::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Ski};
use rpki::rtr::store::RtrStore;
#[test]
@@ -107,6 +109,60 @@ fn store_db_versioned_state_persists_and_restores_all_versions() {
);
}
#[test]
fn save_cache_state_persists_single_canonical_snapshot_for_all_versions() {
let temp = tempfile::tempdir().unwrap();
let store = RtrStore::open(temp.path()).unwrap();
let source = mixed_snapshot();
let snapshots = std::array::from_fn(|version| source.project_for_version(version as u8));
let deltas: [Option<&Delta>; 3] = [None, None, None];
let windows: [Option<(u32, u32)>; 3] = [None, None, None];
let clear = [true, true, true];
store
.save_cache_state_versioned(
CacheAvailability::Ready,
&snapshots,
&[1, 2, 3],
&[10, 11, 12],
&deltas,
&windows,
&clear,
)
.unwrap();
let restored_v0 = store.get_snapshot_for_version(0).unwrap().unwrap();
assert_eq!(restored_v0.origins().len(), 1);
assert_eq!(restored_v0.router_keys().len(), 0);
assert_eq!(restored_v0.aspas().len(), 0);
let restored_v1 = store.get_snapshot_for_version(1).unwrap().unwrap();
assert_eq!(restored_v1.origins().len(), 1);
assert_eq!(restored_v1.router_keys().len(), 1);
assert_eq!(restored_v1.aspas().len(), 0);
let restored_v2 = store.get_snapshot_for_version(2).unwrap().unwrap();
assert_eq!(restored_v2.origins().len(), 1);
assert_eq!(restored_v2.router_keys().len(), 1);
assert_eq!(restored_v2.aspas().len(), 1);
}
fn mixed_snapshot() -> Snapshot {
Snapshot::from_payloads(vec![
Payload::RouteOrigin(RouteOrigin::new(
IPAddressPrefix::new(IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)), 24),
24,
Asn::from(64496),
)),
Payload::RouterKey(RouterKey::new(
Ski::default(),
Asn::from(64497),
vec![1, 2, 3],
)),
Payload::Aspa(Aspa::new(Asn::from(64498), vec![Asn::from(64499)])),
])
}
#[test]
fn store_db_versioned_delta_window_wraparound_is_isolated_by_version() {
let dir = tempfile::tempdir().unwrap();
@@ -371,7 +427,8 @@ fn store_db_versioned_load_delta_window_requires_complete_range() {
.unwrap();
let err = store.load_delta_window_for_version(1, 10, 11).unwrap_err();
assert!(err
.to_string()
.contains("delta window starts at 10, but first persisted delta is Some(11)"));
assert!(
err.to_string()
.contains("delta window starts at 10, but first persisted delta is Some(11)")
);
}