2492 lines
82 KiB
Rust
2492 lines
82 KiB
Rust
use url::Url;
|
|
|
|
use crate::analysis::timing::TimingHandle;
|
|
use crate::audit::PublicationPointAudit;
|
|
use crate::audit_downloads::DownloadLogHandle;
|
|
use crate::ccr::CcrAccumulator;
|
|
use crate::current_repo_index::{CurrentRepoIndexHandle, CurrentRepoObject};
|
|
use crate::data_model::ta::TrustAnchor;
|
|
use crate::parallel::config::{ParallelPhase1Config, ParallelPhase2Config};
|
|
use crate::parallel::repo_runtime::{Phase1RepoSyncRuntime, RepoSyncRuntime};
|
|
use crate::parallel::repo_worker::{
|
|
LiveRepoTransportExecutor, RepoTransportWorkerPool, RepoWorkerPoolConfig,
|
|
};
|
|
use crate::parallel::run_coordinator::GlobalRunCoordinator;
|
|
use crate::parallel::types::{TalInputSpec, TalSource};
|
|
use crate::replay::archive::ReplayArchiveIndex;
|
|
use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
|
|
use crate::replay::delta_fetch_http::PayloadDeltaReplayHttpFetcher;
|
|
use crate::replay::delta_fetch_rsync::{
|
|
PayloadDeltaReplayCurrentStoreRsyncFetcher, PayloadDeltaReplayRsyncFetcher,
|
|
};
|
|
use crate::replay::fetch_http::PayloadReplayHttpFetcher;
|
|
use crate::replay::fetch_rsync::PayloadReplayRsyncFetcher;
|
|
use crate::sync::rrdp::Fetcher;
|
|
use crate::validation::from_tal::{
|
|
DiscoveredRootCaInstance, FromTalError, canonical_tal_rsync_uri_from_bytes,
|
|
discover_root_ca_instance_from_tal_and_ta_der,
|
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name,
|
|
discover_root_ca_instance_from_tal_url,
|
|
discover_root_ca_instance_from_tal_url_with_strict_name,
|
|
discover_root_ca_instance_from_tal_with_fetchers,
|
|
discover_root_ca_instance_from_tal_with_fetchers_strict_name,
|
|
};
|
|
use crate::validation::objects::ParallelRoaWorkerPool;
|
|
use crate::validation::tree::{
|
|
CaInstanceHandle, TreeRunAuditOutput, TreeRunConfig, TreeRunError, TreeRunOutput,
|
|
run_tree_serial, run_tree_serial_audit, run_tree_serial_audit_multi_root,
|
|
};
|
|
use crate::validation::tree_parallel::{
|
|
run_tree_parallel_phase2_audit, run_tree_parallel_phase2_audit_multi_root,
|
|
};
|
|
use crate::validation::tree_runner::Rpkiv1PublicationPointRunner;
|
|
use std::collections::HashMap;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
fn tal_id_from_url_like(s: &str) -> Option<String> {
|
|
let url = Url::parse(s).ok()?;
|
|
if let Some(last) = url
|
|
.path_segments()
|
|
.and_then(|segments| segments.filter(|seg| !seg.is_empty()).next_back())
|
|
{
|
|
let stem = last.rsplit_once('.').map(|(stem, _)| stem).unwrap_or(last);
|
|
let trimmed = stem.trim();
|
|
if !trimmed.is_empty() {
|
|
return Some(trimmed.to_string());
|
|
}
|
|
}
|
|
url.host_str().map(|host| host.to_string())
|
|
}
|
|
|
|
fn derive_tal_id(discovery: &DiscoveredRootCaInstance) -> String {
|
|
discovery
|
|
.tal_url
|
|
.as_deref()
|
|
.and_then(tal_id_from_url_like)
|
|
.or_else(|| {
|
|
discovery
|
|
.trust_anchor
|
|
.resolved_ta_uri
|
|
.as_ref()
|
|
.and_then(|uri| tal_id_from_url_like(uri.as_str()))
|
|
})
|
|
.or_else(|| {
|
|
discovery
|
|
.trust_anchor
|
|
.tal
|
|
.ta_uris
|
|
.first()
|
|
.and_then(|uri| tal_id_from_url_like(uri.as_str()))
|
|
})
|
|
.unwrap_or_else(|| "unknown-tal".to_string())
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct RunTreeFromTalOutput {
|
|
pub discovery: DiscoveredRootCaInstance,
|
|
pub tree: TreeRunOutput,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct RunTreeFromTalAuditOutput {
|
|
pub discovery: DiscoveredRootCaInstance,
|
|
pub discoveries: Vec<DiscoveredRootCaInstance>,
|
|
pub successful_tal_inputs: Vec<TalInputSpec>,
|
|
pub tree: TreeRunOutput,
|
|
pub publication_points: Vec<PublicationPointAudit>,
|
|
pub downloads: Vec<crate::audit::AuditDownloadEvent>,
|
|
pub download_stats: crate::audit::AuditDownloadStats,
|
|
pub current_repo_objects: Vec<CurrentRepoObject>,
|
|
pub ccr_accumulator: Option<CcrAccumulator>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct TalRootDiscovery {
|
|
pub tal_input: TalInputSpec,
|
|
pub discovery: DiscoveredRootCaInstance,
|
|
pub root_handle: CaInstanceHandle,
|
|
}
|
|
|
|
fn snapshot_current_repo_objects(
|
|
current_repo_index: Option<&CurrentRepoIndexHandle>,
|
|
collect: bool,
|
|
) -> Vec<CurrentRepoObject> {
|
|
if !collect {
|
|
return Vec::new();
|
|
}
|
|
current_repo_index
|
|
.and_then(|handle| handle.lock().ok().map(|idx| idx.snapshot_objects()))
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn make_live_runner<'a>(
|
|
store: &'a crate::storage::RocksStore,
|
|
policy: &'a crate::policy::Policy,
|
|
http_fetcher: &'a dyn Fetcher,
|
|
rsync_fetcher: &'a dyn crate::fetch::rsync::RsyncFetcher,
|
|
validation_time: time::OffsetDateTime,
|
|
timing: Option<TimingHandle>,
|
|
download_log: Option<DownloadLogHandle>,
|
|
current_repo_index: Option<CurrentRepoIndexHandle>,
|
|
repo_sync_runtime: Option<Arc<dyn RepoSyncRuntime>>,
|
|
parallel_phase2_config: Option<ParallelPhase2Config>,
|
|
ccr_accumulator: Option<CcrAccumulator>,
|
|
persist_vcir: bool,
|
|
) -> Rpkiv1PublicationPointRunner<'a> {
|
|
let parallel_roa_worker_pool = parallel_phase2_config
|
|
.as_ref()
|
|
.and_then(|config| ParallelRoaWorkerPool::new(config).ok());
|
|
Rpkiv1PublicationPointRunner {
|
|
store,
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
timing,
|
|
download_log,
|
|
replay_archive_index: None,
|
|
replay_delta_index: None,
|
|
rrdp_dedup: true,
|
|
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
|
rsync_dedup: true,
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
current_repo_index,
|
|
repo_sync_runtime,
|
|
parallel_phase2_config,
|
|
parallel_roa_worker_pool,
|
|
ccr_accumulator: ccr_accumulator.map(Mutex::new),
|
|
persist_vcir,
|
|
}
|
|
}
|
|
|
|
fn build_phase1_repo_sync_runtime<H, R>(
|
|
store: Arc<crate::storage::RocksStore>,
|
|
policy: &crate::policy::Policy,
|
|
http_fetcher: &H,
|
|
rsync_fetcher: &R,
|
|
parallel_config: ParallelPhase1Config,
|
|
timing: Option<TimingHandle>,
|
|
download_log: Option<DownloadLogHandle>,
|
|
tal_inputs: Vec<crate::parallel::types::TalInputSpec>,
|
|
) -> Result<(Arc<dyn RepoSyncRuntime>, CurrentRepoIndexHandle), RunTreeFromTalError>
|
|
where
|
|
H: Fetcher + Clone + 'static,
|
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
|
{
|
|
let coordinator = GlobalRunCoordinator::new(parallel_config.clone(), tal_inputs);
|
|
let current_repo_index = coordinator.current_repo_index_handle();
|
|
let rsync_fetcher_arc = Arc::new(rsync_fetcher.clone());
|
|
let executor = LiveRepoTransportExecutor::new(
|
|
Arc::clone(&store),
|
|
current_repo_index.clone(),
|
|
Arc::new(http_fetcher.clone()),
|
|
Arc::clone(&rsync_fetcher_arc),
|
|
timing,
|
|
download_log,
|
|
);
|
|
let pool = RepoTransportWorkerPool::new(RepoWorkerPoolConfig::from(¶llel_config), executor)
|
|
.map_err(RunTreeFromTalError::Replay)?;
|
|
let resolver: Arc<dyn Fn(&str) -> String + Send + Sync> =
|
|
Arc::new(move |base: &str| rsync_fetcher_arc.dedup_key(base));
|
|
let _ = policy; // policy reserved for later runtime-level decisions
|
|
let runtime = Arc::new(Phase1RepoSyncRuntime::new(
|
|
coordinator,
|
|
pool,
|
|
resolver,
|
|
policy.sync_preference,
|
|
));
|
|
Ok((runtime, current_repo_index))
|
|
}
|
|
|
|
fn root_discovery_from_tal_input(
|
|
tal_input: &TalInputSpec,
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
strict_name: bool,
|
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
|
match &tal_input.source {
|
|
TalSource::Url(url) => {
|
|
if strict_name {
|
|
discover_root_ca_instance_from_tal_url_with_strict_name(http_fetcher, url)
|
|
} else {
|
|
discover_root_ca_instance_from_tal_url(http_fetcher, url)
|
|
}
|
|
}
|
|
TalSource::DerBytes {
|
|
tal_bytes, ta_der, ..
|
|
} => {
|
|
if strict_name {
|
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name(
|
|
tal_bytes, ta_der, None,
|
|
)
|
|
} else {
|
|
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, None)
|
|
}
|
|
}
|
|
TalSource::FilePath(path) => {
|
|
let tal_bytes = std::fs::read(path).map_err(|e| {
|
|
FromTalError::TalFetch(format!("read TAL file failed: {}: {e}", path.display()))
|
|
})?;
|
|
let tal = crate::data_model::tal::Tal::decode_bytes(&tal_bytes)
|
|
.map_err(FromTalError::from)?;
|
|
if strict_name {
|
|
discover_root_ca_instance_from_tal_with_fetchers_strict_name(
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
tal,
|
|
None,
|
|
)
|
|
} else {
|
|
discover_root_ca_instance_from_tal_with_fetchers(
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
tal,
|
|
None,
|
|
)
|
|
}
|
|
}
|
|
TalSource::FilePathWithTa { tal_path, ta_path } => {
|
|
let tal_bytes = std::fs::read(tal_path).map_err(|e| {
|
|
FromTalError::TalFetch(format!("read TAL file failed: {}: {e}", tal_path.display()))
|
|
})?;
|
|
let ta_der = std::fs::read(ta_path).map_err(|e| {
|
|
FromTalError::TaFetch(format!("read TA file failed: {}: {e}", ta_path.display()))
|
|
})?;
|
|
let resolved_ta_uri = canonical_tal_rsync_uri_from_bytes(&tal_bytes)?;
|
|
if strict_name {
|
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name(
|
|
&tal_bytes,
|
|
&ta_der,
|
|
Some(&resolved_ta_uri),
|
|
)
|
|
} else {
|
|
discover_root_ca_instance_from_tal_and_ta_der(
|
|
&tal_bytes,
|
|
&ta_der,
|
|
Some(&resolved_ta_uri),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn discover_root_ca_instance_from_tal_url_with_policy(
|
|
policy: &crate::policy::Policy,
|
|
http_fetcher: &dyn Fetcher,
|
|
tal_url: &str,
|
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
|
if policy.strict.name {
|
|
discover_root_ca_instance_from_tal_url_with_strict_name(http_fetcher, tal_url)
|
|
} else {
|
|
discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)
|
|
}
|
|
}
|
|
|
|
fn discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&Url>,
|
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
|
if policy.strict.name {
|
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name(
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)
|
|
} else {
|
|
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)
|
|
}
|
|
}
|
|
|
|
fn discover_multiple_roots_from_tal_inputs(
|
|
tal_inputs: &[TalInputSpec],
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
strict_name: bool,
|
|
) -> Result<Vec<TalRootDiscovery>, RunTreeFromTalError> {
|
|
let mut roots = Vec::with_capacity(tal_inputs.len());
|
|
for tal_input in tal_inputs {
|
|
let discovery = match root_discovery_from_tal_input(
|
|
tal_input,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
strict_name,
|
|
) {
|
|
Ok(discovery) => discovery,
|
|
Err(error)
|
|
if should_isolate_multi_tal_strict_name_failure(
|
|
tal_inputs.len(),
|
|
strict_name,
|
|
&error,
|
|
) =>
|
|
{
|
|
eprintln!(
|
|
"warning: skipping TAL '{}' because strict name validation failed during trust anchor discovery: {error}",
|
|
tal_input.tal_id
|
|
);
|
|
continue;
|
|
}
|
|
Err(error) => return Err(error.into()),
|
|
};
|
|
let root_handle = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
tal_input.tal_id.clone(),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
roots.push(TalRootDiscovery {
|
|
tal_input: tal_input.clone(),
|
|
discovery,
|
|
root_handle,
|
|
});
|
|
}
|
|
if roots.is_empty() {
|
|
return Err(RunTreeFromTalError::Replay(
|
|
"multi-TAL root discovery returned no usable roots after strict name filtering"
|
|
.to_string(),
|
|
));
|
|
}
|
|
Ok(roots)
|
|
}
|
|
|
|
fn should_isolate_multi_tal_strict_name_failure(
|
|
tal_input_count: usize,
|
|
strict_name: bool,
|
|
error: &FromTalError,
|
|
) -> bool {
|
|
strict_name
|
|
&& tal_input_count > 1
|
|
&& error.to_string().contains("Name strict validation failed")
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum RunTreeFromTalError {
|
|
#[error("{0}")]
|
|
FromTal(#[from] FromTalError),
|
|
|
|
#[error("payload replay setup failed: {0}")]
|
|
Replay(String),
|
|
|
|
#[error("{0}")]
|
|
Tree(#[from] TreeRunError),
|
|
}
|
|
|
|
pub fn root_handle_from_trust_anchor(
|
|
trust_anchor: &TrustAnchor,
|
|
tal_id: String,
|
|
ca_certificate_rsync_uri: Option<String>,
|
|
ca_instance: &crate::validation::ca_instance::CaInstanceUris,
|
|
) -> CaInstanceHandle {
|
|
let ta_rc = trust_anchor.ta_certificate.rc_ca.clone();
|
|
CaInstanceHandle {
|
|
depth: 0,
|
|
tal_id,
|
|
parent_manifest_rsync_uri: None,
|
|
ca_certificate_der: trust_anchor.ta_certificate.raw_der.clone(),
|
|
ca_certificate_rsync_uri,
|
|
effective_ip_resources: ta_rc.tbs.extensions.ip_resources.clone(),
|
|
effective_as_resources: ta_rc.tbs.extensions.as_resources.clone(),
|
|
rsync_base_uri: ca_instance.rsync_base_uri.clone(),
|
|
manifest_rsync_uri: ca_instance.manifest_rsync_uri.clone(),
|
|
publication_point_rsync_uri: ca_instance.publication_point_rsync_uri.clone(),
|
|
rrdp_notification_uri: ca_instance.rrdp_notification_uri.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn run_tree_from_tal_url_serial(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_url: &str,
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
|
|
let discovery =
|
|
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
|
|
|
|
let runner = make_live_runner(
|
|
store,
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
config.persist_vcir,
|
|
);
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let tree = run_tree_serial(root, &runner, config)?;
|
|
|
|
Ok(RunTreeFromTalOutput { discovery, tree })
|
|
}
|
|
|
|
pub fn run_tree_from_tal_url_serial_audit(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_url: &str,
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
let discovery =
|
|
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
|
|
|
|
let download_log = DownloadLogHandle::new();
|
|
let runner = make_live_runner(
|
|
store,
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
None,
|
|
Some(download_log.clone()),
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
config.persist_vcir,
|
|
);
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = run_tree_serial_audit(root, &runner, config)?;
|
|
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
pub fn run_tree_from_tal_url_serial_audit_with_timing(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_url: &str,
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
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_url_with_policy(policy, http_fetcher, tal_url)?;
|
|
drop(_tal);
|
|
|
|
let download_log = DownloadLogHandle::new();
|
|
let runner = make_live_runner(
|
|
store,
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
Some(timing.clone()),
|
|
Some(download_log.clone()),
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
config.persist_vcir,
|
|
);
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let _tree = timing.span_phase("tree_run_total");
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = run_tree_serial_audit(root, &runner, config)?;
|
|
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
fn run_single_root_parallel_audit_inner<H, R>(
|
|
store: Arc<crate::storage::RocksStore>,
|
|
policy: &crate::policy::Policy,
|
|
discovery: DiscoveredRootCaInstance,
|
|
tal_inputs: Vec<TalInputSpec>,
|
|
http_fetcher: &H,
|
|
rsync_fetcher: &R,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
parallel_config: ParallelPhase1Config,
|
|
phase2_config: Option<ParallelPhase2Config>,
|
|
collect_current_repo_objects: bool,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError>
|
|
where
|
|
H: Fetcher + Clone + 'static,
|
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
|
{
|
|
let phase2_enabled = phase2_config.is_some();
|
|
let download_log = DownloadLogHandle::new();
|
|
let (runtime, current_repo_index) = build_phase1_repo_sync_runtime(
|
|
Arc::clone(&store),
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
parallel_config,
|
|
None,
|
|
Some(download_log.clone()),
|
|
tal_inputs,
|
|
)?;
|
|
let current_repo_index_for_output = current_repo_index.clone();
|
|
let runner = make_live_runner(
|
|
store.as_ref(),
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
None,
|
|
Some(download_log.clone()),
|
|
Some(current_repo_index),
|
|
Some(runtime),
|
|
phase2_config,
|
|
(phase2_enabled && config.build_ccr_accumulator)
|
|
.then(|| CcrAccumulator::new(vec![discovery.trust_anchor.clone()])),
|
|
config.persist_vcir,
|
|
);
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = if phase2_enabled {
|
|
run_tree_parallel_phase2_audit(root, &runner, config)?
|
|
} else {
|
|
run_tree_serial_audit(root, &runner, config)?
|
|
};
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: snapshot_current_repo_objects(
|
|
Some(¤t_repo_index_for_output),
|
|
collect_current_repo_objects,
|
|
),
|
|
ccr_accumulator: runner.ccr_accumulator_snapshot(),
|
|
})
|
|
}
|
|
|
|
fn run_multi_root_parallel_audit_inner<H, R>(
|
|
store: Arc<crate::storage::RocksStore>,
|
|
policy: &crate::policy::Policy,
|
|
tal_inputs: Vec<TalInputSpec>,
|
|
http_fetcher: &H,
|
|
rsync_fetcher: &R,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
parallel_config: ParallelPhase1Config,
|
|
phase2_config: Option<ParallelPhase2Config>,
|
|
collect_current_repo_objects: bool,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError>
|
|
where
|
|
H: Fetcher + Clone + 'static,
|
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
|
{
|
|
let phase2_enabled = phase2_config.is_some();
|
|
if tal_inputs.is_empty() {
|
|
return Err(RunTreeFromTalError::Replay(
|
|
"multi-TAL run requires at least one TAL input".to_string(),
|
|
));
|
|
}
|
|
let roots = discover_multiple_roots_from_tal_inputs(
|
|
&tal_inputs,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
policy.strict.name,
|
|
)?;
|
|
let primary = roots.first().cloned().ok_or_else(|| {
|
|
RunTreeFromTalError::Replay("multi-TAL root discovery returned no roots".to_string())
|
|
})?;
|
|
let discoveries = roots
|
|
.iter()
|
|
.map(|item| item.discovery.clone())
|
|
.collect::<Vec<_>>();
|
|
let successful_tal_inputs = roots
|
|
.iter()
|
|
.map(|item| item.tal_input.clone())
|
|
.collect::<Vec<_>>();
|
|
let root_handles = roots
|
|
.iter()
|
|
.map(|item| item.root_handle.clone())
|
|
.collect::<Vec<_>>();
|
|
|
|
let download_log = DownloadLogHandle::new();
|
|
let (runtime, current_repo_index) = build_phase1_repo_sync_runtime(
|
|
Arc::clone(&store),
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
parallel_config,
|
|
None,
|
|
Some(download_log.clone()),
|
|
successful_tal_inputs.clone(),
|
|
)?;
|
|
let current_repo_index_for_output = current_repo_index.clone();
|
|
let runner = make_live_runner(
|
|
store.as_ref(),
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
None,
|
|
Some(download_log.clone()),
|
|
Some(current_repo_index),
|
|
Some(runtime),
|
|
phase2_config,
|
|
(phase2_enabled && config.build_ccr_accumulator).then(|| {
|
|
CcrAccumulator::new(
|
|
discoveries
|
|
.iter()
|
|
.map(|item| item.trust_anchor.clone())
|
|
.collect::<Vec<_>>(),
|
|
)
|
|
}),
|
|
config.persist_vcir,
|
|
);
|
|
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = if phase2_enabled {
|
|
run_tree_parallel_phase2_audit_multi_root(root_handles, &runner, config)?
|
|
} else {
|
|
run_tree_serial_audit_multi_root(root_handles, &runner, config)?
|
|
};
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: primary.discovery.clone(),
|
|
discoveries,
|
|
successful_tal_inputs,
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: snapshot_current_repo_objects(
|
|
Some(¤t_repo_index_for_output),
|
|
collect_current_repo_objects,
|
|
),
|
|
ccr_accumulator: runner.ccr_accumulator_snapshot(),
|
|
})
|
|
}
|
|
|
|
pub fn run_tree_from_tal_url_parallel_phase1_audit<H, R>(
|
|
store: Arc<crate::storage::RocksStore>,
|
|
policy: &crate::policy::Policy,
|
|
tal_url: &str,
|
|
http_fetcher: &H,
|
|
rsync_fetcher: &R,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
parallel_config: ParallelPhase1Config,
|
|
collect_current_repo_objects: bool,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError>
|
|
where
|
|
H: Fetcher + Clone + 'static,
|
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
|
{
|
|
let discovery =
|
|
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
|
|
run_single_root_parallel_audit_inner(
|
|
store,
|
|
policy,
|
|
discovery,
|
|
vec![TalInputSpec::from_url(tal_url.to_string())],
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
config,
|
|
parallel_config,
|
|
None,
|
|
collect_current_repo_objects,
|
|
)
|
|
}
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_parallel_phase1_audit<H, R>(
|
|
store: Arc<crate::storage::RocksStore>,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&url::Url>,
|
|
http_fetcher: &H,
|
|
rsync_fetcher: &R,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
parallel_config: ParallelPhase1Config,
|
|
collect_current_repo_objects: bool,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError>
|
|
where
|
|
H: Fetcher + Clone + 'static,
|
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
|
{
|
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
|
policy,
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)?;
|
|
let derived_tal_id = derive_tal_id(&discovery);
|
|
let tal_inputs = vec![TalInputSpec {
|
|
tal_id: derived_tal_id.clone(),
|
|
rir_id: derived_tal_id,
|
|
source: TalSource::DerBytes {
|
|
tal_url: discovery
|
|
.tal_url
|
|
.clone()
|
|
.unwrap_or_else(|| "embedded-tal".to_string()),
|
|
tal_bytes: tal_bytes.to_vec(),
|
|
ta_der: ta_der.to_vec(),
|
|
},
|
|
}];
|
|
run_single_root_parallel_audit_inner(
|
|
store,
|
|
policy,
|
|
discovery,
|
|
tal_inputs,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
config,
|
|
parallel_config,
|
|
None,
|
|
collect_current_repo_objects,
|
|
)
|
|
}
|
|
|
|
pub fn run_tree_from_multiple_tals_parallel_phase1_audit<H, R>(
|
|
store: Arc<crate::storage::RocksStore>,
|
|
policy: &crate::policy::Policy,
|
|
tal_inputs: Vec<TalInputSpec>,
|
|
http_fetcher: &H,
|
|
rsync_fetcher: &R,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
parallel_config: ParallelPhase1Config,
|
|
collect_current_repo_objects: bool,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError>
|
|
where
|
|
H: Fetcher + Clone + 'static,
|
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
|
{
|
|
run_multi_root_parallel_audit_inner(
|
|
store,
|
|
policy,
|
|
tal_inputs,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
config,
|
|
parallel_config,
|
|
None,
|
|
collect_current_repo_objects,
|
|
)
|
|
}
|
|
|
|
pub fn run_tree_from_tal_url_parallel_phase2_audit<H, R>(
|
|
store: Arc<crate::storage::RocksStore>,
|
|
policy: &crate::policy::Policy,
|
|
tal_url: &str,
|
|
http_fetcher: &H,
|
|
rsync_fetcher: &R,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
parallel_config: ParallelPhase1Config,
|
|
phase2_config: ParallelPhase2Config,
|
|
collect_current_repo_objects: bool,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError>
|
|
where
|
|
H: Fetcher + Clone + 'static,
|
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
|
{
|
|
let discovery =
|
|
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
|
|
run_single_root_parallel_audit_inner(
|
|
store,
|
|
policy,
|
|
discovery,
|
|
vec![TalInputSpec::from_url(tal_url.to_string())],
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
config,
|
|
parallel_config,
|
|
Some(phase2_config),
|
|
collect_current_repo_objects,
|
|
)
|
|
}
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_parallel_phase2_audit<H, R>(
|
|
store: Arc<crate::storage::RocksStore>,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&url::Url>,
|
|
http_fetcher: &H,
|
|
rsync_fetcher: &R,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
parallel_config: ParallelPhase1Config,
|
|
phase2_config: ParallelPhase2Config,
|
|
collect_current_repo_objects: bool,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError>
|
|
where
|
|
H: Fetcher + Clone + 'static,
|
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
|
{
|
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
|
policy,
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)?;
|
|
let derived_tal_id = derive_tal_id(&discovery);
|
|
let tal_inputs = vec![TalInputSpec {
|
|
tal_id: derived_tal_id.clone(),
|
|
rir_id: derived_tal_id,
|
|
source: TalSource::DerBytes {
|
|
tal_url: discovery
|
|
.tal_url
|
|
.clone()
|
|
.unwrap_or_else(|| "embedded-tal".to_string()),
|
|
tal_bytes: tal_bytes.to_vec(),
|
|
ta_der: ta_der.to_vec(),
|
|
},
|
|
}];
|
|
run_single_root_parallel_audit_inner(
|
|
store,
|
|
policy,
|
|
discovery,
|
|
tal_inputs,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
config,
|
|
parallel_config,
|
|
Some(phase2_config),
|
|
collect_current_repo_objects,
|
|
)
|
|
}
|
|
|
|
pub fn run_tree_from_multiple_tals_parallel_phase2_audit<H, R>(
|
|
store: Arc<crate::storage::RocksStore>,
|
|
policy: &crate::policy::Policy,
|
|
tal_inputs: Vec<TalInputSpec>,
|
|
http_fetcher: &H,
|
|
rsync_fetcher: &R,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
parallel_config: ParallelPhase1Config,
|
|
phase2_config: ParallelPhase2Config,
|
|
collect_current_repo_objects: bool,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError>
|
|
where
|
|
H: Fetcher + Clone + 'static,
|
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
|
{
|
|
run_multi_root_parallel_audit_inner(
|
|
store,
|
|
policy,
|
|
tal_inputs,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
config,
|
|
parallel_config,
|
|
Some(phase2_config),
|
|
collect_current_repo_objects,
|
|
)
|
|
}
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_serial(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&Url>,
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
|
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
|
policy,
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)?;
|
|
|
|
let runner = Rpkiv1PublicationPointRunner {
|
|
store,
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
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,
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
};
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let tree = run_tree_serial(root, &runner, config)?;
|
|
|
|
Ok(RunTreeFromTalOutput { discovery, tree })
|
|
}
|
|
|
|
pub fn run_tree_from_tal_bytes_serial_audit(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
tal_uri: Option<String>,
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
let tal = crate::data_model::tal::Tal::decode_bytes(tal_bytes).map_err(FromTalError::from)?;
|
|
let discovery = discover_root_ca_instance_from_tal_with_fetchers(
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
tal,
|
|
tal_uri,
|
|
)?;
|
|
|
|
let download_log = DownloadLogHandle::new();
|
|
let runner = Rpkiv1PublicationPointRunner {
|
|
store,
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
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,
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
};
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = run_tree_serial_audit(root, &runner, config)?;
|
|
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
pub fn run_tree_from_tal_bytes_serial_audit_with_timing(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
tal_uri: Option<String>,
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
timing: &TimingHandle,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
let _tal = timing.span_phase("tal_bootstrap");
|
|
let tal = crate::data_model::tal::Tal::decode_bytes(tal_bytes).map_err(FromTalError::from)?;
|
|
let discovery = discover_root_ca_instance_from_tal_with_fetchers(
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
tal,
|
|
tal_uri,
|
|
)?;
|
|
drop(_tal);
|
|
|
|
let download_log = DownloadLogHandle::new();
|
|
let runner = Rpkiv1PublicationPointRunner {
|
|
store,
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
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,
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
};
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let _tree = timing.span_phase("tree_run");
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = run_tree_serial_audit(root, &runner, config)?;
|
|
drop(_tree);
|
|
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_serial_audit(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&Url>,
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
|
policy,
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)?;
|
|
|
|
let download_log = DownloadLogHandle::new();
|
|
let runner = Rpkiv1PublicationPointRunner {
|
|
store,
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
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,
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
};
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = run_tree_serial_audit(root, &runner, config)?;
|
|
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_serial_audit_with_timing(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&Url>,
|
|
http_fetcher: &dyn Fetcher,
|
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
|
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_with_policy(
|
|
policy,
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)?;
|
|
drop(_tal);
|
|
|
|
let download_log = DownloadLogHandle::new();
|
|
let runner = Rpkiv1PublicationPointRunner {
|
|
store,
|
|
policy,
|
|
http_fetcher,
|
|
rsync_fetcher,
|
|
validation_time,
|
|
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,
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
};
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let _tree = timing.span_phase("tree_run_total");
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = run_tree_serial_audit(root, &runner, config)?;
|
|
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_payload_replay_serial(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&Url>,
|
|
payload_archive_root: &std::path::Path,
|
|
payload_locks_path: &std::path::Path,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
|
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
|
policy,
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)?;
|
|
let replay_index = Arc::new(
|
|
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())
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?;
|
|
let rsync_fetcher = PayloadReplayRsyncFetcher::new(replay_index.clone());
|
|
|
|
let runner = Rpkiv1PublicationPointRunner {
|
|
store,
|
|
policy,
|
|
http_fetcher: &http_fetcher,
|
|
rsync_fetcher: &rsync_fetcher,
|
|
validation_time,
|
|
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,
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
};
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let tree = run_tree_serial(root, &runner, config)?;
|
|
|
|
Ok(RunTreeFromTalOutput { discovery, tree })
|
|
}
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&Url>,
|
|
payload_archive_root: &std::path::Path,
|
|
payload_locks_path: &std::path::Path,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
|
policy,
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)?;
|
|
let replay_index = Arc::new(
|
|
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())
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?;
|
|
let rsync_fetcher = PayloadReplayRsyncFetcher::new(replay_index.clone());
|
|
let download_log = DownloadLogHandle::new();
|
|
|
|
let runner = Rpkiv1PublicationPointRunner {
|
|
store,
|
|
policy,
|
|
http_fetcher: &http_fetcher,
|
|
rsync_fetcher: &rsync_fetcher,
|
|
validation_time,
|
|
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,
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
};
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = run_tree_serial_audit(root, &runner, config)?;
|
|
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&Url>,
|
|
payload_archive_root: &std::path::Path,
|
|
payload_locks_path: &std::path::Path,
|
|
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_with_policy(
|
|
policy,
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)?;
|
|
drop(_tal);
|
|
let replay_index = Arc::new(
|
|
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())
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?;
|
|
let rsync_fetcher = PayloadReplayRsyncFetcher::new(replay_index.clone());
|
|
let download_log = DownloadLogHandle::new();
|
|
|
|
let runner = Rpkiv1PublicationPointRunner {
|
|
store,
|
|
policy,
|
|
http_fetcher: &http_fetcher,
|
|
rsync_fetcher: &rsync_fetcher,
|
|
validation_time,
|
|
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,
|
|
rsync_repo_cache: Mutex::new(HashMap::new()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
};
|
|
|
|
let root = root_handle_from_trust_anchor(
|
|
&discovery.trust_anchor,
|
|
derive_tal_id(&discovery),
|
|
None,
|
|
&discovery.ca_instance,
|
|
);
|
|
let _tree = timing.span_phase("tree_run_total");
|
|
let TreeRunAuditOutput {
|
|
tree,
|
|
publication_points,
|
|
} = run_tree_serial_audit(root, &runner, config)?;
|
|
|
|
let downloads = download_log.snapshot_events();
|
|
let download_stats = DownloadLogHandle::stats_from_events(&downloads);
|
|
Ok(RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
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()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
}
|
|
}
|
|
|
|
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()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
}
|
|
}
|
|
|
|
fn build_payload_delta_replay_current_store_runner<'a>(
|
|
store: &'a crate::storage::RocksStore,
|
|
policy: &'a crate::policy::Policy,
|
|
delta_index: Arc<ReplayDeltaArchiveIndex>,
|
|
http_fetcher: &'a PayloadDeltaReplayHttpFetcher,
|
|
rsync_fetcher: &'a PayloadDeltaReplayCurrentStoreRsyncFetcher<'a>,
|
|
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()),
|
|
current_repo_index: None,
|
|
repo_sync_runtime: None,
|
|
parallel_phase2_config: None,
|
|
parallel_roa_worker_pool: None,
|
|
ccr_accumulator: None,
|
|
persist_vcir: true,
|
|
}
|
|
}
|
|
|
|
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: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
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_with_policy(
|
|
policy,
|
|
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_with_policy(
|
|
policy,
|
|
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()),
|
|
)
|
|
}
|
|
|
|
fn run_payload_delta_replay_step_audit_inner(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
discovery: DiscoveredRootCaInstance,
|
|
delta_payload_archive_root: &std::path::Path,
|
|
previous_locks_path: &std::path::Path,
|
|
delta_locks_path: &std::path::Path,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
timing: Option<TimingHandle>,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
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(previous_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 delta_http_fetcher = PayloadDeltaReplayHttpFetcher::from_index(delta_index.clone())
|
|
.map_err(|e| RunTreeFromTalError::Replay(e.to_string()))?;
|
|
let delta_rsync_fetcher =
|
|
PayloadDeltaReplayCurrentStoreRsyncFetcher::new(store, 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_step_total");
|
|
let delta_runner = build_payload_delta_replay_current_store_runner(
|
|
store,
|
|
policy,
|
|
delta_index,
|
|
&delta_http_fetcher,
|
|
&delta_rsync_fetcher,
|
|
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_current_store_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: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points,
|
|
downloads,
|
|
download_stats,
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
})
|
|
}
|
|
|
|
pub fn run_tree_from_tal_and_ta_der_payload_delta_replay_step_serial_audit(
|
|
store: &crate::storage::RocksStore,
|
|
policy: &crate::policy::Policy,
|
|
tal_bytes: &[u8],
|
|
ta_der: &[u8],
|
|
resolved_ta_uri: Option<&Url>,
|
|
delta_payload_archive_root: &std::path::Path,
|
|
previous_locks_path: &std::path::Path,
|
|
delta_locks_path: &std::path::Path,
|
|
validation_time: time::OffsetDateTime,
|
|
config: &TreeRunConfig,
|
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
|
policy,
|
|
tal_bytes,
|
|
ta_der,
|
|
resolved_ta_uri,
|
|
)?;
|
|
run_payload_delta_replay_step_audit_inner(
|
|
store,
|
|
policy,
|
|
discovery,
|
|
delta_payload_archive_root,
|
|
previous_locks_path,
|
|
delta_locks_path,
|
|
validation_time,
|
|
config,
|
|
None,
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod multi_tal_tests {
|
|
use super::*;
|
|
use crate::current_repo_index::CurrentRepoIndex;
|
|
use crate::storage::{RepositoryViewEntry, RepositoryViewState};
|
|
|
|
struct RejectingHttpFetcher;
|
|
|
|
impl Fetcher for RejectingHttpFetcher {
|
|
fn fetch(&self, uri: &str) -> Result<Vec<u8>, String> {
|
|
Err(format!("unexpected http fetch: {uri}"))
|
|
}
|
|
}
|
|
|
|
struct RejectingRsyncFetcher;
|
|
|
|
impl crate::fetch::rsync::RsyncFetcher for RejectingRsyncFetcher {
|
|
fn fetch_objects(
|
|
&self,
|
|
_rsync_base_uri: &str,
|
|
) -> crate::fetch::rsync::RsyncFetchResult<Vec<(String, Vec<u8>)>> {
|
|
Err(crate::fetch::rsync::RsyncFetchError::Fetch(
|
|
"unexpected rsync fetch".to_string(),
|
|
))
|
|
}
|
|
|
|
fn dedup_key(&self, base_uri: &str) -> String {
|
|
base_uri.to_string()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn snapshot_current_repo_objects_is_on_demand() {
|
|
let handle = CurrentRepoIndex::shared();
|
|
handle
|
|
.lock()
|
|
.expect("lock index")
|
|
.apply_repository_view_entries(&[RepositoryViewEntry {
|
|
rsync_uri: "rsync://example.test/repo/a.roa".to_string(),
|
|
current_hash: Some("11".repeat(32)),
|
|
repository_source: Some("rsync://example.test/repo/".to_string()),
|
|
object_type: Some("roa".to_string()),
|
|
state: RepositoryViewState::Present,
|
|
}])
|
|
.expect("apply present entry");
|
|
|
|
assert!(
|
|
snapshot_current_repo_objects(Some(&handle), false).is_empty(),
|
|
"collection should be skipped when disabled"
|
|
);
|
|
|
|
let collected = snapshot_current_repo_objects(Some(&handle), true);
|
|
assert_eq!(collected.len(), 1);
|
|
assert_eq!(collected[0].rsync_uri, "rsync://example.test/repo/a.roa");
|
|
}
|
|
|
|
#[test]
|
|
fn discover_multiple_roots_from_tal_inputs_builds_multiple_root_handles() {
|
|
let apnic_tal =
|
|
std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read apnic tal");
|
|
let apnic_ta = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta");
|
|
let arin_tal = std::fs::read("tests/fixtures/tal/arin.tal").expect("read arin tal");
|
|
let arin_ta = std::fs::read("tests/fixtures/ta/arin-ta.cer").expect("read arin ta");
|
|
|
|
let tal_inputs = vec![
|
|
TalInputSpec::from_ta_der("https://example.test/apnic.tal", apnic_tal, apnic_ta),
|
|
TalInputSpec::from_ta_der("https://example.test/arin.tal", arin_tal, arin_ta),
|
|
];
|
|
|
|
let roots = discover_multiple_roots_from_tal_inputs(
|
|
&tal_inputs,
|
|
&RejectingHttpFetcher,
|
|
&RejectingRsyncFetcher,
|
|
false,
|
|
)
|
|
.expect("discover roots");
|
|
|
|
assert_eq!(roots.len(), 2);
|
|
assert_eq!(roots[0].tal_input.tal_id, "apnic");
|
|
assert_eq!(roots[1].tal_input.tal_id, "arin");
|
|
assert_eq!(roots[0].root_handle.tal_id, "apnic");
|
|
assert_eq!(roots[1].root_handle.tal_id, "arin");
|
|
assert_ne!(
|
|
roots[0].root_handle.manifest_rsync_uri,
|
|
roots[1].root_handle.manifest_rsync_uri
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn discover_multiple_roots_isolates_strict_name_failure() {
|
|
let apnic_tal =
|
|
std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read apnic tal");
|
|
let apnic_ta = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta");
|
|
let arin_tal = std::fs::read("tests/fixtures/tal/arin.tal").expect("read arin tal");
|
|
let arin_ta = std::fs::read("tests/fixtures/ta/arin-ta.cer").expect("read arin ta");
|
|
|
|
let tal_inputs = vec![
|
|
TalInputSpec::from_ta_der("https://example.test/apnic.tal", apnic_tal, apnic_ta),
|
|
TalInputSpec::from_ta_der("https://example.test/arin.tal", arin_tal, arin_ta),
|
|
];
|
|
|
|
let roots = discover_multiple_roots_from_tal_inputs(
|
|
&tal_inputs,
|
|
&RejectingHttpFetcher,
|
|
&RejectingRsyncFetcher,
|
|
true,
|
|
)
|
|
.expect("strict discovery should keep usable roots");
|
|
|
|
assert_eq!(roots.len(), 1);
|
|
assert_eq!(roots[0].tal_input.tal_id, "arin");
|
|
assert_eq!(roots[0].root_handle.tal_id, "arin");
|
|
}
|
|
|
|
#[test]
|
|
fn discover_single_root_keeps_strict_name_failure_fatal() {
|
|
let apnic_tal =
|
|
std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read apnic tal");
|
|
let apnic_ta = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta");
|
|
let tal_inputs = vec![TalInputSpec::from_ta_der(
|
|
"https://example.test/apnic.tal",
|
|
apnic_tal,
|
|
apnic_ta,
|
|
)];
|
|
|
|
let error = discover_multiple_roots_from_tal_inputs(
|
|
&tal_inputs,
|
|
&RejectingHttpFetcher,
|
|
&RejectingRsyncFetcher,
|
|
true,
|
|
)
|
|
.expect_err("single-TAL strict failure should remain fatal");
|
|
|
|
assert!(error.to_string().contains("Name strict validation failed"));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod replay_api_tests {
|
|
use super::*;
|
|
use crate::analysis::timing::{TimingHandle, TimingMeta};
|
|
use time::format_description::well_known::Rfc3339;
|
|
|
|
fn apnic_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("target/live/payload_replay/payload-archive");
|
|
let locks_path = std::path::PathBuf::from("target/live/payload_replay/locks.json");
|
|
let validation_time = time::OffsetDateTime::parse("2026-03-13T02:30:00Z", &Rfc3339)
|
|
.expect("parse validation time");
|
|
(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");
|
|
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_replay_serial_audit(
|
|
&store,
|
|
&crate::policy::Policy::default(),
|
|
&tal_bytes,
|
|
&ta_der,
|
|
None,
|
|
std::path::Path::new("tests/fixtures/missing-payload-archive"),
|
|
std::path::Path::new("tests/fixtures/missing-locks.json"),
|
|
time::OffsetDateTime::now_utc(),
|
|
&TreeRunConfig {
|
|
max_depth: Some(0),
|
|
max_instances: Some(1),
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
)
|
|
.unwrap_err();
|
|
assert!(matches!(err, RunTreeFromTalError::Replay(_)), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn payload_replay_api_root_only_apnic_archive_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, archive_root, locks_path, validation_time) = apnic_replay_inputs();
|
|
if !archive_root.is_dir() || !locks_path.is_file() {
|
|
eprintln!(
|
|
"skipping payload replay api test; missing fixtures: archive={} locks={}",
|
|
archive_root.display(),
|
|
locks_path.display()
|
|
);
|
|
return;
|
|
}
|
|
|
|
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),
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
)
|
|
.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);
|
|
assert_eq!(out.discovery.trust_anchor.resolved_ta_uri, None);
|
|
assert!(!out.downloads.is_empty());
|
|
assert!(
|
|
out.downloads.iter().all(|d| d.success),
|
|
"expected successful replay downloads"
|
|
);
|
|
}
|
|
|
|
#[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();
|
|
if !archive_root.is_dir() || !locks_path.is_file() {
|
|
eprintln!(
|
|
"skipping multi-rir payload replay api test; missing fixtures: archive={} locks={}",
|
|
archive_root.display(),
|
|
locks_path.display()
|
|
);
|
|
return;
|
|
}
|
|
|
|
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),
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
)
|
|
.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");
|
|
let db_path = temp.path().join("db");
|
|
let store = crate::storage::RocksStore::open(&db_path).expect("open db");
|
|
let (tal_bytes, ta_der, archive_root, locks_path, validation_time) = apnic_replay_inputs();
|
|
if !archive_root.is_dir() || !locks_path.is_file() {
|
|
eprintln!(
|
|
"skipping payload replay api timing test; missing fixtures: archive={} locks={}",
|
|
archive_root.display(),
|
|
locks_path.display()
|
|
);
|
|
return;
|
|
}
|
|
|
|
let timing = TimingHandle::new(TimingMeta {
|
|
recorded_at_utc_rfc3339: "2026-03-13T03:00:00Z".to_string(),
|
|
validation_time_utc_rfc3339: "2026-03-13T02:30: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_replay_serial_audit_with_timing(
|
|
&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),
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
&timing,
|
|
)
|
|
.expect("run replay root-only audit with timing");
|
|
|
|
assert_eq!(out.tree.instances_processed, 1);
|
|
let timing_json = temp.path().join("timing_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");
|
|
let counts = json.get("counts").expect("counts");
|
|
assert!(
|
|
counts
|
|
.get("repo_sync_rrdp_ok_total")
|
|
.and_then(|v| v.as_u64())
|
|
.unwrap_or(0)
|
|
>= 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),
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
)
|
|
.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),
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
)
|
|
.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();
|
|
if !base_archive.is_dir()
|
|
|| !base_locks.is_file()
|
|
|| !delta_archive.is_dir()
|
|
|| !delta_locks.is_file()
|
|
{
|
|
eprintln!(
|
|
"skipping payload delta replay api test; missing fixtures: base_archive={} base_locks={} delta_archive={} delta_locks={}",
|
|
base_archive.display(),
|
|
base_locks.display(),
|
|
delta_archive.display(),
|
|
delta_locks.display()
|
|
);
|
|
return;
|
|
}
|
|
|
|
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),
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
)
|
|
.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();
|
|
if !base_archive.is_dir()
|
|
|| !base_locks.is_file()
|
|
|| !delta_archive.is_dir()
|
|
|| !delta_locks.is_file()
|
|
{
|
|
eprintln!(
|
|
"skipping payload delta replay timing test; missing fixtures: base_archive={} base_locks={} delta_archive={} delta_locks={}",
|
|
base_archive.display(),
|
|
base_locks.display(),
|
|
delta_archive.display(),
|
|
delta_locks.display()
|
|
);
|
|
return;
|
|
}
|
|
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),
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
&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)
|
|
);
|
|
}
|
|
}
|