|
|
|
|
@@ -5,6 +5,9 @@ use crate::audit::PublicationPointAudit;
|
|
|
|
|
use crate::audit_downloads::DownloadLogHandle;
|
|
|
|
|
use crate::data_model::ta::TrustAnchor;
|
|
|
|
|
use crate::replay::archive::ReplayArchiveIndex;
|
|
|
|
|
use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
|
|
|
|
|
use crate::replay::delta_fetch_http::PayloadDeltaReplayHttpFetcher;
|
|
|
|
|
use crate::replay::delta_fetch_rsync::PayloadDeltaReplayRsyncFetcher;
|
|
|
|
|
use crate::replay::fetch_http::PayloadReplayHttpFetcher;
|
|
|
|
|
use crate::replay::fetch_rsync::PayloadReplayRsyncFetcher;
|
|
|
|
|
use crate::sync::rrdp::Fetcher;
|
|
|
|
|
@@ -127,6 +130,7 @@ pub fn run_tree_from_tal_url_serial(
|
|
|
|
|
timing: None,
|
|
|
|
|
download_log: None,
|
|
|
|
|
replay_archive_index: None,
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
@@ -165,6 +169,7 @@ pub fn run_tree_from_tal_url_serial_audit(
|
|
|
|
|
timing: None,
|
|
|
|
|
download_log: Some(download_log.clone()),
|
|
|
|
|
replay_archive_index: None,
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
@@ -217,6 +222,7 @@ pub fn run_tree_from_tal_url_serial_audit_with_timing(
|
|
|
|
|
timing: Some(timing.clone()),
|
|
|
|
|
download_log: Some(download_log.clone()),
|
|
|
|
|
replay_archive_index: None,
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
@@ -269,6 +275,7 @@ pub fn run_tree_from_tal_and_ta_der_serial(
|
|
|
|
|
timing: None,
|
|
|
|
|
download_log: None,
|
|
|
|
|
replay_archive_index: None,
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
@@ -310,6 +317,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit(
|
|
|
|
|
timing: None,
|
|
|
|
|
download_log: Some(download_log.clone()),
|
|
|
|
|
replay_archive_index: None,
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
@@ -365,6 +373,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit_with_timing(
|
|
|
|
|
timing: Some(timing.clone()),
|
|
|
|
|
download_log: Some(download_log.clone()),
|
|
|
|
|
replay_archive_index: None,
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
@@ -408,7 +417,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial(
|
|
|
|
|
let discovery =
|
|
|
|
|
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
|
|
|
|
let replay_index = Arc::new(
|
|
|
|
|
ReplayArchiveIndex::load(payload_archive_root, payload_locks_path)
|
|
|
|
|
ReplayArchiveIndex::load_allow_missing_rsync_modules(payload_archive_root, payload_locks_path)
|
|
|
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?,
|
|
|
|
|
);
|
|
|
|
|
let http_fetcher = PayloadReplayHttpFetcher::new(replay_index.clone())
|
|
|
|
|
@@ -424,6 +433,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial(
|
|
|
|
|
timing: None,
|
|
|
|
|
download_log: None,
|
|
|
|
|
replay_archive_index: Some(replay_index),
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
@@ -455,7 +465,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
|
|
|
|
|
let discovery =
|
|
|
|
|
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
|
|
|
|
let replay_index = Arc::new(
|
|
|
|
|
ReplayArchiveIndex::load(payload_archive_root, payload_locks_path)
|
|
|
|
|
ReplayArchiveIndex::load_allow_missing_rsync_modules(payload_archive_root, payload_locks_path)
|
|
|
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?,
|
|
|
|
|
);
|
|
|
|
|
let http_fetcher = PayloadReplayHttpFetcher::new(replay_index.clone())
|
|
|
|
|
@@ -472,6 +482,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
|
|
|
|
|
timing: None,
|
|
|
|
|
download_log: Some(download_log.clone()),
|
|
|
|
|
replay_archive_index: Some(replay_index),
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
@@ -517,7 +528,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
|
|
|
|
|
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
|
|
|
|
drop(_tal);
|
|
|
|
|
let replay_index = Arc::new(
|
|
|
|
|
ReplayArchiveIndex::load(payload_archive_root, payload_locks_path)
|
|
|
|
|
ReplayArchiveIndex::load_allow_missing_rsync_modules(payload_archive_root, payload_locks_path)
|
|
|
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?,
|
|
|
|
|
);
|
|
|
|
|
let http_fetcher = PayloadReplayHttpFetcher::new(replay_index.clone())
|
|
|
|
|
@@ -534,6 +545,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
|
|
|
|
|
timing: Some(timing.clone()),
|
|
|
|
|
download_log: Some(download_log.clone()),
|
|
|
|
|
replay_archive_index: Some(replay_index),
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
@@ -563,6 +575,239 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn build_payload_replay_runner<'a>(
|
|
|
|
|
store: &'a crate::storage::RocksStore,
|
|
|
|
|
policy: &'a crate::policy::Policy,
|
|
|
|
|
replay_index: Arc<ReplayArchiveIndex>,
|
|
|
|
|
http_fetcher: &'a PayloadReplayHttpFetcher,
|
|
|
|
|
rsync_fetcher: &'a PayloadReplayRsyncFetcher,
|
|
|
|
|
validation_time: time::OffsetDateTime,
|
|
|
|
|
timing: Option<TimingHandle>,
|
|
|
|
|
download_log: Option<DownloadLogHandle>,
|
|
|
|
|
) -> Rpkiv1PublicationPointRunner<'a> {
|
|
|
|
|
Rpkiv1PublicationPointRunner {
|
|
|
|
|
store,
|
|
|
|
|
policy,
|
|
|
|
|
http_fetcher,
|
|
|
|
|
rsync_fetcher,
|
|
|
|
|
validation_time,
|
|
|
|
|
timing,
|
|
|
|
|
download_log,
|
|
|
|
|
replay_archive_index: Some(replay_index),
|
|
|
|
|
replay_delta_index: None,
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn build_payload_delta_replay_runner<'a>(
|
|
|
|
|
store: &'a crate::storage::RocksStore,
|
|
|
|
|
policy: &'a crate::policy::Policy,
|
|
|
|
|
delta_index: Arc<ReplayDeltaArchiveIndex>,
|
|
|
|
|
http_fetcher: &'a PayloadDeltaReplayHttpFetcher,
|
|
|
|
|
rsync_fetcher: &'a PayloadDeltaReplayRsyncFetcher,
|
|
|
|
|
validation_time: time::OffsetDateTime,
|
|
|
|
|
timing: Option<TimingHandle>,
|
|
|
|
|
download_log: Option<DownloadLogHandle>,
|
|
|
|
|
) -> Rpkiv1PublicationPointRunner<'a> {
|
|
|
|
|
Rpkiv1PublicationPointRunner {
|
|
|
|
|
store,
|
|
|
|
|
policy,
|
|
|
|
|
http_fetcher,
|
|
|
|
|
rsync_fetcher,
|
|
|
|
|
validation_time,
|
|
|
|
|
timing,
|
|
|
|
|
download_log,
|
|
|
|
|
replay_archive_index: None,
|
|
|
|
|
replay_delta_index: Some(delta_index),
|
|
|
|
|
rrdp_dedup: true,
|
|
|
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
rsync_dedup: true,
|
|
|
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run_payload_delta_replay_audit_inner(
|
|
|
|
|
store: &crate::storage::RocksStore,
|
|
|
|
|
policy: &crate::policy::Policy,
|
|
|
|
|
discovery: DiscoveredRootCaInstance,
|
|
|
|
|
base_payload_archive_root: &std::path::Path,
|
|
|
|
|
base_locks_path: &std::path::Path,
|
|
|
|
|
delta_payload_archive_root: &std::path::Path,
|
|
|
|
|
delta_locks_path: &std::path::Path,
|
|
|
|
|
base_validation_time: time::OffsetDateTime,
|
|
|
|
|
validation_time: time::OffsetDateTime,
|
|
|
|
|
config: &TreeRunConfig,
|
|
|
|
|
timing: Option<TimingHandle>,
|
|
|
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
|
|
|
let base_index = Arc::new(
|
|
|
|
|
ReplayArchiveIndex::load_allow_missing_rsync_modules(
|
|
|
|
|
base_payload_archive_root,
|
|
|
|
|
base_locks_path,
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?,
|
|
|
|
|
);
|
|
|
|
|
let delta_index = Arc::new(
|
|
|
|
|
ReplayDeltaArchiveIndex::load(delta_payload_archive_root, delta_locks_path)
|
|
|
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?,
|
|
|
|
|
);
|
|
|
|
|
delta_index
|
|
|
|
|
.validate_base_locks_sha256_file(base_locks_path)
|
|
|
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?;
|
|
|
|
|
|
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
|
|
|
&discovery.trust_anchor,
|
|
|
|
|
derive_tal_id(&discovery),
|
|
|
|
|
None,
|
|
|
|
|
&discovery.ca_instance,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let base_http_fetcher = PayloadReplayHttpFetcher::new(base_index.clone())
|
|
|
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?;
|
|
|
|
|
let base_rsync_fetcher = PayloadReplayRsyncFetcher::new(base_index.clone());
|
|
|
|
|
if let Some(t) = timing.as_ref() {
|
|
|
|
|
let _phase = t.span_phase("payload_delta_replay_base_total");
|
|
|
|
|
let base_runner = build_payload_replay_runner(
|
|
|
|
|
store,
|
|
|
|
|
policy,
|
|
|
|
|
base_index.clone(),
|
|
|
|
|
&base_http_fetcher,
|
|
|
|
|
&base_rsync_fetcher,
|
|
|
|
|
base_validation_time,
|
|
|
|
|
Some(t.clone()),
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
let _base = run_tree_serial(root.clone(), &base_runner, config)?;
|
|
|
|
|
} else {
|
|
|
|
|
let base_runner = build_payload_replay_runner(
|
|
|
|
|
store,
|
|
|
|
|
policy,
|
|
|
|
|
base_index.clone(),
|
|
|
|
|
&base_http_fetcher,
|
|
|
|
|
&base_rsync_fetcher,
|
|
|
|
|
base_validation_time,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
let _base = run_tree_serial(root.clone(), &base_runner, config)?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let delta_http_fetcher = PayloadDeltaReplayHttpFetcher::from_index(delta_index.clone())
|
|
|
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?;
|
|
|
|
|
let delta_rsync_fetcher = PayloadDeltaReplayRsyncFetcher::new(base_index, delta_index.clone());
|
|
|
|
|
let download_log = DownloadLogHandle::new();
|
|
|
|
|
let (tree, publication_points) = if let Some(t) = timing.as_ref() {
|
|
|
|
|
let _phase = t.span_phase("payload_delta_replay_target_total");
|
|
|
|
|
let delta_runner = build_payload_delta_replay_runner(
|
|
|
|
|
store,
|
|
|
|
|
policy,
|
|
|
|
|
delta_index,
|
|
|
|
|
&delta_http_fetcher,
|
|
|
|
|
&delta_rsync_fetcher,
|
|
|
|
|
base_validation_time,
|
|
|
|
|
Some(t.clone()),
|
|
|
|
|
Some(download_log.clone()),
|
|
|
|
|
);
|
|
|
|
|
let TreeRunAuditOutput {
|
|
|
|
|
tree,
|
|
|
|
|
publication_points,
|
|
|
|
|
} = run_tree_serial_audit(root, &delta_runner, config)?;
|
|
|
|
|
(tree, publication_points)
|
|
|
|
|
} else {
|
|
|
|
|
let delta_runner = build_payload_delta_replay_runner(
|
|
|
|
|
store,
|
|
|
|
|
policy,
|
|
|
|
|
delta_index,
|
|
|
|
|
&delta_http_fetcher,
|
|
|
|
|
&delta_rsync_fetcher,
|
|
|
|
|
validation_time,
|
|
|
|
|
None,
|
|
|
|
|
Some(download_log.clone()),
|
|
|
|
|
);
|
|
|
|
|
let TreeRunAuditOutput {
|
|
|
|
|
tree,
|
|
|
|
|
publication_points,
|
|
|
|
|
} = run_tree_serial_audit(root, &delta_runner, config)?;
|
|
|
|
|
(tree, publication_points)
|
|
|
|
|
};
|
|
|
|
|
let downloads = download_log.snapshot_events();
|
|
|
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
|
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
|
|
|
discovery,
|
|
|
|
|
tree,
|
|
|
|
|
publication_points,
|
|
|
|
|
downloads,
|
|
|
|
|
download_stats,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_payload_delta_replay_serial_audit(
|
|
|
|
|
store: &crate::storage::RocksStore,
|
|
|
|
|
policy: &crate::policy::Policy,
|
|
|
|
|
tal_bytes: &[u8],
|
|
|
|
|
ta_der: &[u8],
|
|
|
|
|
resolved_ta_uri: Option<&Url>,
|
|
|
|
|
base_payload_archive_root: &std::path::Path,
|
|
|
|
|
base_locks_path: &std::path::Path,
|
|
|
|
|
delta_payload_archive_root: &std::path::Path,
|
|
|
|
|
delta_locks_path: &std::path::Path,
|
|
|
|
|
base_validation_time: time::OffsetDateTime,
|
|
|
|
|
validation_time: time::OffsetDateTime,
|
|
|
|
|
config: &TreeRunConfig,
|
|
|
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
|
|
|
let discovery =
|
|
|
|
|
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
|
|
|
|
run_payload_delta_replay_audit_inner(
|
|
|
|
|
store,
|
|
|
|
|
policy,
|
|
|
|
|
discovery,
|
|
|
|
|
base_payload_archive_root,
|
|
|
|
|
base_locks_path,
|
|
|
|
|
delta_payload_archive_root,
|
|
|
|
|
delta_locks_path,
|
|
|
|
|
base_validation_time,
|
|
|
|
|
validation_time,
|
|
|
|
|
config,
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_payload_delta_replay_serial_audit_with_timing(
|
|
|
|
|
store: &crate::storage::RocksStore,
|
|
|
|
|
policy: &crate::policy::Policy,
|
|
|
|
|
tal_bytes: &[u8],
|
|
|
|
|
ta_der: &[u8],
|
|
|
|
|
resolved_ta_uri: Option<&Url>,
|
|
|
|
|
base_payload_archive_root: &std::path::Path,
|
|
|
|
|
base_locks_path: &std::path::Path,
|
|
|
|
|
delta_payload_archive_root: &std::path::Path,
|
|
|
|
|
delta_locks_path: &std::path::Path,
|
|
|
|
|
base_validation_time: time::OffsetDateTime,
|
|
|
|
|
validation_time: time::OffsetDateTime,
|
|
|
|
|
config: &TreeRunConfig,
|
|
|
|
|
timing: &TimingHandle,
|
|
|
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
|
|
|
let _tal = timing.span_phase("tal_bootstrap");
|
|
|
|
|
let discovery =
|
|
|
|
|
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
|
|
|
|
drop(_tal);
|
|
|
|
|
run_payload_delta_replay_audit_inner(
|
|
|
|
|
store,
|
|
|
|
|
policy,
|
|
|
|
|
discovery,
|
|
|
|
|
base_payload_archive_root,
|
|
|
|
|
base_locks_path,
|
|
|
|
|
delta_payload_archive_root,
|
|
|
|
|
delta_locks_path,
|
|
|
|
|
base_validation_time,
|
|
|
|
|
validation_time,
|
|
|
|
|
config,
|
|
|
|
|
Some(timing.clone()),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod replay_api_tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
@@ -587,6 +832,58 @@ mod replay_api_tests {
|
|
|
|
|
(tal_bytes, ta_der, archive_root, locks_path, validation_time)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apnic_multi_rir_replay_inputs() -> (
|
|
|
|
|
Vec<u8>,
|
|
|
|
|
Vec<u8>,
|
|
|
|
|
std::path::PathBuf,
|
|
|
|
|
std::path::PathBuf,
|
|
|
|
|
time::OffsetDateTime,
|
|
|
|
|
) {
|
|
|
|
|
let tal_bytes = std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal")
|
|
|
|
|
.expect("read apnic tal fixture");
|
|
|
|
|
let ta_der =
|
|
|
|
|
std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta fixture");
|
|
|
|
|
let archive_root =
|
|
|
|
|
std::path::PathBuf::from("../../rpki/target/live/20260316-112341-multi-final3/apnic/base-payload-archive");
|
|
|
|
|
let locks_path =
|
|
|
|
|
std::path::PathBuf::from("../../rpki/target/live/20260316-112341-multi-final3/apnic/base-locks.json");
|
|
|
|
|
let validation_time = time::OffsetDateTime::parse("2026-03-16T11:49:48+08:00", &Rfc3339)
|
|
|
|
|
.expect("parse validation time");
|
|
|
|
|
(tal_bytes, ta_der, archive_root, locks_path, validation_time)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apnic_delta_replay_inputs() -> (
|
|
|
|
|
Vec<u8>,
|
|
|
|
|
Vec<u8>,
|
|
|
|
|
std::path::PathBuf,
|
|
|
|
|
std::path::PathBuf,
|
|
|
|
|
std::path::PathBuf,
|
|
|
|
|
std::path::PathBuf,
|
|
|
|
|
time::OffsetDateTime,
|
|
|
|
|
) {
|
|
|
|
|
let tal_bytes = std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal")
|
|
|
|
|
.expect("read apnic tal fixture");
|
|
|
|
|
let ta_der =
|
|
|
|
|
std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta fixture");
|
|
|
|
|
let root =
|
|
|
|
|
std::path::PathBuf::from("target/live/apnic_delta_demo/20260315-170223-autoplay");
|
|
|
|
|
let base_archive = root.join("base-payload-archive");
|
|
|
|
|
let base_locks = root.join("base-locks.json");
|
|
|
|
|
let delta_archive = root.join("payload-delta-archive");
|
|
|
|
|
let delta_locks = root.join("locks-delta.json");
|
|
|
|
|
let validation_time = time::OffsetDateTime::parse("2026-03-15T10:00:00Z", &Rfc3339)
|
|
|
|
|
.expect("parse validation time");
|
|
|
|
|
(
|
|
|
|
|
tal_bytes,
|
|
|
|
|
ta_der,
|
|
|
|
|
base_archive,
|
|
|
|
|
base_locks,
|
|
|
|
|
delta_archive,
|
|
|
|
|
delta_locks,
|
|
|
|
|
validation_time,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn payload_replay_api_reports_setup_error_for_missing_archive() {
|
|
|
|
|
let temp = tempfile::tempdir().expect("tempdir");
|
|
|
|
|
@@ -656,6 +953,37 @@ mod replay_api_tests {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn payload_replay_api_root_only_apnic_multi_rir_bundle_runs_with_lenient_rsync_modules() {
|
|
|
|
|
let temp = tempfile::tempdir().expect("tempdir");
|
|
|
|
|
let store = crate::storage::RocksStore::open(&temp.path().join("db")).expect("open db");
|
|
|
|
|
let (tal_bytes, ta_der, archive_root, locks_path, validation_time) =
|
|
|
|
|
apnic_multi_rir_replay_inputs();
|
|
|
|
|
assert!(archive_root.is_dir(), "payload replay archive missing: {}", archive_root.display());
|
|
|
|
|
assert!(locks_path.is_file(), "payload replay locks missing: {}", locks_path.display());
|
|
|
|
|
|
|
|
|
|
let out = run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
|
|
|
|
|
&store,
|
|
|
|
|
&crate::policy::Policy::default(),
|
|
|
|
|
&tal_bytes,
|
|
|
|
|
&ta_der,
|
|
|
|
|
None,
|
|
|
|
|
&archive_root,
|
|
|
|
|
&locks_path,
|
|
|
|
|
validation_time,
|
|
|
|
|
&TreeRunConfig {
|
|
|
|
|
max_depth: Some(0),
|
|
|
|
|
max_instances: Some(1),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.expect("run replay root-only audit");
|
|
|
|
|
|
|
|
|
|
assert_eq!(out.tree.instances_processed, 1);
|
|
|
|
|
assert_eq!(out.tree.instances_failed, 0);
|
|
|
|
|
assert_eq!(out.publication_points.len(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn payload_replay_api_root_only_apnic_archive_runs_with_timing() {
|
|
|
|
|
let temp = tempfile::tempdir().expect("tempdir");
|
|
|
|
|
@@ -714,4 +1042,184 @@ mod replay_api_tests {
|
|
|
|
|
>= 1
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn payload_delta_replay_api_rejects_base_locks_sha_mismatch() {
|
|
|
|
|
let temp = tempfile::tempdir().expect("tempdir");
|
|
|
|
|
let store = crate::storage::RocksStore::open(&temp.path().join("db")).expect("open db");
|
|
|
|
|
let (
|
|
|
|
|
tal_bytes,
|
|
|
|
|
ta_der,
|
|
|
|
|
base_archive,
|
|
|
|
|
_base_locks,
|
|
|
|
|
delta_archive,
|
|
|
|
|
delta_locks,
|
|
|
|
|
validation_time,
|
|
|
|
|
) = apnic_delta_replay_inputs();
|
|
|
|
|
let wrong_base_locks = temp.path().join("wrong-base-locks.json");
|
|
|
|
|
std::fs::write(&wrong_base_locks, b"wrong-base-locks").expect("write wrong base locks");
|
|
|
|
|
let err = run_tree_from_tal_and_ta_der_payload_delta_replay_serial_audit(
|
|
|
|
|
&store,
|
|
|
|
|
&crate::policy::Policy::default(),
|
|
|
|
|
&tal_bytes,
|
|
|
|
|
&ta_der,
|
|
|
|
|
None,
|
|
|
|
|
&base_archive,
|
|
|
|
|
&wrong_base_locks,
|
|
|
|
|
&delta_archive,
|
|
|
|
|
&delta_locks,
|
|
|
|
|
validation_time,
|
|
|
|
|
validation_time,
|
|
|
|
|
&TreeRunConfig {
|
|
|
|
|
max_depth: Some(0),
|
|
|
|
|
max_instances: Some(1),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap_err();
|
|
|
|
|
assert!(matches!(err, RunTreeFromTalError::Replay(_)), "{err}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn payload_delta_replay_api_reports_setup_error_for_missing_inputs() {
|
|
|
|
|
let temp = tempfile::tempdir().expect("tempdir");
|
|
|
|
|
let store = crate::storage::RocksStore::open(&temp.path().join("db")).expect("open db");
|
|
|
|
|
let tal_bytes = std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal")
|
|
|
|
|
.expect("read apnic tal fixture");
|
|
|
|
|
let ta_der =
|
|
|
|
|
std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta fixture");
|
|
|
|
|
let err = run_tree_from_tal_and_ta_der_payload_delta_replay_serial_audit(
|
|
|
|
|
&store,
|
|
|
|
|
&crate::policy::Policy::default(),
|
|
|
|
|
&tal_bytes,
|
|
|
|
|
&ta_der,
|
|
|
|
|
None,
|
|
|
|
|
std::path::Path::new("tests/fixtures/missing-base-archive"),
|
|
|
|
|
std::path::Path::new("tests/fixtures/missing-base-locks.json"),
|
|
|
|
|
std::path::Path::new("tests/fixtures/missing-delta-archive"),
|
|
|
|
|
std::path::Path::new("tests/fixtures/missing-delta-locks.json"),
|
|
|
|
|
time::OffsetDateTime::now_utc(),
|
|
|
|
|
time::OffsetDateTime::now_utc(),
|
|
|
|
|
&TreeRunConfig {
|
|
|
|
|
max_depth: Some(0),
|
|
|
|
|
max_instances: Some(1),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.unwrap_err();
|
|
|
|
|
assert!(matches!(err, RunTreeFromTalError::Replay(_)), "{err}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn payload_delta_replay_api_root_only_apnic_bundle_runs() {
|
|
|
|
|
let temp = tempfile::tempdir().expect("tempdir");
|
|
|
|
|
let store = crate::storage::RocksStore::open(&temp.path().join("db")).expect("open db");
|
|
|
|
|
let (
|
|
|
|
|
tal_bytes,
|
|
|
|
|
ta_der,
|
|
|
|
|
base_archive,
|
|
|
|
|
base_locks,
|
|
|
|
|
delta_archive,
|
|
|
|
|
delta_locks,
|
|
|
|
|
validation_time,
|
|
|
|
|
) = apnic_delta_replay_inputs();
|
|
|
|
|
assert!(
|
|
|
|
|
base_archive.is_dir(),
|
|
|
|
|
"base archive missing: {}",
|
|
|
|
|
base_archive.display()
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
base_locks.is_file(),
|
|
|
|
|
"base locks missing: {}",
|
|
|
|
|
base_locks.display()
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
delta_archive.is_dir(),
|
|
|
|
|
"delta archive missing: {}",
|
|
|
|
|
delta_archive.display()
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
delta_locks.is_file(),
|
|
|
|
|
"delta locks missing: {}",
|
|
|
|
|
delta_locks.display()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let out = run_tree_from_tal_and_ta_der_payload_delta_replay_serial_audit(
|
|
|
|
|
&store,
|
|
|
|
|
&crate::policy::Policy::default(),
|
|
|
|
|
&tal_bytes,
|
|
|
|
|
&ta_der,
|
|
|
|
|
None,
|
|
|
|
|
&base_archive,
|
|
|
|
|
&base_locks,
|
|
|
|
|
&delta_archive,
|
|
|
|
|
&delta_locks,
|
|
|
|
|
validation_time,
|
|
|
|
|
validation_time,
|
|
|
|
|
&TreeRunConfig {
|
|
|
|
|
max_depth: Some(0),
|
|
|
|
|
max_instances: Some(1),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.expect("run delta replay root-only audit");
|
|
|
|
|
|
|
|
|
|
assert_eq!(out.tree.instances_processed, 1);
|
|
|
|
|
assert_eq!(out.tree.instances_failed, 0);
|
|
|
|
|
assert_eq!(out.publication_points.len(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn payload_delta_replay_api_root_only_apnic_bundle_runs_with_timing() {
|
|
|
|
|
let temp = tempfile::tempdir().expect("tempdir");
|
|
|
|
|
let db_path = temp.path().join("db");
|
|
|
|
|
let store = crate::storage::RocksStore::open(&db_path).expect("open db");
|
|
|
|
|
let (
|
|
|
|
|
tal_bytes,
|
|
|
|
|
ta_der,
|
|
|
|
|
base_archive,
|
|
|
|
|
base_locks,
|
|
|
|
|
delta_archive,
|
|
|
|
|
delta_locks,
|
|
|
|
|
validation_time,
|
|
|
|
|
) = apnic_delta_replay_inputs();
|
|
|
|
|
let timing = TimingHandle::new(TimingMeta {
|
|
|
|
|
recorded_at_utc_rfc3339: "2026-03-16T00:00:00Z".to_string(),
|
|
|
|
|
validation_time_utc_rfc3339: "2026-03-15T10:00:00Z".to_string(),
|
|
|
|
|
tal_url: None,
|
|
|
|
|
db_path: Some(db_path.to_string_lossy().into_owned()),
|
|
|
|
|
});
|
|
|
|
|
let out = run_tree_from_tal_and_ta_der_payload_delta_replay_serial_audit_with_timing(
|
|
|
|
|
&store,
|
|
|
|
|
&crate::policy::Policy::default(),
|
|
|
|
|
&tal_bytes,
|
|
|
|
|
&ta_der,
|
|
|
|
|
None,
|
|
|
|
|
&base_archive,
|
|
|
|
|
&base_locks,
|
|
|
|
|
&delta_archive,
|
|
|
|
|
&delta_locks,
|
|
|
|
|
validation_time,
|
|
|
|
|
validation_time,
|
|
|
|
|
&TreeRunConfig {
|
|
|
|
|
max_depth: Some(0),
|
|
|
|
|
max_instances: Some(1),
|
|
|
|
|
},
|
|
|
|
|
&timing,
|
|
|
|
|
)
|
|
|
|
|
.expect("run delta replay root-only audit with timing");
|
|
|
|
|
assert_eq!(out.tree.instances_processed, 1);
|
|
|
|
|
let timing_json = temp.path().join("timing_delta_replay.json");
|
|
|
|
|
timing
|
|
|
|
|
.write_json(&timing_json, 20)
|
|
|
|
|
.expect("write timing json");
|
|
|
|
|
let json: serde_json::Value =
|
|
|
|
|
serde_json::from_slice(&std::fs::read(&timing_json).expect("read timing json"))
|
|
|
|
|
.expect("parse timing json");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
json["phases"]["payload_delta_replay_base_total"]["count"].as_u64(),
|
|
|
|
|
Some(1)
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
json["phases"]["payload_delta_replay_target_total"]["count"].as_u64(),
|
|
|
|
|
Some(1)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|