20260420_2 完成输出report和ccr优化,snapshot耗时优化到70多秒

This commit is contained in:
2026-04-20 18:28:59 +08:00
parent f6a601e16c
commit 542bd7be80
21 changed files with 2111 additions and 344 deletions

View File

@@ -4,7 +4,7 @@ use crate::data_model::signed_object::SignedObjectVerifyError;
use crate::policy::{CaFailedFetchPolicy, Policy};
use crate::report::{RfcRef, Warning};
use crate::storage::{PackFile, PackTime, RocksStore, StorageError, VcirArtifactRole};
use crate::validation::cert_path::{CertPathError, validate_signed_object_ee_cert_path_fast};
use crate::validation::cert_path::{validate_signed_object_ee_cert_path_fast, CertPathError};
use crate::validation::publication_point::PublicationPointSnapshot;
use sha2::Digest;
use std::cmp::Ordering;
@@ -398,11 +398,10 @@ pub fn process_manifest_publication_point_after_repo_sync(
Err(ManifestProcessError::StopAllOutput(fresh_err))
}
CaFailedFetchPolicy::ReuseCurrentInstanceVcir => {
let mut warnings = vec![
Warning::new(format!("manifest failed fetch: {fresh_err}"))
let mut warnings =
vec![Warning::new(format!("manifest failed fetch: {fresh_err}"))
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
.with_context(manifest_rsync_uri),
];
.with_context(manifest_rsync_uri)];
match load_current_instance_vcir_publication_point(
store,
@@ -932,7 +931,8 @@ mod tests {
use crate::storage::{
PackFile, PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult,
ValidatedManifestMeta, VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus,
VcirAuditSummary, VcirInstanceGate, VcirRelatedArtifact, VcirSummary,
VcirAuditSummary, VcirCcrManifestProjection, VcirInstanceGate, VcirRelatedArtifact,
VcirSummary,
};
use std::path::Path;
@@ -1039,6 +1039,19 @@ mod tests {
) -> ValidatedCaInstanceResult {
let gate_time =
PackTime::from_utc_offset_datetime(validation_time + time::Duration::hours(1));
let ccr_manifest_projection = VcirCcrManifestProjection {
manifest_rsync_uri: manifest_rsync_uri.to_string(),
manifest_sha256: hex::decode(manifest_sha256).expect("decode manifest sha256"),
manifest_size: 2048,
manifest_ee_aki: vec![0x11; 20],
manifest_number_be: vec![1],
manifest_this_update: PackTime::from_utc_offset_datetime(validation_time),
manifest_sia_locations_der: vec![vec![
0x30, 0x11, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x05, 0x86, 0x05,
b'r', b's', b'y', b'n', b'c',
]],
subordinate_skis: Vec::new(),
};
ValidatedCaInstanceResult {
manifest_rsync_uri: manifest_rsync_uri.to_string(),
parent_manifest_rsync_uri: None,
@@ -1054,6 +1067,7 @@ mod tests {
validated_manifest_this_update: PackTime::from_utc_offset_datetime(validation_time),
validated_manifest_next_update: gate_time.clone(),
},
ccr_manifest_projection,
instance_gate: VcirInstanceGate {
manifest_next_update: gate_time.clone(),
current_crl_next_update: gate_time.clone(),
@@ -1313,12 +1327,10 @@ mod tests {
.apply_repository_view_entries(&entries)
.expect("apply current index");
assert!(
store
.get_repository_view_entry(&manifest_rsync_uri)
.expect("get repository view")
.is_none()
);
assert!(store
.get_repository_view_entry(&manifest_rsync_uri)
.expect("get repository view")
.is_none());
let (fresh, _timing) = try_build_fresh_publication_point_with_timing(
&store,

View File

@@ -77,6 +77,7 @@ pub fn run_publication_point_once(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let result = runner

View File

@@ -3,6 +3,7 @@ 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};
@@ -90,6 +91,7 @@ pub struct RunTreeFromTalAuditOutput {
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)]
@@ -118,6 +120,7 @@ fn make_live_runner<'a>(
current_repo_index: Option<CurrentRepoIndexHandle>,
repo_sync_runtime: Option<Arc<dyn RepoSyncRuntime>>,
parallel_phase2_config: Option<ParallelPhase2Config>,
ccr_accumulator: Option<CcrAccumulator>,
) -> Rpkiv1PublicationPointRunner<'a> {
let parallel_roa_worker_pool = parallel_phase2_config
.as_ref()
@@ -140,6 +143,7 @@ fn make_live_runner<'a>(
repo_sync_runtime,
parallel_phase2_config,
parallel_roa_worker_pool,
ccr_accumulator: ccr_accumulator.map(Mutex::new),
}
}
@@ -291,6 +295,7 @@ pub fn run_tree_from_tal_url_serial(
None,
None,
None,
None,
);
let root = root_handle_from_trust_anchor(
@@ -327,6 +332,7 @@ pub fn run_tree_from_tal_url_serial_audit(
None,
None,
None,
None,
);
let root = root_handle_from_trust_anchor(
@@ -350,6 +356,7 @@ pub fn run_tree_from_tal_url_serial_audit(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}
@@ -379,6 +386,7 @@ pub fn run_tree_from_tal_url_serial_audit_with_timing(
None,
None,
None,
None,
);
let root = root_handle_from_trust_anchor(
@@ -403,6 +411,7 @@ pub fn run_tree_from_tal_url_serial_audit_with_timing(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}
@@ -446,6 +455,7 @@ where
Some(current_repo_index),
Some(runtime),
phase2_config,
phase2_enabled.then(|| CcrAccumulator::new(vec![discovery.trust_anchor.clone()])),
);
let root = root_handle_from_trust_anchor(
@@ -472,6 +482,9 @@ where
downloads,
download_stats,
current_repo_objects: snapshot_current_repo_objects(Some(&current_repo_index_for_output)),
ccr_accumulator: phase2_enabled
.then(|| runner.ccr_accumulator_snapshot())
.flatten(),
})
}
@@ -532,6 +545,14 @@ where
Some(current_repo_index),
Some(runtime),
phase2_config,
phase2_enabled.then(|| {
CcrAccumulator::new(
discoveries
.iter()
.map(|item| item.trust_anchor.clone())
.collect::<Vec<_>>(),
)
}),
);
let TreeRunAuditOutput {
@@ -552,6 +573,9 @@ where
downloads,
download_stats,
current_repo_objects: snapshot_current_repo_objects(Some(&current_repo_index_for_output)),
ccr_accumulator: phase2_enabled
.then(|| runner.ccr_accumulator_snapshot())
.flatten(),
})
}
@@ -792,6 +816,7 @@ pub fn run_tree_from_tal_and_ta_der_serial(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let root = root_handle_from_trust_anchor(
@@ -842,6 +867,7 @@ pub fn run_tree_from_tal_bytes_serial_audit(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let root = root_handle_from_trust_anchor(
@@ -865,6 +891,7 @@ pub fn run_tree_from_tal_bytes_serial_audit(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}
@@ -908,6 +935,7 @@ pub fn run_tree_from_tal_bytes_serial_audit_with_timing(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let root = root_handle_from_trust_anchor(
@@ -933,6 +961,7 @@ pub fn run_tree_from_tal_bytes_serial_audit_with_timing(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}
@@ -969,6 +998,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let root = root_handle_from_trust_anchor(
@@ -992,6 +1022,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}
@@ -1031,6 +1062,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit_with_timing(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let root = root_handle_from_trust_anchor(
@@ -1055,6 +1087,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit_with_timing(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}
@@ -1100,6 +1133,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let root = root_handle_from_trust_anchor(
@@ -1156,6 +1190,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let root = root_handle_from_trust_anchor(
@@ -1179,6 +1214,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}
@@ -1228,6 +1264,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let root = root_handle_from_trust_anchor(
@@ -1252,6 +1289,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}
@@ -1283,6 +1321,7 @@ fn build_payload_replay_runner<'a>(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
}
}
@@ -1314,6 +1353,7 @@ fn build_payload_delta_replay_runner<'a>(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
}
}
@@ -1345,6 +1385,7 @@ fn build_payload_delta_replay_current_store_runner<'a>(
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
}
}
@@ -1461,6 +1502,7 @@ fn run_payload_delta_replay_audit_inner(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}
@@ -1605,6 +1647,7 @@ fn run_payload_delta_replay_step_audit_inner(
downloads,
download_stats,
current_repo_objects: Vec::new(),
ccr_accumulator: None,
})
}

View File

@@ -4,11 +4,12 @@ use crate::audit::{
ObjectAuditEntry, PublicationPointAudit,
};
use crate::audit_downloads::DownloadLogHandle;
use crate::ccr::CcrAccumulator;
use crate::current_repo_index::CurrentRepoIndexHandle;
use crate::data_model::aspa::AspaObject;
use crate::data_model::crl::RpkixCrl;
use crate::data_model::manifest::ManifestObject;
use crate::data_model::rc::ResourceCertificate;
use crate::data_model::rc::{AccessDescription, ResourceCertificate, SubjectInfoAccess};
use crate::data_model::roa::{RoaAfi, RoaObject};
use crate::data_model::router_cert::{
BgpsecRouterCertificate, BgpsecRouterCertificateDecodeError, BgpsecRouterCertificatePathError,
@@ -23,8 +24,9 @@ use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
use crate::report::{RfcRef, Warning};
use crate::storage::{
PackFile, PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult, VcirArtifactKind,
VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary, VcirChildEntry,
VcirInstanceGate, VcirLocalOutput, VcirOutputType, VcirRelatedArtifact, VcirSummary,
VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary, VcirCcrManifestProjection,
VcirChildEntry, VcirInstanceGate, VcirLocalOutput, VcirOutputType, VcirRelatedArtifact,
VcirSummary,
};
use crate::sync::repo::{
sync_publication_point, sync_publication_point_replay, sync_publication_point_replay_delta,
@@ -49,6 +51,7 @@ use crate::validation::publication_point::PublicationPointSnapshot;
use crate::validation::tree::{
CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner,
};
use sha2::Digest;
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
@@ -134,9 +137,47 @@ pub struct Rpkiv1PublicationPointRunner<'a> {
pub repo_sync_runtime: Option<Arc<dyn RepoSyncRuntime>>,
pub parallel_phase2_config: Option<ParallelPhase2Config>,
pub parallel_roa_worker_pool: Option<ParallelRoaWorkerPool>,
pub ccr_accumulator: Option<Mutex<CcrAccumulator>>,
}
impl<'a> Rpkiv1PublicationPointRunner<'a> {
pub(crate) fn ccr_accumulator_snapshot(&self) -> Option<CcrAccumulator> {
self.ccr_accumulator
.as_ref()
.and_then(|accumulator| accumulator.lock().ok().map(|guard| guard.clone()))
}
pub(crate) fn append_ccr_manifest_projection(
&self,
projection: &VcirCcrManifestProjection,
) -> Result<(), String> {
if let Some(accumulator) = self.ccr_accumulator.as_ref() {
accumulator
.lock()
.map_err(|_| "lock CCR accumulator failed".to_string())?
.append_manifest_projection(projection)?;
}
Ok(())
}
fn append_ccr_manifest_projection_from_reuse(
&self,
projection: &VcirReuseProjection,
) -> Result<(), String> {
match projection.source {
PublicationPointSource::Fresh => Err(
"invalid reuse projection source: fresh does not belong to failed-fetch reuse"
.to_string(),
),
PublicationPointSource::VcirCurrentInstance => self.append_ccr_manifest_projection(
projection.ccr_manifest_projection.as_ref().ok_or_else(|| {
"vcir current-instance reuse is missing CCR manifest projection".to_string()
})?,
),
PublicationPointSource::FailedFetchNoCache => Ok(()),
}
}
pub(crate) fn stage_fresh_publication_point_after_repo_ready(
&self,
ca: &CaInstanceHandle,
@@ -237,6 +278,14 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
.map_err(|e| format!("persist VCIR failed: {e}"))?;
let persist_vcir_ms = persist_vcir_started.elapsed().as_millis() as u64;
if self.ccr_accumulator.is_some() {
let child_entries =
build_vcir_child_entries(&discovered_children, self.validation_time)?;
let ccr_manifest_projection =
build_vcir_ccr_manifest_projection_from_fresh(ca, &pack, &child_entries)?;
self.append_ccr_manifest_projection(&ccr_manifest_projection)?;
}
let audit_build_started = std::time::Instant::now();
let audit = build_publication_point_audit_from_snapshot(
ca,
@@ -745,6 +794,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
self.validation_time,
)
.map_err(|e| format!("failed fetch VCIR projection failed: {e}"))?;
self.append_ccr_manifest_projection_from_reuse(&projection)?;
let projection_ms = projection_started.elapsed().as_millis() as u64;
warnings.extend(projection.warnings.clone());
let audit_build_started = std::time::Instant::now();
@@ -888,6 +938,7 @@ enum CachedIssuerCrl {
struct VcirReuseProjection {
source: PublicationPointSource,
vcir: Option<ValidatedCaInstanceResult>,
ccr_manifest_projection: Option<VcirCcrManifestProjection>,
snapshot: Option<PublicationPointSnapshot>,
objects: crate::validation::objects::ObjectsOutput,
child_audits: Vec<ObjectAuditEntry>,
@@ -1872,6 +1923,19 @@ fn empty_objects_output() -> crate::validation::objects::ObjectsOutput {
}
}
fn reuse_ccr_manifest_projection_from_vcir(
ca: &CaInstanceHandle,
vcir: &ValidatedCaInstanceResult,
) -> Result<VcirCcrManifestProjection, String> {
if vcir.ccr_manifest_projection.manifest_rsync_uri != ca.manifest_rsync_uri {
return Err(format!(
"vcir CCR manifest projection URI mismatch: expected {}, got {}",
ca.manifest_rsync_uri, vcir.ccr_manifest_projection.manifest_rsync_uri
));
}
Ok(vcir.ccr_manifest_projection.clone())
}
fn project_current_instance_vcir_on_failed_fetch(
store: &RocksStore,
ca: &CaInstanceHandle,
@@ -1896,6 +1960,7 @@ fn project_current_instance_vcir_on_failed_fetch(
return Ok(VcirReuseProjection {
source: PublicationPointSource::FailedFetchNoCache,
vcir: None,
ccr_manifest_projection: None,
snapshot: None,
objects: empty_objects_output(),
child_audits: Vec::new(),
@@ -1915,6 +1980,7 @@ fn project_current_instance_vcir_on_failed_fetch(
return Ok(VcirReuseProjection {
source: PublicationPointSource::FailedFetchNoCache,
vcir: Some(vcir),
ccr_manifest_projection: None,
snapshot: None,
objects: empty_objects_output(),
child_audits: Vec::new(),
@@ -1936,6 +2002,7 @@ fn project_current_instance_vcir_on_failed_fetch(
return Ok(VcirReuseProjection {
source: PublicationPointSource::FailedFetchNoCache,
vcir: Some(vcir),
ccr_manifest_projection: None,
snapshot: None,
objects: empty_objects_output(),
child_audits: Vec::new(),
@@ -1950,6 +2017,7 @@ fn project_current_instance_vcir_on_failed_fetch(
.with_context(&ca.manifest_rsync_uri),
);
let ccr_manifest_projection = reuse_ccr_manifest_projection_from_vcir(ca, &vcir)?;
let snapshot = reconstruct_snapshot_from_vcir(store, ca, &vcir, &mut warnings);
let objects = build_objects_output_from_vcir(&vcir, validation_time, &mut warnings);
let (discovered_children, child_audits) =
@@ -1958,6 +2026,7 @@ fn project_current_instance_vcir_on_failed_fetch(
Ok(VcirReuseProjection {
source: PublicationPointSource::VcirCurrentInstance,
vcir: Some(vcir),
ccr_manifest_projection: Some(ccr_manifest_projection),
snapshot,
objects,
child_audits,
@@ -2510,6 +2579,8 @@ fn build_vcir_from_fresh_result_with_timing(
child_audits,
);
timing.related_artifacts_ms = related_artifacts_started.elapsed().as_millis() as u64;
let ccr_manifest_projection =
build_vcir_ccr_manifest_projection_from_fresh(ca, pack, &child_entries)?;
let accepted_object_count = related_artifacts
.iter()
.filter(|artifact| artifact.validation_status == VcirArtifactValidationStatus::Accepted)
@@ -2552,6 +2623,7 @@ fn build_vcir_from_fresh_result_with_timing(
validated_manifest_this_update: pack.this_update.clone(),
validated_manifest_next_update: pack.next_update.clone(),
},
ccr_manifest_projection,
instance_gate: VcirInstanceGate {
manifest_next_update: pack.next_update.clone(),
current_crl_next_update: PackTime::from_utc_offset_datetime(
@@ -2598,6 +2670,138 @@ fn build_vcir_from_fresh_result_with_timing(
Ok((vcir, timing))
}
fn build_vcir_ccr_manifest_projection_from_fresh(
ca: &CaInstanceHandle,
pack: &PublicationPointSnapshot,
child_entries: &[VcirChildEntry],
) -> Result<VcirCcrManifestProjection, String> {
let manifest = ManifestObject::decode_der(&pack.manifest_bytes)
.map_err(|e| format!("decode manifest for VCIR CCR projection failed: {e}"))?;
let ee = &manifest.signed_object.signed_data.certificates[0].resource_cert;
let manifest_ee_aki = ee
.tbs
.extensions
.authority_key_identifier
.clone()
.ok_or_else(|| "manifest EE certificate missing AuthorityKeyIdentifier".to_string())?;
let manifest_sia_locations_der = match ee
.tbs
.extensions
.subject_info_access
.as_ref()
.ok_or_else(|| "manifest EE certificate missing Subject Information Access".to_string())?
{
SubjectInfoAccess::Ee(ee_sia) => ee_sia
.access_descriptions
.iter()
.map(encode_access_description_der_for_vcir_ccr_projection)
.collect::<Result<Vec<_>, _>>()?,
SubjectInfoAccess::Ca(_) => {
return Err(
"manifest EE certificate Subject Information Access has CA variant".to_string(),
);
}
};
let mut subordinate_skis = child_entries
.iter()
.map(|child| {
hex::decode(&child.child_ski)
.map_err(|e| format!("decode child_ski for VCIR CCR projection failed: {e}"))
})
.collect::<Result<Vec<_>, _>>()?;
subordinate_skis.sort();
subordinate_skis.dedup();
Ok(VcirCcrManifestProjection {
manifest_rsync_uri: ca.manifest_rsync_uri.clone(),
manifest_sha256: sha2::Sha256::digest(&pack.manifest_bytes).to_vec(),
manifest_size: pack.manifest_bytes.len() as u64,
manifest_ee_aki,
manifest_number_be: pack.manifest_number_be.clone(),
manifest_this_update: pack.this_update.clone(),
manifest_sia_locations_der,
subordinate_skis,
})
}
fn encode_access_description_der_for_vcir_ccr_projection(
access_description: &AccessDescription,
) -> Result<Vec<u8>, String> {
let oid = encode_oid_der_for_vcir_ccr_projection(&access_description.access_method_oid)?;
let uri = encode_tlv_for_vcir_ccr_projection(
0x86,
access_description.access_location.as_bytes().to_vec(),
);
Ok(encode_sequence_for_vcir_ccr_projection(&[oid, uri]))
}
fn encode_oid_der_for_vcir_ccr_projection(oid: &str) -> Result<Vec<u8>, String> {
let arcs = oid
.split('.')
.map(|part| {
part.parse::<u64>()
.map_err(|_| format!("unsupported accessMethod OID: {oid}"))
})
.collect::<Result<Vec<_>, _>>()?;
if arcs.len() < 2 {
return Err(format!("unsupported accessMethod OID: {oid}"));
}
if arcs[0] > 2 || (arcs[0] < 2 && arcs[1] >= 40) {
return Err(format!("unsupported accessMethod OID: {oid}"));
}
let mut body = Vec::new();
body.push((arcs[0] * 40 + arcs[1]) as u8);
for arc in &arcs[2..] {
encode_base128_for_vcir_ccr_projection(*arc, &mut body);
}
Ok(encode_tlv_for_vcir_ccr_projection(0x06, body))
}
fn encode_base128_for_vcir_ccr_projection(mut value: u64, out: &mut Vec<u8>) {
let mut tmp = vec![(value & 0x7F) as u8];
value >>= 7;
while value > 0 {
tmp.push(((value & 0x7F) as u8) | 0x80);
value >>= 7;
}
tmp.reverse();
out.extend_from_slice(&tmp);
}
fn encode_sequence_for_vcir_ccr_projection(elements: &[Vec<u8>]) -> Vec<u8> {
let total_len: usize = elements.iter().map(Vec::len).sum();
let mut buf = Vec::with_capacity(total_len);
for element in elements {
buf.extend_from_slice(element);
}
encode_tlv_for_vcir_ccr_projection(0x30, buf)
}
fn encode_tlv_for_vcir_ccr_projection(tag: u8, value: Vec<u8>) -> Vec<u8> {
let mut out = Vec::with_capacity(1 + 9 + value.len());
out.push(tag);
encode_length_for_vcir_ccr_projection(value.len(), &mut out);
out.extend_from_slice(&value);
out
}
fn encode_length_for_vcir_ccr_projection(len: usize, out: &mut Vec<u8>) {
if len < 0x80 {
out.push(len as u8);
return;
}
let mut bytes = Vec::new();
let mut value = len;
while value > 0 {
bytes.push((value & 0xFF) as u8);
value >>= 8;
}
bytes.reverse();
out.push(0x80 | (bytes.len() as u8));
out.extend_from_slice(&bytes);
}
struct CurrentCrlRef<'a> {
file: &'a PackFile,
crl: RpkixCrl,
@@ -3048,6 +3252,32 @@ mod tests {
}
}
fn sample_runner_with_ccr_accumulator<'a>(
store: &'a RocksStore,
policy: &'a Policy,
) -> Rpkiv1PublicationPointRunner<'a> {
Rpkiv1PublicationPointRunner {
store,
policy,
http_fetcher: &NeverHttpFetcher,
rsync_fetcher: &FailingRsyncFetcher,
validation_time: time::OffsetDateTime::now_utc(),
timing: None,
download_log: None,
replay_archive_index: None,
replay_delta_index: None,
rrdp_dedup: false,
rrdp_repo_cache: Mutex::new(HashMap::new()),
rsync_dedup: false,
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: Some(Mutex::new(CcrAccumulator::new(Vec::new()))),
}
}
fn openssl_available() -> bool {
Command::new("openssl")
.arg("version")
@@ -3509,6 +3739,19 @@ authorityKeyIdentifier = keyid:always
let router_hash = sha256_hex(b"router-bytes");
let ee_hash = sha256_hex(b"ee-cert-bytes");
let gate_until = PackTime::from_utc_offset_datetime(now + time::Duration::hours(1));
let ccr_manifest_projection = VcirCcrManifestProjection {
manifest_rsync_uri: manifest_uri.clone(),
manifest_sha256: hex::decode(&manifest_hash).expect("decode manifest hash"),
manifest_size: 2048,
manifest_ee_aki: vec![0x11; 20],
manifest_number_be: vec![1],
manifest_this_update: PackTime::from_utc_offset_datetime(now),
manifest_sia_locations_der: vec![vec![
0x30, 0x11, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x05, 0x86, 0x05,
b'r', b's', b'y', b'n', b'c',
]],
subordinate_skis: vec![vec![0x33; 20]],
};
ValidatedCaInstanceResult {
manifest_rsync_uri: manifest_uri.clone(),
parent_manifest_rsync_uri: None,
@@ -3524,6 +3767,7 @@ authorityKeyIdentifier = keyid:always
validated_manifest_this_update: PackTime::from_utc_offset_datetime(now),
validated_manifest_next_update: gate_until.clone(),
},
ccr_manifest_projection,
instance_gate: VcirInstanceGate {
manifest_next_update: gate_until.clone(),
current_crl_next_update: gate_until.clone(),
@@ -3901,6 +4145,22 @@ authorityKeyIdentifier = keyid:always
.expect("vcir exists");
assert_eq!(vcir.manifest_rsync_uri, pack.manifest_rsync_uri);
assert_eq!(vcir.summary.local_vrp_count as usize, objects.vrps.len());
assert_eq!(
vcir.ccr_manifest_projection.manifest_rsync_uri,
pack.manifest_rsync_uri
);
assert_eq!(
vcir.ccr_manifest_projection.manifest_number_be,
pack.manifest_number_be
);
assert_eq!(
vcir.ccr_manifest_projection.manifest_this_update,
pack.this_update
);
assert_eq!(
vcir.ccr_manifest_projection.manifest_size,
pack.manifest_bytes.len() as u64
);
let first_output = vcir.local_outputs.first().expect("local outputs stored");
assert!(store
.get_audit_rule_index_entry(crate::storage::AuditRuleKind::Roa, &first_output.rule_hash)
@@ -3908,6 +4168,81 @@ authorityKeyIdentifier = keyid:always
.is_some());
}
#[test]
fn build_vcir_ccr_manifest_projection_from_fresh_real_snapshot_matches_manifest_contents() {
let (pack, issuer_ca_der, validation_time) =
cernet_publication_point_snapshot_for_vcir_tests();
let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer ca");
let ca = CaInstanceHandle {
depth: 0,
tal_id: "test-tal".to_string(),
parent_manifest_rsync_uri: None,
ca_certificate_der: issuer_ca_der,
ca_certificate_rsync_uri: Some(
"rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer".to_string(),
),
effective_ip_resources: issuer_ca.tbs.extensions.ip_resources.clone(),
effective_as_resources: issuer_ca.tbs.extensions.as_resources.clone(),
rsync_base_uri: pack.publication_point_rsync_uri.clone(),
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
publication_point_rsync_uri: pack.publication_point_rsync_uri.clone(),
rrdp_notification_uri: None,
};
let child_discovery =
discover_children_from_fresh_snapshot_with_audit(&ca, &pack, validation_time, None)
.expect("discover children");
let child_entries = build_vcir_child_entries(&child_discovery.children, validation_time)
.expect("build child entries");
let projection = build_vcir_ccr_manifest_projection_from_fresh(&ca, &pack, &child_entries)
.expect("build ccr manifest projection");
let manifest = ManifestObject::decode_der(&pack.manifest_bytes).expect("decode manifest");
let expected_locations = match manifest.signed_object.signed_data.certificates[0]
.resource_cert
.tbs
.extensions
.subject_info_access
.as_ref()
.expect("manifest sia")
{
SubjectInfoAccess::Ee(ee_sia) => ee_sia
.access_descriptions
.iter()
.map(encode_access_description_der_for_vcir_ccr_projection)
.collect::<Result<Vec<_>, _>>()
.expect("encode locations"),
SubjectInfoAccess::Ca(_) => panic!("manifest ee SIA should not be CA variant"),
};
assert_eq!(projection.manifest_rsync_uri, pack.manifest_rsync_uri);
assert_eq!(
projection.manifest_sha256,
sha2::Sha256::digest(&pack.manifest_bytes).to_vec()
);
assert_eq!(projection.manifest_size, pack.manifest_bytes.len() as u64);
assert_eq!(
projection.manifest_ee_aki,
manifest.signed_object.signed_data.certificates[0]
.resource_cert
.tbs
.extensions
.authority_key_identifier
.clone()
.expect("manifest aki")
);
assert_eq!(
projection.manifest_number_be,
manifest.manifest.manifest_number.bytes_be
);
assert_eq!(projection.manifest_this_update, pack.this_update);
assert_eq!(projection.manifest_sia_locations_der, expected_locations);
let expected_subordinate_skis = child_entries
.iter()
.map(|child| hex::decode(&child.child_ski).expect("decode child ski"))
.collect::<Vec<_>>();
assert_eq!(projection.subordinate_skis, expected_subordinate_skis);
}
#[test]
fn build_vcir_related_artifacts_classifies_snapshot_files_and_audit_statuses() {
let manifest_bytes = std::fs::read(
@@ -4243,6 +4578,7 @@ authorityKeyIdentifier = keyid:always
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
// For this fixture-driven smoke, we provide the correct issuer CA certificate (the CA for
@@ -4404,6 +4740,7 @@ authorityKeyIdentifier = keyid:always
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let first = runner.run_publication_point(&handle).expect("first run ok");
@@ -4515,6 +4852,7 @@ authorityKeyIdentifier = keyid:always
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let first = runner.run_publication_point(&handle).expect("first run ok");
@@ -4629,6 +4967,7 @@ authorityKeyIdentifier = keyid:always
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let first = runner.run_publication_point(&handle).expect("first run ok");
@@ -4715,6 +5054,7 @@ authorityKeyIdentifier = keyid:always
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let first = ok_runner
.run_publication_point(&handle)
@@ -4744,6 +5084,7 @@ authorityKeyIdentifier = keyid:always
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let second = bad_runner
.run_publication_point(&handle)
@@ -5274,6 +5615,10 @@ authorityKeyIdentifier = keyid:always
projection.discovered_children[0].handle.manifest_rsync_uri,
"rsync://example.test/repo/child/child.mft"
);
assert_eq!(
projection.ccr_manifest_projection.as_ref(),
Some(&vcir.ccr_manifest_projection)
);
assert!(
projection.snapshot.is_some(),
"expected reconstructed snapshot"
@@ -5325,6 +5670,7 @@ authorityKeyIdentifier = keyid:always
projection.source,
PublicationPointSource::FailedFetchNoCache
);
assert!(projection.ccr_manifest_projection.is_none());
assert!(projection.objects.vrps.is_empty());
assert!(projection.objects.aspas.is_empty());
assert!(projection.discovered_children.is_empty());
@@ -5364,6 +5710,7 @@ authorityKeyIdentifier = keyid:always
PublicationPointSource::FailedFetchNoCache
);
assert!(projection.vcir.is_none());
assert!(projection.ccr_manifest_projection.is_none());
assert!(projection.snapshot.is_none());
assert!(projection.objects.audit.is_empty());
assert!(projection.discovered_children.is_empty());
@@ -5414,6 +5761,7 @@ authorityKeyIdentifier = keyid:always
PublicationPointSource::FailedFetchNoCache
);
assert!(projection.vcir.is_some());
assert!(projection.ccr_manifest_projection.is_none());
assert!(projection.snapshot.is_none());
assert!(projection.discovered_children.is_empty());
assert!(projection.warnings.iter().any(|warning| {
@@ -5423,6 +5771,144 @@ authorityKeyIdentifier = keyid:always
}));
}
#[test]
fn project_current_instance_vcir_rejects_mismatched_ccr_projection_uri() {
let now = time::OffsetDateTime::now_utc();
let child_cert_hash = sha256_hex(b"child-cert");
let mut vcir = sample_vcir_for_projection(now, &child_cert_hash);
vcir.ccr_manifest_projection.manifest_rsync_uri =
"rsync://example.test/repo/issuer/other.mft".to_string();
let store_dir = tempfile::tempdir().expect("store dir");
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
store.put_vcir(&vcir).expect("put vcir");
let ca = CaInstanceHandle {
depth: 0,
tal_id: "test-tal".to_string(),
parent_manifest_rsync_uri: None,
ca_certificate_der: Vec::new(),
ca_certificate_rsync_uri: None,
effective_ip_resources: None,
effective_as_resources: None,
rsync_base_uri: "rsync://example.test/repo/issuer/".to_string(),
manifest_rsync_uri: vcir.manifest_rsync_uri.clone(),
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
rrdp_notification_uri: None,
};
let err = project_current_instance_vcir_on_failed_fetch(
&store,
&ca,
&ManifestFreshError::RepoSyncFailed {
detail: "synthetic".to_string(),
},
now,
)
.unwrap_err();
assert!(
err.contains("vcir CCR manifest projection URI mismatch"),
"{err}"
);
}
#[test]
fn fresh_and_reuse_paths_produce_equivalent_ccr_manifest_projection() {
let (pack, issuer_ca_der, validation_time) =
cernet_publication_point_snapshot_for_vcir_tests();
let ca = CaInstanceHandle {
depth: 0,
tal_id: "test-tal".to_string(),
parent_manifest_rsync_uri: None,
ca_certificate_der: issuer_ca_der,
ca_certificate_rsync_uri: Some("rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer".to_string()),
effective_ip_resources: None,
effective_as_resources: None,
rsync_base_uri: pack.publication_point_rsync_uri.clone(),
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
publication_point_rsync_uri: pack.publication_point_rsync_uri.clone(),
rrdp_notification_uri: None,
};
let child_discovery =
discover_children_from_fresh_snapshot_with_audit(&ca, &pack, validation_time, None)
.expect("discover children");
let fresh_vcir = build_vcir_from_fresh_result(
&ca,
&pack,
&empty_objects_output(),
&[],
&child_discovery.audits,
&child_discovery.children,
validation_time,
)
.expect("build fresh vcir");
let reuse_projection = reuse_ccr_manifest_projection_from_vcir(&ca, &fresh_vcir)
.expect("reuse projection from vcir");
assert_eq!(fresh_vcir.ccr_manifest_projection, reuse_projection);
}
#[test]
fn append_ccr_manifest_projection_from_reuse_requires_projection_for_current_instance() {
let store_dir = tempfile::tempdir().expect("store dir");
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
let policy = Policy::default();
let runner = sample_runner_with_ccr_accumulator(&store, &policy);
let err = runner
.append_ccr_manifest_projection_from_reuse(&VcirReuseProjection {
source: PublicationPointSource::VcirCurrentInstance,
vcir: None,
ccr_manifest_projection: None,
snapshot: None,
objects: empty_objects_output(),
child_audits: Vec::new(),
discovered_children: Vec::new(),
warnings: Vec::new(),
})
.unwrap_err();
assert!(err.contains("missing CCR manifest projection"), "{err}");
assert_eq!(
runner
.ccr_accumulator_snapshot()
.expect("ccr accumulator snapshot")
.manifest_count(),
0
);
}
#[test]
fn append_ccr_manifest_projection_from_reuse_skips_failed_fetch_no_cache() {
let store_dir = tempfile::tempdir().expect("store dir");
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
let policy = Policy::default();
let runner = sample_runner_with_ccr_accumulator(&store, &policy);
runner
.append_ccr_manifest_projection_from_reuse(&VcirReuseProjection {
source: PublicationPointSource::FailedFetchNoCache,
vcir: None,
ccr_manifest_projection: None,
snapshot: None,
objects: empty_objects_output(),
child_audits: Vec::new(),
discovered_children: Vec::new(),
warnings: Vec::new(),
})
.expect("failed-fetch no-cache should not append");
assert_eq!(
runner
.ccr_accumulator_snapshot()
.expect("ccr accumulator snapshot")
.manifest_count(),
0
);
}
#[test]
fn parse_snapshot_time_value_reports_invalid_timestamp() {
let err = parse_snapshot_time_value(&PackTime {
@@ -5919,6 +6405,7 @@ authorityKeyIdentifier = keyid:always
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let first = runner_rrdp
.run_publication_point(&handle)
@@ -5951,6 +6438,7 @@ authorityKeyIdentifier = keyid:always
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: None,
};
let third = runner_rsync
.run_publication_point(&handle)