use std::collections::{BTreeMap, BTreeSet}; use std::fs; use std::path::{Path, PathBuf}; use rpki::bundle::{ build_vap_compare_rows, build_vrp_compare_rows, sha256_hex, write_json, write_vap_csv, write_vrp_csv, }; use rpki::ccr::{build_ccr_from_run, decode_content_info, verify_content_info, write_ccr_file}; use rpki::policy::Policy; use rpki::replay::archive::canonical_rsync_module; use rpki::storage::RocksStore; use rpki::validation::run_tree_from_tal::{ run_tree_from_tal_and_ta_der_payload_delta_replay_step_serial_audit, run_tree_from_tal_and_ta_der_payload_replay_serial_audit, }; use rpki::validation::tree::TreeRunConfig; use serde::{Deserialize, Serialize}; use time::format_description::well_known::Rfc3339; fn usage() -> &'static str { "Usage: replay_bundle_refresh_sequence_outputs --rir-dir [--keep-db]" } #[derive(Default)] struct Args { rir_dir: Option, keep_db: bool, } fn parse_args() -> Result { let mut args = Args::default(); let argv: Vec = std::env::args().skip(1).collect(); let mut i = 0; while i < argv.len() { match argv[i].as_str() { "--rir-dir" => { i += 1; args.rir_dir = Some(PathBuf::from( argv.get(i).ok_or("--rir-dir requires a value")?, )); } "--keep-db" => { args.keep_db = true; } "--help" | "-h" => { return Err(usage().to_string()); } other => return Err(format!("unknown argument: {other}\n{}", usage())), } i += 1; } if args.rir_dir.is_none() { return Err(format!("--rir-dir is required\n{}", usage())); } Ok(args) } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] struct RirBundleMetadataV2Serde { schema_version: String, bundle_producer: String, rir: String, tal_sha256: String, ta_cert_sha256: String, has_any_aspa: bool, has_any_router_key: bool, base: BaseBundleStateMetadataV2Serde, delta_sequence: DeltaSequenceMetadataV2Serde, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] struct BaseBundleStateMetadataV2Serde { validation_time: String, ccr_sha256: String, vrp_count: usize, vap_count: usize, relative_archive_path: String, relative_locks_path: String, relative_ccr_path: String, relative_vrps_path: String, relative_vaps_path: String, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] struct DeltaSequenceMetadataV2Serde { configured_delta_count: usize, configured_interval_seconds: u64, steps: Vec, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] struct DeltaStepMetadataV2Serde { index: usize, id: String, relative_path: String, base_ref: String, validation_time: String, delta_ccr_sha256: String, vrp_count: usize, vap_count: usize, relative_archive_path: String, relative_transition_locks_path: String, relative_target_locks_path: String, relative_ccr_path: String, relative_vrps_path: String, relative_vaps_path: String, has_aspa: bool, has_router_key: bool, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] struct VerificationV2 { base: serde_json::Value, steps: Vec, summary: serde_json::Value, } fn parse_time(value: &str) -> Result { time::OffsetDateTime::parse(value, &Rfc3339) .map_err(|e| format!("invalid RFC3339 time `{value}`: {e}")) } fn path_join(root: &Path, relative: &str) -> PathBuf { root.join(relative) } fn is_failed_fetch_source(source: &str) -> bool { source == "failed_fetch_no_cache" } fn current_module_objects_from_store( store: &RocksStore, module_uri: &str, ) -> Result>, String> { let entries = store .list_repository_view_entries_with_prefix(module_uri) .map_err(|e| format!("list repository view failed for {module_uri}: {e}"))?; let mut out = BTreeMap::new(); for entry in entries { if entry.state != rpki::storage::RepositoryViewState::Present { continue; } let bytes = store .load_current_object_bytes_by_uri(&entry.rsync_uri) .map_err(|e| format!("load current object failed for {}: {e}", entry.rsync_uri))? .ok_or_else(|| format!("current object missing for {}", entry.rsync_uri))?; out.insert(entry.rsync_uri, bytes); } Ok(out) } fn rsync_bucket_dir(capture_root: &Path, module_uri: &str) -> PathBuf { capture_root .join("rsync") .join("modules") .join(sha256_hex(module_uri.as_bytes())) } fn materialize_rsync_module_from_store( capture_root: &Path, module_uri: &str, objects: &BTreeMap>, ) -> Result, String> { let bucket_dir = rsync_bucket_dir(capture_root, module_uri); let tree_root = bucket_dir.join("tree"); if tree_root.exists() { fs::remove_dir_all(&tree_root) .map_err(|e| format!("remove old rsync tree failed: {}: {e}", tree_root.display()))?; } let relative_root = module_uri .strip_prefix("rsync://") .ok_or_else(|| format!("invalid rsync module uri: {module_uri}"))? .trim_end_matches('/'); fs::create_dir_all(tree_root.join(relative_root)).map_err(|e| { format!( "create rsync tree root failed: {}: {e}", tree_root.join(relative_root).display() ) })?; for (uri, bytes) in objects { let rel = uri .strip_prefix(module_uri) .ok_or_else(|| format!("object uri {uri} does not belong to module {module_uri}"))?; let path = tree_root.join(relative_root).join(rel); if let Some(parent) = path.parent() { fs::create_dir_all(parent).map_err(|e| { format!( "create rsync object parent failed: {}: {e}", parent.display() ) })?; } fs::write(&path, bytes) .map_err(|e| format!("write rsync object failed: {}: {e}", path.display()))?; } Ok(objects.keys().cloned().collect()) } fn copy_dir_all(src: &Path, dst: &Path) -> Result<(), String> { fs::create_dir_all(dst) .map_err(|e| format!("create directory failed: {}: {e}", dst.display()))?; for entry in fs::read_dir(src).map_err(|e| format!("read directory failed: {}: {e}", src.display()))? { let entry = entry.map_err(|e| format!("read entry failed: {}: {e}", src.display()))?; let file_type = entry .file_type() .map_err(|e| format!("read file type failed: {}: {e}", entry.path().display()))?; let target = dst.join(entry.file_name()); if file_type.is_dir() { copy_dir_all(&entry.path(), &target)?; } else if file_type.is_file() { fs::copy(entry.path(), &target).map_err(|e| { format!( "copy file failed: {} -> {}: {e}", entry.path().display(), target.display() ) })?; } } Ok(()) } fn load_json(path: &Path) -> Result { serde_json::from_slice( &fs::read(path).map_err(|e| format!("read json failed: {}: {e}", path.display()))?, ) .map_err(|e| format!("parse json failed: {}: {e}", path.display())) } fn write_json_value(path: &Path, value: &serde_json::Value) -> Result<(), String> { if let Some(parent) = path.parent() { fs::create_dir_all(parent) .map_err(|e| format!("create json parent failed: {}: {e}", parent.display()))?; } fs::write( path, serde_json::to_vec_pretty(value).map_err(|e| format!("serialize json failed: {e}"))?, ) .map_err(|e| format!("write json failed: {}: {e}", path.display())) } fn base_capture_root_from_locks(archive_root: &Path, locks_path: &Path) -> Result { let value = load_json(locks_path)?; let capture = value .get("capture") .and_then(|v| v.as_str()) .ok_or_else(|| format!("missing capture in {}", locks_path.display()))?; Ok(archive_root.join("v1").join("captures").join(capture)) } fn keep_rsync_module(pp: &rpki::audit::PublicationPointAudit) -> Result, String> { if is_failed_fetch_source(&pp.source) { return Ok(None); } let module_uri = canonical_rsync_module(&pp.rsync_base_uri).map_err(|e| { format!( "canonicalize rsync module failed for {}: {e}", pp.rsync_base_uri ) })?; if pp.rrdp_notification_uri.is_none() || pp.repo_sync_source.as_deref() == Some("rsync") { return Ok(Some(module_uri)); } Ok(None) } fn repair_base_inputs( archive_root: &Path, locks_path: &Path, publication_points: &[rpki::audit::PublicationPointAudit], store: &RocksStore, verification: &mut VerificationV2, ) -> Result<(), String> { let capture_root = base_capture_root_from_locks(archive_root, locks_path)?; let mut locks = load_json(locks_path)?; let candidate_modules: BTreeSet = publication_points .iter() .filter_map(|pp| keep_rsync_module(pp).transpose()) .collect::, _>>()? .into_iter() .collect(); let old_modules: Vec = locks .get("rsync") .and_then(|v| v.as_object()) .map(|m| m.keys().cloned().collect()) .unwrap_or_default(); if let Some(rrdp_obj) = locks.get_mut("rrdp").and_then(|v| v.as_object_mut()) { for pp in publication_points { let Some(notify_uri) = pp.rrdp_notification_uri.as_deref() else { continue; }; let lock_value = match store .get_rrdp_source_record(notify_uri) .map_err(|e| format!("read rrdp source record failed for {notify_uri}: {e}"))? { Some(record) if record.last_session_id.is_some() && record.last_serial.is_some() && record.last_snapshot_uri.is_some() && record.last_snapshot_hash.is_some() => { serde_json::json!({ "transport": "rrdp", "session": record.last_session_id, "serial": record.last_serial, }) } _ => serde_json::json!({ "transport": "rsync", "session": null, "serial": null, }), }; rrdp_obj.insert(notify_uri.to_string(), lock_value); } let repos_dir = capture_root.join("rrdp").join("repos"); if repos_dir.exists() { for entry in fs::read_dir(&repos_dir) .map_err(|e| format!("scan rrdp repo dir failed: {}: {e}", repos_dir.display()))? { let entry = entry.map_err(|e| { format!("read rrdp repo entry failed: {}: {e}", repos_dir.display()) })?; let meta = entry.path().join("meta.json"); if !meta.exists() { continue; } let meta_value = load_json(&meta)?; let notify_uri = match meta_value.get("rpkiNotify").and_then(|v| v.as_str()) { Some(value) => value.to_string(), None => continue, }; if rrdp_obj.contains_key(¬ify_uri) { continue; } let lock_value = match store .get_rrdp_source_record(¬ify_uri) .map_err(|e| format!("read rrdp source record failed for {notify_uri}: {e}"))? { Some(record) if record.last_session_id.is_some() && record.last_serial.is_some() && record.last_snapshot_uri.is_some() && record.last_snapshot_hash.is_some() => { serde_json::json!({ "transport": "rrdp", "session": record.last_session_id, "serial": record.last_serial, }) } _ => serde_json::json!({ "transport": "rsync", "session": null, "serial": null, }), }; rrdp_obj.insert(notify_uri, lock_value); } } } let mut final_modules = serde_json::Map::new(); for module_uri in candidate_modules { let objects = current_module_objects_from_store(store, &module_uri)?; if objects.is_empty() { continue; } let _files = materialize_rsync_module_from_store(&capture_root, &module_uri, &objects)?; final_modules.insert( module_uri, serde_json::json!({ "transport": "rsync" }), ); } for module_uri in old_modules { if !final_modules.contains_key(&module_uri) { let bucket_dir = rsync_bucket_dir(&capture_root, &module_uri); let _ = fs::remove_dir_all(bucket_dir); } } if let Some(rsync_value) = locks.get_mut("rsync") { *rsync_value = serde_json::Value::Object(final_modules.clone()); } write_json_value(locks_path, &locks)?; verification.base["capture"]["rrdpRepoCount"] = serde_json::Value::from( locks .get("rrdp") .and_then(|v| v.as_object()) .map(|m| m.len()) .unwrap_or(0), ); verification.base["capture"]["rsyncModuleCount"] = serde_json::Value::from(final_modules.len()); Ok(()) } fn repair_target_locks( locks_path: &Path, previous_locks_path: &Path, publication_points: &[rpki::audit::PublicationPointAudit], store: &RocksStore, ) -> Result<(), String> { let mut locks = load_json(locks_path)?; let previous_locks = load_json(previous_locks_path)?; if let Some(rrdp_obj) = locks.get_mut("rrdp").and_then(|v| v.as_object_mut()) { for pp in publication_points { let Some(notify_uri) = pp.rrdp_notification_uri.as_deref() else { continue; }; let mut lock_value = match store .get_rrdp_source_record(notify_uri) .map_err(|e| format!("read rrdp source record failed for {notify_uri}: {e}"))? { Some(record) if record.last_session_id.is_some() && record.last_serial.is_some() && record.last_snapshot_uri.is_some() && record.last_snapshot_hash.is_some() => { serde_json::json!({ "transport": "rrdp", "session": record.last_session_id, "serial": record.last_serial, }) } _ => serde_json::json!({ "transport": "rsync", "session": null, "serial": null, }), }; let previous_transport = previous_locks .get("rrdp") .and_then(|v| v.get(notify_uri)) .and_then(|v| v.get("transport")) .and_then(|v| v.as_str()); if previous_transport != Some("rrdp") { lock_value = serde_json::json!({ "transport": "rsync", "session": null, "serial": null, }); } rrdp_obj.insert(notify_uri.to_string(), lock_value); } } let candidate_modules: BTreeSet = publication_points .iter() .filter_map(|pp| keep_rsync_module(pp).transpose()) .collect::, _>>()? .into_iter() .collect(); let mut final_modules = serde_json::Map::new(); for module_uri in candidate_modules { let objects = current_module_objects_from_store(store, &module_uri)?; if objects.is_empty() { continue; } final_modules.insert( module_uri, serde_json::json!({ "transport": "rsync" }), ); } if let Some(rsync_value) = locks.get_mut("rsync") { *rsync_value = serde_json::Value::Object(final_modules); } write_json_value(locks_path, &locks) } fn repair_delta_step_inputs( step_dir: &Path, base_archive_root: &Path, base_locks_path: &Path, previous_locks_path: &Path, publication_points: &[rpki::audit::PublicationPointAudit], store: &RocksStore, step_verification: &mut serde_json::Value, ) -> Result<(), String> { let locks_path = step_dir.join("locks-delta.json"); let mut locks = load_json(&locks_path)?; let previous_locks = load_json(previous_locks_path)?; let capture = locks .get("capture") .and_then(|v| v.as_str()) .ok_or_else(|| format!("missing capture in {}", locks_path.display()))? .to_string(); let capture_root = step_dir .join("payload-delta-archive") .join("v1") .join("captures") .join(capture); if let Some(rrdp_obj) = locks.get_mut("rrdp").and_then(|v| v.as_object_mut()) { let repos_dir = capture_root.join("rrdp").join("repos"); if repos_dir.exists() { for entry in fs::read_dir(&repos_dir) .map_err(|e| format!("scan rrdp repo dir failed: {}: {e}", repos_dir.display()))? { let entry = entry.map_err(|e| { format!("read rrdp repo entry failed: {}: {e}", repos_dir.display()) })?; let meta = entry.path().join("meta.json"); if !meta.exists() { continue; } let meta_value = load_json(&meta)?; let notify_uri = match meta_value.get("rpkiNotify").and_then(|v| v.as_str()) { Some(value) => value.to_string(), None => continue, }; if rrdp_obj.contains_key(¬ify_uri) { continue; } let transition_path = entry.path().join("transition.json"); let lock_value = if transition_path.exists() { let transition = load_json(&transition_path)?; serde_json::json!({ "kind": transition.get("kind").cloned().unwrap_or(serde_json::Value::String("fallback-rsync".to_string())), "base": transition.get("base").cloned().unwrap_or(serde_json::json!({ "transport": "rsync", "session": null, "serial": null })), "target": transition.get("target").cloned().unwrap_or(serde_json::json!({ "transport": "rsync", "session": null, "serial": null })), "delta_count": transition.get("delta_count").cloned().unwrap_or(serde_json::Value::from(0)), "deltas": transition.get("deltas").cloned().unwrap_or(serde_json::Value::Array(vec![])), }) } else { serde_json::json!({ "kind": "fallback-rsync", "base": {"transport":"rsync","session":null,"serial":null}, "target": {"transport":"rsync","session":null,"serial":null}, "delta_count": 0, "deltas": [] }) }; rrdp_obj.insert(notify_uri, lock_value); } } for (notify_uri, entry) in rrdp_obj.iter_mut() { let previous_transport = previous_locks .get("rrdp") .and_then(|v| v.get(notify_uri)) .and_then(|v| v.get("transport")) .and_then(|v| v.as_str()); if previous_transport != Some("rrdp") { let fallback = serde_json::json!({ "kind": "fallback-rsync", "base": {"transport":"rsync","session":null,"serial":null}, "target": {"transport":"rsync","session":null,"serial":null}, "delta_count": 0, "deltas": [] }); *entry = fallback.clone(); let bucket_dir = capture_root .join("rrdp") .join("repos") .join(sha256_hex(notify_uri.as_bytes())); if bucket_dir.exists() { write_json(&bucket_dir.join("transition.json"), &fallback)?; } } } } let candidate_modules: BTreeSet = publication_points .iter() .filter_map(|pp| keep_rsync_module(pp).transpose()) .collect::, _>>()? .into_iter() .collect(); let old_modules: Vec = locks .get("rsync") .and_then(|v| v.as_object()) .map(|m| m.keys().cloned().collect()) .unwrap_or_default(); let mut final_modules = serde_json::Map::new(); for module_uri in candidate_modules { let objects = current_module_objects_from_store(store, &module_uri)?; if objects.is_empty() { continue; } let files = materialize_rsync_module_from_store(&capture_root, &module_uri, &objects)?; let bucket_dir = rsync_bucket_dir(&capture_root, &module_uri); write_json( &bucket_dir.join("files.json"), &serde_json::json!({ "version": 1, "module": module_uri, "fileCount": files.len(), "files": files, }), )?; final_modules.insert( module_uri, serde_json::json!({ "file_count": objects.len(), "overlay_only": true }), ); } for module_uri in old_modules { if !final_modules.contains_key(&module_uri) { let bucket_dir = rsync_bucket_dir(&capture_root, &module_uri); let _ = fs::remove_dir_all(bucket_dir); } } if let Some(rsync_value) = locks.get_mut("rsync") { *rsync_value = serde_json::Value::Object(final_modules.clone()); } let base_capture_root = base_capture_root_from_locks(base_archive_root, base_locks_path)?; if let Some(rrdp_obj) = locks.get("rrdp").and_then(|v| v.as_object()) { for (notify_uri, entry) in rrdp_obj { let kind = entry.get("kind").and_then(|v| v.as_str()).unwrap_or(""); if kind != "unchanged" { continue; } let session = entry .get("target") .and_then(|v| v.get("session")) .and_then(|v| v.as_str()) .or_else(|| { entry .get("base") .and_then(|v| v.get("session")) .and_then(|v| v.as_str()) }); let Some(session) = session else { continue }; let bucket_hash = sha256_hex(notify_uri.as_bytes()); let bucket_dir = capture_root.join("rrdp").join("repos").join(&bucket_hash); let session_dir = bucket_dir.join(session); if session_dir.exists() { continue; } let base_bucket_dir = base_capture_root .join("rrdp") .join("repos") .join(&bucket_hash); let base_session_dir = base_bucket_dir.join(session); if !base_session_dir.exists() { continue; } fs::create_dir_all(&bucket_dir).map_err(|e| { format!( "create delta rrdp repo dir failed: {}: {e}", bucket_dir.display() ) })?; let base_meta = base_bucket_dir.join("meta.json"); if !bucket_dir.join("meta.json").exists() && base_meta.exists() { fs::copy(&base_meta, bucket_dir.join("meta.json")).map_err(|e| { format!( "copy base repo meta failed: {} -> {}: {e}", base_meta.display(), bucket_dir.join("meta.json").display() ) })?; } copy_dir_all(&base_session_dir, &session_dir)?; } } write_json_value(&locks_path, &locks)?; step_verification["capture"]["rrdpRepoCount"] = serde_json::Value::from( locks .get("rrdp") .and_then(|v| v.as_object()) .map(|m| m.len()) .unwrap_or(0), ); step_verification["capture"]["rsyncModuleCount"] = serde_json::Value::from(final_modules.len()); Ok(()) } fn rewrite_delta_base_hash(step_dir: &Path, previous_locks_path: &Path) -> Result<(), String> { let previous_locks_bytes = fs::read(previous_locks_path).map_err(|e| { format!( "read previous locks failed for delta base hash rewrite: {}: {e}", previous_locks_path.display() ) })?; let previous_locks_sha256 = sha256_hex(&previous_locks_bytes); let locks_path = step_dir.join("locks-delta.json"); let mut locks = load_json(&locks_path)?; let previous_locks = serde_json::from_slice::(&previous_locks_bytes) .map_err(|e| { format!( "parse previous locks failed: {}: {e}", previous_locks_path.display() ) })?; locks["baseLocksSha256"] = serde_json::Value::String(previous_locks_sha256.clone()); let capture = locks .get("capture") .and_then(|v| v.as_str()) .map(|s| s.to_string()) .ok_or_else(|| format!("missing capture in {}", locks_path.display()))?; write_json_value(&locks_path, &locks)?; let base_meta_path = step_dir .join("payload-delta-archive") .join("v1") .join("captures") .join(&capture) .join("base.json"); let mut base_meta = load_json(&base_meta_path)?; base_meta["baseLocksSha256"] = serde_json::Value::String(previous_locks_sha256); write_json_value(&base_meta_path, &base_meta)?; if let Some(rrdp_obj) = locks.get_mut("rrdp").and_then(|v| v.as_object_mut()) { for (notify_uri, entry) in rrdp_obj.iter_mut() { let previous_transport = previous_locks .get("rrdp") .and_then(|v| v.get(notify_uri)) .and_then(|v| v.get("transport")) .and_then(|v| v.as_str()); if previous_transport != Some("rrdp") { let fallback = serde_json::json!({ "kind": "fallback-rsync", "base": {"transport":"rsync","session":null,"serial":null}, "target": {"transport":"rsync","session":null,"serial":null}, "delta_count": 0, "deltas": [] }); *entry = fallback.clone(); let bucket_dir = step_dir .join("payload-delta-archive") .join("v1") .join("captures") .join(&capture) .join("rrdp") .join("repos") .join(sha256_hex(notify_uri.as_bytes())); if bucket_dir.exists() { write_json(&bucket_dir.join("transition.json"), &fallback)?; } } } } write_json_value(&locks_path, &locks)?; Ok(()) } fn main() { if let Err(err) = real_main() { eprintln!("{err}"); std::process::exit(1); } } fn real_main() -> Result<(), String> { let args = parse_args()?; let rir_dir = args.rir_dir.unwrap(); let bundle_json_path = rir_dir.join("bundle.json"); let verification_path = rir_dir.join("verification.json"); let mut bundle: RirBundleMetadataV2Serde = serde_json::from_slice(&fs::read(&bundle_json_path).map_err(|e| { format!( "read bundle.json failed: {}: {e}", bundle_json_path.display() ) })?) .map_err(|e| { format!( "parse bundle.json failed: {}: {e}", bundle_json_path.display() ) })?; let mut verification: VerificationV2 = serde_json::from_slice(&fs::read(&verification_path).map_err(|e| { format!( "read verification.json failed: {}: {e}", verification_path.display() ) })?) .map_err(|e| { format!( "parse verification.json failed: {}: {e}", verification_path.display() ) })?; let tal_bytes = fs::read(rir_dir.join("tal.tal")) .map_err(|e| format!("read tal.tal failed: {}: {e}", rir_dir.display()))?; let ta_bytes = fs::read(rir_dir.join("ta.cer")) .map_err(|e| format!("read ta.cer failed: {}: {e}", rir_dir.display()))?; let tmp_root = rir_dir.parent().unwrap_or(&rir_dir).join(".tmp-refresh"); let work_db = tmp_root.join(format!("{}-work-db", bundle.rir)); if work_db.exists() { fs::remove_dir_all(&work_db) .map_err(|e| format!("remove old refresh db failed: {}: {e}", work_db.display()))?; } if let Some(parent) = work_db.parent() { fs::create_dir_all(parent) .map_err(|e| format!("create refresh db parent failed: {}: {e}", parent.display()))?; } let store = RocksStore::open(&work_db).map_err(|e| format!("open refresh rocksdb failed: {e}"))?; let base_archive = path_join(&rir_dir, &bundle.base.relative_archive_path); let base_locks = path_join(&rir_dir, &bundle.base.relative_locks_path); let base_ccr = path_join(&rir_dir, &bundle.base.relative_ccr_path); let base_vrps = path_join(&rir_dir, &bundle.base.relative_vrps_path); let base_vaps = path_join(&rir_dir, &bundle.base.relative_vaps_path); let base_validation_time = parse_time(&bundle.base.validation_time)?; let base_out = run_tree_from_tal_and_ta_der_payload_replay_serial_audit( &store, &Policy::default(), &tal_bytes, &ta_bytes, None, &base_archive, &base_locks, base_validation_time, &TreeRunConfig { max_depth: None, max_instances: None, }, ) .map_err(|e| format!("base replay failed: {e}"))?; let base_ccr_content = build_ccr_from_run( &store, &[base_out.discovery.trust_anchor.clone()], &base_out.tree.vrps, &base_out.tree.aspas, &base_out.tree.router_keys, base_validation_time, ) .map_err(|e| format!("build base ccr failed: {e}"))?; write_ccr_file(&base_ccr, &base_ccr_content) .map_err(|e| format!("write base ccr failed: {}: {e}", base_ccr.display()))?; let base_ccr_bytes = fs::read(&base_ccr) .map_err(|e| format!("read base ccr failed: {}: {e}", base_ccr.display()))?; let base_decoded = decode_content_info(&base_ccr_bytes).map_err(|e| format!("decode base ccr failed: {e}"))?; let base_verify = verify_content_info(&base_decoded).map_err(|e| format!("verify base ccr failed: {e}"))?; let base_vrp_rows = build_vrp_compare_rows(&base_out.tree.vrps, &bundle.rir); let base_vap_rows = build_vap_compare_rows(&base_out.tree.aspas, &bundle.rir); write_vrp_csv(&base_vrps, &base_vrp_rows)?; write_vap_csv(&base_vaps, &base_vap_rows)?; bundle.base.ccr_sha256 = sha256_hex(&base_ccr_bytes); bundle.base.vrp_count = base_vrp_rows.len(); bundle.base.vap_count = base_vap_rows.len(); verification.base["ccr"]["sha256"] = serde_json::Value::String(bundle.base.ccr_sha256.clone()); verification.base["ccr"]["stateHashesOk"] = serde_json::Value::Bool(base_verify.state_hashes_ok); verification.base["ccr"]["manifestInstances"] = serde_json::Value::from(base_verify.manifest_instances); verification.base["ccr"]["roaVrpCount"] = serde_json::Value::from(base_vrp_rows.len()); verification.base["ccr"]["aspaPayloadSets"] = serde_json::Value::from(base_vap_rows.len()); verification.base["ccr"]["routerKeyCount"] = serde_json::Value::from(base_verify.router_key_count); verification.base["compareViews"]["baseVrpCount"] = serde_json::Value::from(base_vrp_rows.len()); verification.base["compareViews"]["baseVapCount"] = serde_json::Value::from(base_vap_rows.len()); verification.base["capture"]["selfReplayOk"] = serde_json::Value::Bool(true); repair_base_inputs( &base_archive, &base_locks, &base_out.publication_points, &store, &mut verification, )?; let mut previous_locks_path = base_locks.clone(); let mut any_aspa = !base_vap_rows.is_empty(); let mut all_steps_self_replay_ok = true; for (idx, step) in bundle.delta_sequence.steps.iter_mut().enumerate() { let step_dir = path_join(&rir_dir, &step.relative_path); rewrite_delta_base_hash(&step_dir, &previous_locks_path)?; let delta_archive = path_join(&rir_dir, &step.relative_archive_path); let delta_locks = path_join(&rir_dir, &step.relative_transition_locks_path); let delta_ccr = path_join(&rir_dir, &step.relative_ccr_path); let delta_vrps = path_join(&rir_dir, &step.relative_vrps_path); let delta_vaps = path_join(&rir_dir, &step.relative_vaps_path); let target_locks = path_join(&rir_dir, &step.relative_target_locks_path); let delta_validation_time = parse_time(&step.validation_time)?; let delta_out = run_tree_from_tal_and_ta_der_payload_delta_replay_step_serial_audit( &store, &Policy::default(), &tal_bytes, &ta_bytes, None, &delta_archive, &previous_locks_path, &delta_locks, delta_validation_time, &TreeRunConfig { max_depth: None, max_instances: None, }, ) .map_err(|e| format!("delta step {} replay failed: {e}", step.id))?; let delta_ccr_content = build_ccr_from_run( &store, &[delta_out.discovery.trust_anchor.clone()], &delta_out.tree.vrps, &delta_out.tree.aspas, &delta_out.tree.router_keys, delta_validation_time, ) .map_err(|e| format!("build delta ccr failed for {}: {e}", step.id))?; write_ccr_file(&delta_ccr, &delta_ccr_content) .map_err(|e| format!("write delta ccr failed: {}: {e}", delta_ccr.display()))?; let delta_ccr_bytes = fs::read(&delta_ccr) .map_err(|e| format!("read delta ccr failed: {}: {e}", delta_ccr.display()))?; let delta_decoded = decode_content_info(&delta_ccr_bytes) .map_err(|e| format!("decode delta ccr failed for {}: {e}", step.id))?; let delta_verify = verify_content_info(&delta_decoded) .map_err(|e| format!("verify delta ccr failed for {}: {e}", step.id))?; let delta_vrp_rows = build_vrp_compare_rows(&delta_out.tree.vrps, &bundle.rir); let delta_vap_rows = build_vap_compare_rows(&delta_out.tree.aspas, &bundle.rir); write_vrp_csv(&delta_vrps, &delta_vrp_rows)?; write_vap_csv(&delta_vaps, &delta_vap_rows)?; step.delta_ccr_sha256 = sha256_hex(&delta_ccr_bytes); step.vrp_count = delta_vrp_rows.len(); step.vap_count = delta_vap_rows.len(); step.has_aspa = !delta_vap_rows.is_empty(); any_aspa |= step.has_aspa; if let Some(step_verification) = verification.steps.get_mut(idx) { step_verification["ccr"]["sha256"] = serde_json::Value::String(step.delta_ccr_sha256.clone()); step_verification["ccr"]["stateHashesOk"] = serde_json::Value::Bool(delta_verify.state_hashes_ok); step_verification["ccr"]["manifestInstances"] = serde_json::Value::from(delta_verify.manifest_instances); step_verification["ccr"]["roaVrpCount"] = serde_json::Value::from(delta_vrp_rows.len()); step_verification["ccr"]["aspaPayloadSets"] = serde_json::Value::from(delta_vap_rows.len()); step_verification["ccr"]["routerKeyCount"] = serde_json::Value::from(delta_verify.router_key_count); step_verification["compareViews"]["vrpCount"] = serde_json::Value::from(delta_vrp_rows.len()); step_verification["compareViews"]["vapCount"] = serde_json::Value::from(delta_vap_rows.len()); step_verification["selfReplayOk"] = serde_json::Value::Bool(true); } let step_verification_path = path_join(&rir_dir, &step.relative_path).join("verification.json"); let mut step_verification_json: serde_json::Value = serde_json::from_slice(&fs::read(&step_verification_path).map_err(|e| { format!( "read step verification failed: {}: {e}", step_verification_path.display() ) })?) .map_err(|e| { format!( "parse step verification failed: {}: {e}", step_verification_path.display() ) })?; step_verification_json["ccr"]["sha256"] = serde_json::Value::String(step.delta_ccr_sha256.clone()); step_verification_json["ccr"]["stateHashesOk"] = serde_json::Value::Bool(delta_verify.state_hashes_ok); step_verification_json["ccr"]["manifestInstances"] = serde_json::Value::from(delta_verify.manifest_instances); step_verification_json["ccr"]["roaVrpCount"] = serde_json::Value::from(delta_vrp_rows.len()); step_verification_json["ccr"]["aspaPayloadSets"] = serde_json::Value::from(delta_vap_rows.len()); step_verification_json["ccr"]["routerKeyCount"] = serde_json::Value::from(delta_verify.router_key_count); step_verification_json["compareViews"]["vrpCount"] = serde_json::Value::from(delta_vrp_rows.len()); step_verification_json["compareViews"]["vapCount"] = serde_json::Value::from(delta_vap_rows.len()); step_verification_json["selfReplayOk"] = serde_json::Value::Bool(true); repair_delta_step_inputs( &step_dir, &base_archive, &base_locks, &previous_locks_path, &delta_out.publication_points, &store, &mut step_verification_json, )?; write_json(&step_verification_path, &step_verification_json)?; all_steps_self_replay_ok &= true; repair_target_locks( &target_locks, &previous_locks_path, &delta_out.publication_points, &store, )?; previous_locks_path = target_locks; } bundle.has_any_aspa = any_aspa; verification.summary["baseSelfReplayOk"] = serde_json::Value::Bool(true); verification.summary["allStepsSelfReplayOk"] = serde_json::Value::Bool(all_steps_self_replay_ok); write_json(&bundle_json_path, &bundle)?; write_json(&verification_path, &verification)?; if !args.keep_db && work_db.exists() { fs::remove_dir_all(&work_db) .map_err(|e| format!("remove refresh db failed: {}: {e}", work_db.display()))?; if tmp_root.exists() && fs::read_dir(&tmp_root) .map_err(|e| format!("read_dir failed: {}: {e}", tmp_root.display()))? .next() .is_none() { let _ = fs::remove_dir(&tmp_root); } } println!("{}", rir_dir.display()); Ok(()) }