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, discover_root_ca_instance_from_tal_and_ta_der, discover_root_ca_instance_from_tal_url, discover_root_ca_instance_from_tal_with_fetchers, }; 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 { 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, pub tree: TreeRunOutput, pub publication_points: Vec, pub downloads: Vec, pub download_stats: crate::audit::AuditDownloadStats, pub current_repo_objects: Vec, pub ccr_accumulator: Option, } #[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 { 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, download_log: Option, current_repo_index: Option, repo_sync_runtime: Option>, parallel_phase2_config: Option, ccr_accumulator: Option, 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( store: Arc, policy: &crate::policy::Policy, http_fetcher: &H, rsync_fetcher: &R, parallel_config: ParallelPhase1Config, timing: Option, download_log: Option, tal_inputs: Vec, ) -> Result<(Arc, 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 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, ) -> Result { match &tal_input.source { TalSource::Url(url) => discover_root_ca_instance_from_tal_url(http_fetcher, url), TalSource::DerBytes { tal_bytes, ta_der, .. } => 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)?; 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())) })?; discover_root_ca_instance_from_tal_and_ta_der(&tal_bytes, &ta_der, None) } } } fn discover_multiple_roots_from_tal_inputs( tal_inputs: &[TalInputSpec], http_fetcher: &dyn Fetcher, rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher, ) -> Result, RunTreeFromTalError> { let mut roots = Vec::with_capacity(tal_inputs.len()); for tal_input in tal_inputs { let discovery = root_discovery_from_tal_input(tal_input, http_fetcher, rsync_fetcher)?; 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, }); } Ok(roots) } #[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, 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 { let discovery = discover_root_ca_instance_from_tal_url(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 { let discovery = discover_root_ca_instance_from_tal_url(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], 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 { let _tal = timing.span_phase("tal_bootstrap"); let discovery = discover_root_ca_instance_from_tal_url(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], tree, publication_points, downloads, download_stats, current_repo_objects: Vec::new(), ccr_accumulator: None, }) } fn run_single_root_parallel_audit_inner( store: Arc, policy: &crate::policy::Policy, discovery: DiscoveredRootCaInstance, tal_inputs: Vec, http_fetcher: &H, rsync_fetcher: &R, validation_time: time::OffsetDateTime, config: &TreeRunConfig, parallel_config: ParallelPhase1Config, phase2_config: Option, collect_current_repo_objects: bool, ) -> Result 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], 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( store: Arc, policy: &crate::policy::Policy, tal_inputs: Vec, http_fetcher: &H, rsync_fetcher: &R, validation_time: time::OffsetDateTime, config: &TreeRunConfig, parallel_config: ParallelPhase1Config, phase2_config: Option, collect_current_repo_objects: bool, ) -> Result 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)?; 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::>(); let root_handles = roots .into_iter() .map(|item| item.root_handle) .collect::>(); 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( discoveries .iter() .map(|item| item.trust_anchor.clone()) .collect::>(), ) }), 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, 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( store: Arc, 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 where H: Fetcher + Clone + 'static, R: crate::fetch::rsync::RsyncFetcher + Clone + 'static, { let discovery = discover_root_ca_instance_from_tal_url(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( store: Arc, 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 where H: Fetcher + Clone + 'static, R: crate::fetch::rsync::RsyncFetcher + Clone + 'static, { let discovery = discover_root_ca_instance_from_tal_and_ta_der(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( store: Arc, policy: &crate::policy::Policy, tal_inputs: Vec, http_fetcher: &H, rsync_fetcher: &R, validation_time: time::OffsetDateTime, config: &TreeRunConfig, parallel_config: ParallelPhase1Config, collect_current_repo_objects: bool, ) -> Result 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( store: Arc, 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 where H: Fetcher + Clone + 'static, R: crate::fetch::rsync::RsyncFetcher + Clone + 'static, { let discovery = discover_root_ca_instance_from_tal_url(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( store: Arc, 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 where H: Fetcher + Clone + 'static, R: crate::fetch::rsync::RsyncFetcher + Clone + 'static, { let discovery = discover_root_ca_instance_from_tal_and_ta_der(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( store: Arc, policy: &crate::policy::Policy, tal_inputs: Vec, http_fetcher: &H, rsync_fetcher: &R, validation_time: time::OffsetDateTime, config: &TreeRunConfig, parallel_config: ParallelPhase1Config, phase2_config: ParallelPhase2Config, collect_current_repo_objects: bool, ) -> Result 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 { let discovery = discover_root_ca_instance_from_tal_and_ta_der(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, http_fetcher: &dyn Fetcher, rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher, validation_time: time::OffsetDateTime, config: &TreeRunConfig, ) -> Result { 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], 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, http_fetcher: &dyn Fetcher, rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher, validation_time: time::OffsetDateTime, config: &TreeRunConfig, timing: &TimingHandle, ) -> Result { 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], 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 { let discovery = discover_root_ca_instance_from_tal_and_ta_der(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], 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 { 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); 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], 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 { 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_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 { 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_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], 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 { 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); 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], 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, http_fetcher: &'a PayloadReplayHttpFetcher, rsync_fetcher: &'a PayloadReplayRsyncFetcher, validation_time: time::OffsetDateTime, timing: Option, download_log: Option, ) -> 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, http_fetcher: &'a PayloadDeltaReplayHttpFetcher, rsync_fetcher: &'a PayloadDeltaReplayRsyncFetcher, validation_time: time::OffsetDateTime, timing: Option, download_log: Option, ) -> 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, http_fetcher: &'a PayloadDeltaReplayHttpFetcher, rsync_fetcher: &'a PayloadDeltaReplayCurrentStoreRsyncFetcher<'a>, validation_time: time::OffsetDateTime, timing: Option, download_log: Option, ) -> 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, ) -> Result { 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], 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 { 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 { 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()), ) } 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, ) -> Result { 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], 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 { let discovery = discover_root_ca_instance_from_tal_and_ta_der(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, 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)>> { 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, ) .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 ); } } #[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, Vec, 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, Vec, 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, Vec, 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) ); } }