20260416_2 并行优化phase1后进行snapshot fast path优化,通过四方面关键优化技术消除了验证主链路上snapshot构建是的db访问性能热点问题,性能热点转移到ROA验证处理本身,目前APNIC+ARIN全量同步从500秒压缩到212秒,离rpkclient 112秒差距变小
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use crate::blob_store::RawObjectStore;
|
||||
use crate::current_repo_index::CurrentRepoIndexHandle;
|
||||
use crate::data_model::manifest::{ManifestDecodeError, ManifestObject, ManifestValidateError};
|
||||
use crate::data_model::signed_object::SignedObjectVerifyError;
|
||||
use crate::policy::{CaFailedFetchPolicy, Policy};
|
||||
@@ -299,11 +300,53 @@ pub fn process_manifest_publication_point_fresh_after_repo_sync(
|
||||
repo_sync_ok: bool,
|
||||
repo_sync_error: Option<&str>,
|
||||
) -> Result<FreshValidatedPublicationPoint, ManifestFreshError> {
|
||||
process_manifest_publication_point_fresh_after_repo_sync_with_timing(
|
||||
store,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
None,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
repo_sync_ok,
|
||||
repo_sync_error,
|
||||
)
|
||||
.map(|(fresh, _timing)| fresh)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct FreshPublicationPointTimingBreakdown {
|
||||
pub manifest_load_ms: u64,
|
||||
pub manifest_decode_ms: u64,
|
||||
pub replay_guard_ms: u64,
|
||||
pub manifest_entries_ms: u64,
|
||||
pub pack_files_ms: u64,
|
||||
pub ee_path_validate_ms: u64,
|
||||
}
|
||||
|
||||
pub fn process_manifest_publication_point_fresh_after_repo_sync_with_timing(
|
||||
store: &RocksStore,
|
||||
manifest_rsync_uri: &str,
|
||||
publication_point_rsync_uri: &str,
|
||||
current_repo_index: Option<&CurrentRepoIndexHandle>,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
repo_sync_ok: bool,
|
||||
repo_sync_error: Option<&str>,
|
||||
) -> Result<
|
||||
(
|
||||
FreshValidatedPublicationPoint,
|
||||
FreshPublicationPointTimingBreakdown,
|
||||
),
|
||||
ManifestFreshError,
|
||||
> {
|
||||
if repo_sync_ok {
|
||||
try_build_fresh_publication_point(
|
||||
try_build_fresh_publication_point_with_timing(
|
||||
store,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
current_repo_index,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
@@ -469,11 +512,11 @@ pub fn load_current_instance_vcir_publication_point(
|
||||
if !seen.insert(uri.clone()) {
|
||||
continue;
|
||||
}
|
||||
let entry_bytes = store
|
||||
.get_blob_bytes(&artifact.sha256)?
|
||||
.ok_or_else(|| ManifestReuseError::MissingArtifactRaw {
|
||||
let entry_bytes = store.get_blob_bytes(&artifact.sha256)?.ok_or_else(|| {
|
||||
ManifestReuseError::MissingArtifactRaw {
|
||||
rsync_uri: uri.clone(),
|
||||
})?;
|
||||
}
|
||||
})?;
|
||||
files.push(PackFile::from_bytes_compute_sha256(uri, entry_bytes));
|
||||
}
|
||||
|
||||
@@ -541,6 +584,36 @@ pub(crate) fn try_build_fresh_publication_point(
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<FreshValidatedPublicationPoint, ManifestFreshError> {
|
||||
try_build_fresh_publication_point_with_timing(
|
||||
store,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
None,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
)
|
||||
.map(|(fresh, _timing)| fresh)
|
||||
}
|
||||
|
||||
pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
store: &RocksStore,
|
||||
manifest_rsync_uri: &str,
|
||||
publication_point_rsync_uri: &str,
|
||||
current_repo_index: Option<&CurrentRepoIndexHandle>,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<
|
||||
(
|
||||
FreshValidatedPublicationPoint,
|
||||
FreshPublicationPointTimingBreakdown,
|
||||
),
|
||||
ManifestFreshError,
|
||||
> {
|
||||
let mut timing = FreshPublicationPointTimingBreakdown::default();
|
||||
let current_index_guard = current_repo_index.and_then(|handle| handle.lock().ok());
|
||||
|
||||
if !rsync_uri_is_under_publication_point(manifest_rsync_uri, publication_point_rsync_uri) {
|
||||
return Err(ManifestFreshError::ManifestOutsidePublicationPoint {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
@@ -548,17 +621,37 @@ pub(crate) fn try_build_fresh_publication_point(
|
||||
});
|
||||
}
|
||||
|
||||
let manifest_bytes = store
|
||||
.load_current_object_bytes_by_uri(manifest_rsync_uri)
|
||||
.map_err(|e| ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: format!("{manifest_rsync_uri} ({e})"),
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
let manifest_load_started = std::time::Instant::now();
|
||||
let manifest_bytes = if let Some(index) = current_index_guard.as_ref() {
|
||||
let current = index.get_by_uri(manifest_rsync_uri).ok_or_else(|| {
|
||||
ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
}
|
||||
})?;
|
||||
store
|
||||
.get_blob_bytes(¤t.current_hash_hex)
|
||||
.map_err(|e| ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: format!("{manifest_rsync_uri} ({e})"),
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
})?
|
||||
} else {
|
||||
store
|
||||
.load_current_object_bytes_by_uri(manifest_rsync_uri)
|
||||
.map_err(|e| ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: format!("{manifest_rsync_uri} ({e})"),
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
})?
|
||||
};
|
||||
timing.manifest_load_ms = manifest_load_started.elapsed().as_millis() as u64;
|
||||
|
||||
let manifest_decode_started = std::time::Instant::now();
|
||||
let manifest =
|
||||
decode_and_validate_manifest_with_current_time(&manifest_bytes, validation_time)?;
|
||||
timing.manifest_decode_ms = manifest_decode_started.elapsed().as_millis() as u64;
|
||||
|
||||
let this_update = manifest
|
||||
.manifest
|
||||
@@ -582,6 +675,7 @@ pub(crate) fn try_build_fresh_publication_point(
|
||||
// - If manifestNumber is lower, treat as rollback and reject.
|
||||
// - If manifestNumber is higher, require thisUpdate to be more recent than the previously
|
||||
// validated thisUpdate.
|
||||
let replay_guard_started = std::time::Instant::now();
|
||||
if let Some(old_vcir) = store.get_vcir(manifest_rsync_uri).ok().flatten() {
|
||||
if old_vcir.manifest_rsync_uri == manifest_rsync_uri {
|
||||
let new_num = manifest.manifest.manifest_number.bytes_be.as_slice();
|
||||
@@ -633,34 +727,79 @@ pub(crate) fn try_build_fresh_publication_point(
|
||||
}
|
||||
}
|
||||
}
|
||||
timing.replay_guard_ms = replay_guard_started.elapsed().as_millis() as u64;
|
||||
|
||||
let manifest_entries_started = std::time::Instant::now();
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.map_err(ManifestDecodeError::Validate)?;
|
||||
timing.manifest_entries_ms = manifest_entries_started.elapsed().as_millis() as u64;
|
||||
let mut files = Vec::with_capacity(manifest.manifest.file_count());
|
||||
let pack_files_started = std::time::Instant::now();
|
||||
let external_raw_store = store.external_raw_store_ref().cloned().map(std::sync::Arc::new);
|
||||
for entry in &entries {
|
||||
let rsync_uri =
|
||||
join_rsync_dir_and_file(publication_point_rsync_uri, entry.file_name.as_str());
|
||||
let bytes = store
|
||||
.load_current_object_bytes_by_uri(&rsync_uri)
|
||||
.map_err(|_e| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})?;
|
||||
let current_object = if let Some(index) = current_index_guard.as_ref() {
|
||||
let current =
|
||||
index
|
||||
.get_by_uri(&rsync_uri)
|
||||
.ok_or_else(|| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})?;
|
||||
crate::storage::CurrentObjectWithHash {
|
||||
current_hash_hex: current.current_hash_hex.clone(),
|
||||
current_hash: current.current_hash,
|
||||
bytes: Vec::new(),
|
||||
}
|
||||
} else {
|
||||
store
|
||||
.load_current_object_with_hash_by_uri(&rsync_uri)
|
||||
.map_err(|_e| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})?
|
||||
};
|
||||
|
||||
let computed = sha2::Sha256::digest(&bytes);
|
||||
if computed.as_slice() != entry.hash_bytes.as_ref() {
|
||||
if current_object.current_hash != entry.hash_bytes {
|
||||
return Err(ManifestFreshError::HashMismatch { rsync_uri });
|
||||
}
|
||||
|
||||
files.push(PackFile::from_bytes_compute_sha256(rsync_uri, bytes));
|
||||
if let (Some(_), Some(raw_store)) = (current_index_guard.as_ref(), external_raw_store.as_ref()) {
|
||||
files.push(PackFile::from_lazy_external_raw_store(
|
||||
rsync_uri,
|
||||
current_object.current_hash_hex,
|
||||
current_object.current_hash,
|
||||
raw_store.clone(),
|
||||
));
|
||||
} else {
|
||||
let bytes = if current_object.bytes.is_empty() {
|
||||
store
|
||||
.get_blob_bytes(¤t_object.current_hash_hex)
|
||||
.map_err(|_e| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})?
|
||||
} else {
|
||||
current_object.bytes
|
||||
};
|
||||
files.push(PackFile::from_bytes_with_sha256(
|
||||
rsync_uri,
|
||||
bytes,
|
||||
current_object.current_hash,
|
||||
));
|
||||
}
|
||||
}
|
||||
timing.pack_files_ms = pack_files_started.elapsed().as_millis() as u64;
|
||||
|
||||
// RFC 6488 §3: manifest (signed object) validity includes a valid EE cert path.
|
||||
// We validate this after §6.4/§6.5 so the issuer CRL can be selected from the publication point.
|
||||
let ee_path_validate_started = std::time::Instant::now();
|
||||
validate_manifest_embedded_ee_cert_path(
|
||||
&manifest,
|
||||
&files,
|
||||
@@ -668,17 +807,21 @@ pub(crate) fn try_build_fresh_publication_point(
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
)?;
|
||||
timing.ee_path_validate_ms = ee_path_validate_started.elapsed().as_millis() as u64;
|
||||
|
||||
Ok(FreshValidatedPublicationPoint {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
|
||||
manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(),
|
||||
this_update: PackTime::from_utc_offset_datetime(this_update),
|
||||
next_update: PackTime::from_utc_offset_datetime(next_update),
|
||||
verified_at: PackTime::from_utc_offset_datetime(now),
|
||||
manifest_bytes,
|
||||
files,
|
||||
})
|
||||
Ok((
|
||||
FreshValidatedPublicationPoint {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
|
||||
manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(),
|
||||
this_update: PackTime::from_utc_offset_datetime(this_update),
|
||||
next_update: PackTime::from_utc_offset_datetime(next_update),
|
||||
verified_at: PackTime::from_utc_offset_datetime(now),
|
||||
manifest_bytes,
|
||||
files,
|
||||
},
|
||||
timing,
|
||||
))
|
||||
}
|
||||
|
||||
fn cmp_minimal_be_unsigned(a: &[u8], b: &[u8]) -> Ordering {
|
||||
@@ -735,10 +878,13 @@ fn validate_manifest_embedded_ee_cert_path(
|
||||
for u in crldp_uris {
|
||||
let s = u.as_str();
|
||||
if let Some(f) = crl_files.iter().find(|f| f.rsync_uri == s) {
|
||||
let crl_bytes = f
|
||||
.bytes()
|
||||
.map_err(|e| ManifestFreshError::MissingFile { rsync_uri: format!("{s} ({e})") })?;
|
||||
let _validated = validate_ee_cert_path(
|
||||
ee_der,
|
||||
issuer_ca_der,
|
||||
f.bytes.as_slice(),
|
||||
crl_bytes,
|
||||
issuer_ca_rsync_uri,
|
||||
Some(f.rsync_uri.as_str()),
|
||||
validation_time,
|
||||
@@ -759,6 +905,7 @@ fn validate_manifest_embedded_ee_cert_path(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::current_repo_index::CurrentRepoIndex;
|
||||
use crate::data_model::manifest::ManifestObject;
|
||||
use crate::storage::{
|
||||
PackFile, PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult,
|
||||
@@ -853,6 +1000,12 @@ mod tests {
|
||||
.expect("put repository view entry");
|
||||
}
|
||||
|
||||
fn put_raw_only(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
|
||||
store
|
||||
.put_raw_by_hash_entry(&raw_by_hash_entry(rsync_uri, bytes, object_type))
|
||||
.expect("put raw_by_hash entry");
|
||||
}
|
||||
|
||||
fn sample_current_instance_vcir(
|
||||
manifest_rsync_uri: &str,
|
||||
publication_point_rsync_uri: &str,
|
||||
@@ -1034,6 +1187,132 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_build_fresh_publication_point_detects_hash_mismatch_via_repository_view_hash() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
let (
|
||||
manifest,
|
||||
manifest_bytes,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
validation_time,
|
||||
) = load_manifest_fixture();
|
||||
put_current_object(&store, &manifest_rsync_uri, manifest_bytes, "mft");
|
||||
|
||||
let non_crl_entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse files")
|
||||
.into_iter()
|
||||
.filter(|entry| !entry.file_name.ends_with(".crl"))
|
||||
.collect::<Vec<_>>();
|
||||
let first = &non_crl_entries[0];
|
||||
let second = &non_crl_entries[1];
|
||||
|
||||
let first_uri = format!("{publication_point_rsync_uri}{}", first.file_name);
|
||||
let second_path = manifest_fixture_path()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(second.file_name.as_str());
|
||||
let wrong_bytes = std::fs::read(&second_path).expect("read wrong fixture file");
|
||||
let object_type = first_uri.rsplit('.').next().unwrap_or("bin");
|
||||
put_current_object(&store, &first_uri, wrong_bytes, object_type);
|
||||
|
||||
for entry in non_crl_entries.iter().skip(1) {
|
||||
let file_path = manifest_fixture_path()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path).expect("read fixture file");
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin");
|
||||
put_current_object(&store, &rsync_uri, bytes, object_type);
|
||||
}
|
||||
|
||||
let err = try_build_fresh_publication_point(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_fixture_der(),
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, ManifestFreshError::HashMismatch { .. }),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_build_fresh_publication_point_uses_current_repo_index_without_repository_view() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
let (
|
||||
manifest,
|
||||
manifest_bytes,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
validation_time,
|
||||
) = load_manifest_fixture();
|
||||
|
||||
put_raw_only(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
|
||||
let current_index = CurrentRepoIndex::shared();
|
||||
let mut entries = vec![crate::storage::RepositoryViewEntry {
|
||||
rsync_uri: manifest_rsync_uri.clone(),
|
||||
current_hash: Some(hex::encode(sha2::Sha256::digest(&manifest_bytes))),
|
||||
repository_source: Some("https://example.test/notification.xml".to_string()),
|
||||
object_type: Some("mft".to_string()),
|
||||
state: crate::storage::RepositoryViewState::Present,
|
||||
}];
|
||||
|
||||
for entry in manifest.manifest.parse_files().expect("parse files") {
|
||||
let file_path = manifest_fixture_path()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path).expect("read fixture file");
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin").to_string();
|
||||
put_raw_only(&store, &rsync_uri, bytes.clone(), &object_type);
|
||||
entries.push(crate::storage::RepositoryViewEntry {
|
||||
rsync_uri,
|
||||
current_hash: Some(hex::encode(sha2::Sha256::digest(&bytes))),
|
||||
repository_source: Some("https://example.test/notification.xml".to_string()),
|
||||
object_type: Some(object_type),
|
||||
state: crate::storage::RepositoryViewState::Present,
|
||||
});
|
||||
}
|
||||
|
||||
current_index
|
||||
.lock()
|
||||
.expect("index lock")
|
||||
.apply_repository_view_entries(&entries)
|
||||
.expect("apply current index");
|
||||
|
||||
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,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
Some(¤t_index),
|
||||
&issuer_ca_fixture_der(),
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("fresh publication point via current index");
|
||||
|
||||
assert_eq!(fresh.manifest_rsync_uri, manifest_rsync_uri);
|
||||
assert_eq!(fresh.files.len(), manifest.manifest.file_count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_manifest_embedded_ee_cert_path_rejects_missing_crl_files() {
|
||||
let (manifest, _, _, publication_point_rsync_uri, validation_time) =
|
||||
@@ -1139,7 +1418,7 @@ mod tests {
|
||||
assert_eq!(point.manifest_bytes, manifest_bytes);
|
||||
assert_eq!(point.files.len(), 1);
|
||||
assert_eq!(point.files[0].rsync_uri, locked_uri);
|
||||
assert_eq!(point.files[0].bytes, locked_bytes);
|
||||
assert_eq!(point.files[0].bytes_cloned().expect("locked bytes"), locked_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -218,9 +218,12 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
.iter()
|
||||
.filter(|f| f.rsync_uri.ends_with(".crl"))
|
||||
.map(|f| {
|
||||
let bytes = f
|
||||
.bytes_cloned()
|
||||
.expect("snapshot CRL bytes must be loadable");
|
||||
(
|
||||
f.rsync_uri.clone(),
|
||||
CachedIssuerCrl::Pending(f.bytes.clone()),
|
||||
CachedIssuerCrl::Pending(bytes),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@@ -518,6 +521,9 @@ pub fn process_publication_point_snapshot_for_issuer(
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ObjectValidateError {
|
||||
#[error("object bytes load failed: {0}")]
|
||||
BytesLoad(String),
|
||||
|
||||
#[error("ROA decode failed: {0}")]
|
||||
RoaDecode(#[from] RoaDecodeError),
|
||||
|
||||
@@ -584,7 +590,7 @@ fn process_roa_with_issuer(
|
||||
let _decode = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
||||
let roa = RoaObject::decode_der(&file.bytes)?;
|
||||
let roa = RoaObject::decode_der(file.bytes().map_err(ObjectValidateError::BytesLoad)?)?;
|
||||
drop(_decode);
|
||||
|
||||
let _ee_profile = timing
|
||||
@@ -692,7 +698,7 @@ fn process_aspa_with_issuer(
|
||||
let _decode = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_aspa_decode_and_validate_total"));
|
||||
let aspa = AspaObject::decode_der(&file.bytes)?;
|
||||
let aspa = AspaObject::decode_der(file.bytes().map_err(ObjectValidateError::BytesLoad)?)?;
|
||||
drop(_decode);
|
||||
|
||||
let _ee_profile = timing
|
||||
|
||||
@@ -73,6 +73,7 @@ pub fn run_publication_point_once(
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use url::Url;
|
||||
use crate::analysis::timing::TimingHandle;
|
||||
use crate::audit::PublicationPointAudit;
|
||||
use crate::audit_downloads::DownloadLogHandle;
|
||||
use crate::current_repo_index::{CurrentRepoIndexHandle, CurrentRepoObject};
|
||||
use crate::data_model::ta::TrustAnchor;
|
||||
use crate::parallel::config::ParallelPhase1Config;
|
||||
use crate::parallel::repo_runtime::{Phase1RepoSyncRuntime, RepoSyncRuntime};
|
||||
@@ -84,6 +85,7 @@ pub struct RunTreeFromTalAuditOutput {
|
||||
pub publication_points: Vec<PublicationPointAudit>,
|
||||
pub downloads: Vec<crate::audit::AuditDownloadEvent>,
|
||||
pub download_stats: crate::audit::AuditDownloadStats,
|
||||
pub current_repo_objects: Vec<CurrentRepoObject>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -93,6 +95,14 @@ pub struct TalRootDiscovery {
|
||||
pub root_handle: CaInstanceHandle,
|
||||
}
|
||||
|
||||
fn snapshot_current_repo_objects(
|
||||
current_repo_index: Option<&CurrentRepoIndexHandle>,
|
||||
) -> Vec<CurrentRepoObject> {
|
||||
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,
|
||||
@@ -101,6 +111,7 @@ fn make_live_runner<'a>(
|
||||
validation_time: time::OffsetDateTime,
|
||||
timing: Option<TimingHandle>,
|
||||
download_log: Option<DownloadLogHandle>,
|
||||
current_repo_index: Option<CurrentRepoIndexHandle>,
|
||||
repo_sync_runtime: Option<Arc<dyn RepoSyncRuntime>>,
|
||||
) -> Rpkiv1PublicationPointRunner<'a> {
|
||||
Rpkiv1PublicationPointRunner {
|
||||
@@ -117,6 +128,7 @@ fn make_live_runner<'a>(
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: true,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
current_repo_index,
|
||||
repo_sync_runtime,
|
||||
}
|
||||
}
|
||||
@@ -130,15 +142,17 @@ fn build_phase1_repo_sync_runtime<H, R>(
|
||||
timing: Option<TimingHandle>,
|
||||
download_log: Option<DownloadLogHandle>,
|
||||
tal_inputs: Vec<crate::parallel::types::TalInputSpec>,
|
||||
) -> Result<Arc<dyn RepoSyncRuntime>, RunTreeFromTalError>
|
||||
) -> Result<(Arc<dyn RepoSyncRuntime>, CurrentRepoIndexHandle), RunTreeFromTalError>
|
||||
where
|
||||
H: Fetcher + Clone + 'static,
|
||||
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
||||
{
|
||||
let coordinator = GlobalRunCoordinator::new(parallel_config.clone(), tal_inputs);
|
||||
let current_repo_index = coordinator.current_repo_index_handle();
|
||||
let rsync_fetcher_arc = Arc::new(rsync_fetcher.clone());
|
||||
let executor = LiveRepoTransportExecutor::new(
|
||||
Arc::clone(&store),
|
||||
current_repo_index.clone(),
|
||||
Arc::new(http_fetcher.clone()),
|
||||
Arc::clone(&rsync_fetcher_arc),
|
||||
timing,
|
||||
@@ -149,12 +163,13 @@ where
|
||||
let resolver: Arc<dyn Fn(&str) -> String + Send + Sync> =
|
||||
Arc::new(move |base: &str| rsync_fetcher_arc.dedup_key(base));
|
||||
let _ = policy; // policy reserved for later runtime-level decisions
|
||||
Ok(Arc::new(Phase1RepoSyncRuntime::new(
|
||||
let runtime = Arc::new(Phase1RepoSyncRuntime::new(
|
||||
coordinator,
|
||||
pool,
|
||||
resolver,
|
||||
policy.sync_preference,
|
||||
)))
|
||||
));
|
||||
Ok((runtime, current_repo_index))
|
||||
}
|
||||
|
||||
fn root_discovery_from_tal_input(
|
||||
@@ -171,21 +186,16 @@ fn root_discovery_from_tal_input(
|
||||
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)?;
|
||||
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()
|
||||
))
|
||||
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()
|
||||
))
|
||||
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)
|
||||
}
|
||||
@@ -269,6 +279,7 @@ pub fn run_tree_from_tal_url_serial(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let root = root_handle_from_trust_anchor(
|
||||
@@ -303,6 +314,7 @@ pub fn run_tree_from_tal_url_serial_audit(
|
||||
None,
|
||||
Some(download_log.clone()),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let root = root_handle_from_trust_anchor(
|
||||
@@ -325,6 +337,7 @@ pub fn run_tree_from_tal_url_serial_audit(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -352,6 +365,7 @@ pub fn run_tree_from_tal_url_serial_audit_with_timing(
|
||||
Some(timing.clone()),
|
||||
Some(download_log.clone()),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let root = root_handle_from_trust_anchor(
|
||||
@@ -375,6 +389,7 @@ pub fn run_tree_from_tal_url_serial_audit_with_timing(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -394,7 +409,7 @@ where
|
||||
{
|
||||
let discovery = discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)?;
|
||||
let download_log = DownloadLogHandle::new();
|
||||
let runtime = build_phase1_repo_sync_runtime(
|
||||
let (runtime, current_repo_index) = build_phase1_repo_sync_runtime(
|
||||
Arc::clone(&store),
|
||||
policy,
|
||||
http_fetcher,
|
||||
@@ -404,6 +419,7 @@ where
|
||||
Some(download_log.clone()),
|
||||
vec![TalInputSpec::from_url(tal_url.to_string())],
|
||||
)?;
|
||||
let current_repo_index_for_output = current_repo_index.clone();
|
||||
let runner = make_live_runner(
|
||||
store.as_ref(),
|
||||
policy,
|
||||
@@ -412,6 +428,7 @@ where
|
||||
validation_time,
|
||||
None,
|
||||
Some(download_log.clone()),
|
||||
Some(current_repo_index),
|
||||
Some(runtime),
|
||||
);
|
||||
|
||||
@@ -434,6 +451,7 @@ where
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: snapshot_current_repo_objects(Some(¤t_repo_index_for_output)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -453,7 +471,8 @@ 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 discovery =
|
||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
||||
let download_log = DownloadLogHandle::new();
|
||||
let derived_tal_id = derive_tal_id(&discovery);
|
||||
let tal_inputs = vec![TalInputSpec {
|
||||
@@ -468,7 +487,7 @@ where
|
||||
ta_der: ta_der.to_vec(),
|
||||
},
|
||||
}];
|
||||
let runtime = build_phase1_repo_sync_runtime(
|
||||
let (runtime, current_repo_index) = build_phase1_repo_sync_runtime(
|
||||
Arc::clone(&store),
|
||||
policy,
|
||||
http_fetcher,
|
||||
@@ -478,6 +497,7 @@ where
|
||||
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,
|
||||
@@ -486,6 +506,7 @@ where
|
||||
validation_time,
|
||||
None,
|
||||
Some(download_log.clone()),
|
||||
Some(current_repo_index),
|
||||
Some(runtime),
|
||||
);
|
||||
|
||||
@@ -508,6 +529,7 @@ where
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: snapshot_current_repo_objects(Some(¤t_repo_index_for_output)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -526,21 +548,25 @@ where
|
||||
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
||||
{
|
||||
if tal_inputs.is_empty() {
|
||||
return Err(RunTreeFromTalError::Replay("multi-TAL run requires at least one TAL input".to_string()));
|
||||
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::<Vec<_>>();
|
||||
let primary = roots.first().cloned().ok_or_else(|| {
|
||||
RunTreeFromTalError::Replay("multi-TAL root discovery returned no roots".to_string())
|
||||
})?;
|
||||
let discoveries = roots
|
||||
.iter()
|
||||
.map(|item| item.discovery.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let root_handles = roots
|
||||
.into_iter()
|
||||
.map(|item| item.root_handle)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let download_log = DownloadLogHandle::new();
|
||||
let runtime = build_phase1_repo_sync_runtime(
|
||||
let (runtime, current_repo_index) = build_phase1_repo_sync_runtime(
|
||||
Arc::clone(&store),
|
||||
policy,
|
||||
http_fetcher,
|
||||
@@ -550,6 +576,7 @@ where
|
||||
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,
|
||||
@@ -558,6 +585,7 @@ where
|
||||
validation_time,
|
||||
None,
|
||||
Some(download_log.clone()),
|
||||
Some(current_repo_index),
|
||||
Some(runtime),
|
||||
);
|
||||
|
||||
@@ -574,6 +602,7 @@ where
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: snapshot_current_repo_objects(Some(¤t_repo_index_for_output)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -605,6 +634,7 @@ pub fn run_tree_from_tal_and_ta_der_serial(
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -652,6 +682,7 @@ pub fn run_tree_from_tal_bytes_serial_audit(
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -675,6 +706,7 @@ pub fn run_tree_from_tal_bytes_serial_audit(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -714,6 +746,7 @@ pub fn run_tree_from_tal_bytes_serial_audit_with_timing(
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -739,6 +772,7 @@ pub fn run_tree_from_tal_bytes_serial_audit_with_timing(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -771,6 +805,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit(
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -794,6 +829,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -829,6 +865,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit_with_timing(
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -853,6 +890,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit_with_timing(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -894,6 +932,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial(
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -947,6 +986,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -970,6 +1010,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1015,6 +1056,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -1039,6 +1081,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1066,6 +1109,7 @@ fn build_payload_replay_runner<'a>(
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -1094,6 +1138,7 @@ fn build_payload_delta_replay_runner<'a>(
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -1122,6 +1167,7 @@ fn build_payload_delta_replay_current_store_runner<'a>(
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -1238,6 +1284,7 @@ fn run_payload_delta_replay_audit_inner(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1381,6 +1428,7 @@ fn run_payload_delta_replay_step_audit_inner(
|
||||
publication_points,
|
||||
downloads,
|
||||
download_stats,
|
||||
current_repo_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1442,23 +1490,15 @@ mod multi_tal_tests {
|
||||
|
||||
#[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_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,
|
||||
),
|
||||
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(
|
||||
@@ -1631,16 +1671,14 @@ mod replay_api_tests {
|
||||
let store = crate::storage::RocksStore::open(&temp.path().join("db")).expect("open db");
|
||||
let (tal_bytes, ta_der, archive_root, locks_path, validation_time) =
|
||||
apnic_multi_rir_replay_inputs();
|
||||
assert!(
|
||||
archive_root.is_dir(),
|
||||
"payload replay archive missing: {}",
|
||||
archive_root.display()
|
||||
);
|
||||
assert!(
|
||||
locks_path.is_file(),
|
||||
"payload replay locks missing: {}",
|
||||
locks_path.display()
|
||||
);
|
||||
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,
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::audit::{
|
||||
sha256_hex, sha256_hex_from_32,
|
||||
};
|
||||
use crate::audit_downloads::DownloadLogHandle;
|
||||
use crate::current_repo_index::CurrentRepoIndexHandle;
|
||||
use crate::data_model::aspa::AspaObject;
|
||||
use crate::data_model::crl::RpkixCrl;
|
||||
use crate::data_model::manifest::ManifestObject;
|
||||
@@ -14,8 +15,8 @@ use crate::data_model::router_cert::{
|
||||
BgpsecRouterCertificateProfileError,
|
||||
};
|
||||
use crate::fetch::rsync::RsyncFetcher;
|
||||
use crate::policy::Policy;
|
||||
use crate::parallel::repo_runtime::{RepoSyncRuntime, RepoSyncRuntimeOutcome};
|
||||
use crate::policy::Policy;
|
||||
use crate::replay::archive::ReplayArchiveIndex;
|
||||
use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
|
||||
use crate::report::{RfcRef, Warning};
|
||||
@@ -35,7 +36,7 @@ use crate::validation::ca_path::{
|
||||
};
|
||||
use crate::validation::manifest::{
|
||||
ManifestFreshError, PublicationPointData, PublicationPointSource,
|
||||
process_manifest_publication_point_fresh_after_repo_sync,
|
||||
process_manifest_publication_point_fresh_after_repo_sync_with_timing,
|
||||
};
|
||||
use crate::validation::objects::{
|
||||
AspaAttestation, RouterKeyPayload, Vrp, process_publication_point_for_issuer,
|
||||
@@ -53,6 +54,26 @@ use serde_json::json;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct BuildVcirTimingBreakdown {
|
||||
select_crl_ms: u64,
|
||||
current_ca_decode_ms: u64,
|
||||
local_outputs_ms: u64,
|
||||
child_entries_ms: u64,
|
||||
related_artifacts_ms: u64,
|
||||
struct_build_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct PersistVcirTimingBreakdown {
|
||||
embedded_collect_ms: u64,
|
||||
embedded_store_ms: u64,
|
||||
build_vcir_ms: u64,
|
||||
previous_load_ms: u64,
|
||||
replace_vcir_ms: u64,
|
||||
build_vcir: BuildVcirTimingBreakdown,
|
||||
}
|
||||
|
||||
pub struct Rpkiv1PublicationPointRunner<'a> {
|
||||
pub store: &'a RocksStore,
|
||||
pub policy: &'a Policy,
|
||||
@@ -78,6 +99,7 @@ pub struct Rpkiv1PublicationPointRunner<'a> {
|
||||
/// same `rsync_base_uri` (observed in APNIC full sync timing reports).
|
||||
pub rsync_dedup: bool,
|
||||
pub rsync_repo_cache: Mutex<HashMap<String, bool>>, // rsync_base_uri -> rsync_ok
|
||||
pub current_repo_index: Option<CurrentRepoIndexHandle>,
|
||||
pub repo_sync_runtime: Option<Arc<dyn RepoSyncRuntime>>,
|
||||
}
|
||||
|
||||
@@ -196,7 +218,12 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
warnings: repo_warnings,
|
||||
} = runtime.sync_publication_point_repo(ca)?;
|
||||
warnings.extend(repo_warnings);
|
||||
(repo_sync_ok, repo_sync_err, repo_sync_source, repo_sync_phase)
|
||||
(
|
||||
repo_sync_ok,
|
||||
repo_sync_err,
|
||||
repo_sync_source,
|
||||
repo_sync_phase,
|
||||
)
|
||||
} else if skip_sync_due_to_dedup {
|
||||
let source = if effective_notification_uri.is_some() {
|
||||
Some("rrdp_dedup_skip".to_string())
|
||||
@@ -332,15 +359,17 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
}),
|
||||
);
|
||||
|
||||
let snapshot_prepare_started = std::time::Instant::now();
|
||||
let fresh_publication_point = {
|
||||
let _manifest_total = self
|
||||
.timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("manifest_processing_total"));
|
||||
process_manifest_publication_point_fresh_after_repo_sync(
|
||||
process_manifest_publication_point_fresh_after_repo_sync_with_timing(
|
||||
self.store,
|
||||
&ca.manifest_rsync_uri,
|
||||
&ca.publication_point_rsync_uri,
|
||||
self.current_repo_index.as_ref(),
|
||||
&ca.ca_certificate_der,
|
||||
ca.ca_certificate_rsync_uri.as_deref(),
|
||||
self.validation_time,
|
||||
@@ -348,9 +377,11 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
repo_sync_err.as_deref(),
|
||||
)
|
||||
};
|
||||
let snapshot_prepare_ms = snapshot_prepare_started.elapsed().as_millis() as u64;
|
||||
|
||||
match fresh_publication_point {
|
||||
Ok(fresh_point) => {
|
||||
Ok((fresh_point, snapshot_prepare_timing)) => {
|
||||
let objects_processing_started = std::time::Instant::now();
|
||||
let mut objects = {
|
||||
let _objects_total = self
|
||||
.timing
|
||||
@@ -367,7 +398,9 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
self.timing.as_ref(),
|
||||
)
|
||||
};
|
||||
let objects_processing_ms = objects_processing_started.elapsed().as_millis() as u64;
|
||||
|
||||
let child_discovery_started = std::time::Instant::now();
|
||||
let out = {
|
||||
let _child_disc_total = self
|
||||
.timing
|
||||
@@ -391,15 +424,19 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
(Vec::new(), Vec::new(), Vec::new())
|
||||
}
|
||||
};
|
||||
let child_discovery_ms = child_discovery_started.elapsed().as_millis() as u64;
|
||||
|
||||
objects.router_keys.extend(discovered_router_keys);
|
||||
objects
|
||||
.local_outputs_cache
|
||||
.extend(build_router_key_local_outputs(ca, &objects.router_keys));
|
||||
|
||||
let snapshot_pack_started = std::time::Instant::now();
|
||||
let pack = fresh_point.to_publication_point_snapshot();
|
||||
let snapshot_pack_ms = snapshot_pack_started.elapsed().as_millis() as u64;
|
||||
|
||||
persist_vcir_for_fresh_result(
|
||||
let persist_vcir_started = std::time::Instant::now();
|
||||
let persist_vcir_timing = persist_vcir_for_fresh_result_with_timing(
|
||||
self.store,
|
||||
ca,
|
||||
&pack,
|
||||
@@ -410,7 +447,9 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
self.validation_time,
|
||||
)
|
||||
.map_err(|e| format!("persist VCIR failed: {e}"))?;
|
||||
let persist_vcir_ms = persist_vcir_started.elapsed().as_millis() as u64;
|
||||
|
||||
let audit_build_started = std::time::Instant::now();
|
||||
let audit = build_publication_point_audit_from_snapshot(
|
||||
ca,
|
||||
PublicationPointSource::Fresh,
|
||||
@@ -423,6 +462,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
&objects,
|
||||
&child_audits,
|
||||
);
|
||||
let audit_build_ms = audit_build_started.elapsed().as_millis() as u64;
|
||||
let result = PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
snapshot: Some(pack),
|
||||
@@ -442,6 +482,30 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
"repo_sync_phase": repo_sync_phase,
|
||||
"repo_sync_duration_ms": repo_sync_duration_ms,
|
||||
"total_duration_ms": total_duration_ms,
|
||||
"post_repo_duration_ms": total_duration_ms.saturating_sub(repo_sync_duration_ms),
|
||||
"snapshot_prepare_ms": snapshot_prepare_ms,
|
||||
"snapshot_manifest_load_ms": snapshot_prepare_timing.manifest_load_ms,
|
||||
"snapshot_manifest_decode_ms": snapshot_prepare_timing.manifest_decode_ms,
|
||||
"snapshot_replay_guard_ms": snapshot_prepare_timing.replay_guard_ms,
|
||||
"snapshot_manifest_entries_ms": snapshot_prepare_timing.manifest_entries_ms,
|
||||
"snapshot_pack_files_ms": snapshot_prepare_timing.pack_files_ms,
|
||||
"snapshot_ee_path_validate_ms": snapshot_prepare_timing.ee_path_validate_ms,
|
||||
"objects_processing_ms": objects_processing_ms,
|
||||
"child_discovery_ms": child_discovery_ms,
|
||||
"snapshot_pack_ms": snapshot_pack_ms,
|
||||
"persist_vcir_ms": persist_vcir_ms,
|
||||
"persist_embedded_collect_ms": persist_vcir_timing.embedded_collect_ms,
|
||||
"persist_embedded_store_ms": persist_vcir_timing.embedded_store_ms,
|
||||
"persist_build_vcir_ms": persist_vcir_timing.build_vcir_ms,
|
||||
"persist_previous_load_ms": persist_vcir_timing.previous_load_ms,
|
||||
"persist_replace_vcir_ms": persist_vcir_timing.replace_vcir_ms,
|
||||
"persist_select_crl_ms": persist_vcir_timing.build_vcir.select_crl_ms,
|
||||
"persist_current_ca_decode_ms": persist_vcir_timing.build_vcir.current_ca_decode_ms,
|
||||
"persist_local_outputs_ms": persist_vcir_timing.build_vcir.local_outputs_ms,
|
||||
"persist_child_entries_ms": persist_vcir_timing.build_vcir.child_entries_ms,
|
||||
"persist_related_artifacts_ms": persist_vcir_timing.build_vcir.related_artifacts_ms,
|
||||
"persist_vcir_struct_ms": persist_vcir_timing.build_vcir.struct_build_ms,
|
||||
"audit_build_ms": audit_build_ms,
|
||||
"warning_count": result.warnings.len(),
|
||||
"vrp_count": result.objects.vrps.len(),
|
||||
"vap_count": result.objects.aspas.len(),
|
||||
@@ -461,6 +525,30 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
"repo_sync_phase": repo_sync_phase,
|
||||
"repo_sync_duration_ms": repo_sync_duration_ms,
|
||||
"total_duration_ms": total_duration_ms,
|
||||
"post_repo_duration_ms": total_duration_ms.saturating_sub(repo_sync_duration_ms),
|
||||
"snapshot_prepare_ms": snapshot_prepare_ms,
|
||||
"snapshot_manifest_load_ms": snapshot_prepare_timing.manifest_load_ms,
|
||||
"snapshot_manifest_decode_ms": snapshot_prepare_timing.manifest_decode_ms,
|
||||
"snapshot_replay_guard_ms": snapshot_prepare_timing.replay_guard_ms,
|
||||
"snapshot_manifest_entries_ms": snapshot_prepare_timing.manifest_entries_ms,
|
||||
"snapshot_pack_files_ms": snapshot_prepare_timing.pack_files_ms,
|
||||
"snapshot_ee_path_validate_ms": snapshot_prepare_timing.ee_path_validate_ms,
|
||||
"objects_processing_ms": objects_processing_ms,
|
||||
"child_discovery_ms": child_discovery_ms,
|
||||
"snapshot_pack_ms": snapshot_pack_ms,
|
||||
"persist_vcir_ms": persist_vcir_ms,
|
||||
"persist_embedded_collect_ms": persist_vcir_timing.embedded_collect_ms,
|
||||
"persist_embedded_store_ms": persist_vcir_timing.embedded_store_ms,
|
||||
"persist_build_vcir_ms": persist_vcir_timing.build_vcir_ms,
|
||||
"persist_previous_load_ms": persist_vcir_timing.previous_load_ms,
|
||||
"persist_replace_vcir_ms": persist_vcir_timing.replace_vcir_ms,
|
||||
"persist_select_crl_ms": persist_vcir_timing.build_vcir.select_crl_ms,
|
||||
"persist_current_ca_decode_ms": persist_vcir_timing.build_vcir.current_ca_decode_ms,
|
||||
"persist_local_outputs_ms": persist_vcir_timing.build_vcir.local_outputs_ms,
|
||||
"persist_child_entries_ms": persist_vcir_timing.build_vcir.child_entries_ms,
|
||||
"persist_related_artifacts_ms": persist_vcir_timing.build_vcir.related_artifacts_ms,
|
||||
"persist_vcir_struct_ms": persist_vcir_timing.build_vcir.struct_build_ms,
|
||||
"audit_build_ms": audit_build_ms,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -479,6 +567,10 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
"repo_sync_phase": repo_sync_phase,
|
||||
"repo_sync_duration_ms": repo_sync_duration_ms,
|
||||
"total_duration_ms": total_duration_ms,
|
||||
"post_repo_duration_ms": total_duration_ms.saturating_sub(repo_sync_duration_ms),
|
||||
"snapshot_prepare_ms": snapshot_prepare_ms,
|
||||
"projection_ms": 0,
|
||||
"audit_build_ms": 0,
|
||||
"error": fresh_err.to_string(),
|
||||
}),
|
||||
);
|
||||
@@ -498,6 +590,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
Err(format!("{fresh_err}"))
|
||||
}
|
||||
crate::policy::CaFailedFetchPolicy::ReuseCurrentInstanceVcir => {
|
||||
let projection_started = std::time::Instant::now();
|
||||
let projection = project_current_instance_vcir_on_failed_fetch(
|
||||
self.store,
|
||||
ca,
|
||||
@@ -505,7 +598,9 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
self.validation_time,
|
||||
)
|
||||
.map_err(|e| format!("failed fetch VCIR projection failed: {e}"))?;
|
||||
let projection_ms = projection_started.elapsed().as_millis() as u64;
|
||||
warnings.extend(projection.warnings.clone());
|
||||
let audit_build_started = std::time::Instant::now();
|
||||
let audit = build_publication_point_audit_from_vcir(
|
||||
ca,
|
||||
projection.source,
|
||||
@@ -519,6 +614,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
&projection.objects,
|
||||
&projection.child_audits,
|
||||
);
|
||||
let audit_build_ms = audit_build_started.elapsed().as_millis() as u64;
|
||||
let result = PublicationPointRunResult {
|
||||
source: projection.source,
|
||||
snapshot: projection.snapshot,
|
||||
@@ -538,6 +634,10 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
"repo_sync_phase": repo_sync_phase,
|
||||
"repo_sync_duration_ms": repo_sync_duration_ms,
|
||||
"total_duration_ms": total_duration_ms,
|
||||
"post_repo_duration_ms": total_duration_ms.saturating_sub(repo_sync_duration_ms),
|
||||
"snapshot_prepare_ms": snapshot_prepare_ms,
|
||||
"projection_ms": projection_ms,
|
||||
"audit_build_ms": audit_build_ms,
|
||||
"warning_count": result.warnings.len(),
|
||||
"vrp_count": result.objects.vrps.len(),
|
||||
"vap_count": result.objects.aspas.len(),
|
||||
@@ -604,6 +704,10 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
"repo_sync_phase": repo_sync_phase,
|
||||
"repo_sync_duration_ms": repo_sync_duration_ms,
|
||||
"total_duration_ms": total_duration_ms,
|
||||
"post_repo_duration_ms": total_duration_ms.saturating_sub(repo_sync_duration_ms),
|
||||
"snapshot_prepare_ms": snapshot_prepare_ms,
|
||||
"projection_ms": projection_ms,
|
||||
"audit_build_ms": audit_build_ms,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -715,13 +819,16 @@ fn discover_children_from_fresh_snapshot_with_audit<P: PublicationPointData>(
|
||||
let mut crl_cache: std::collections::HashMap<String, CachedIssuerCrl> = locked_files
|
||||
.iter()
|
||||
.filter(|f| f.rsync_uri.ends_with(".crl"))
|
||||
.map(|f| {
|
||||
(
|
||||
.map(|f| -> Result<(String, CachedIssuerCrl), String> {
|
||||
let bytes = f
|
||||
.bytes_cloned()
|
||||
.map_err(|e| format!("snapshot CRL bytes load failed: {e}"))?;
|
||||
Ok((
|
||||
f.rsync_uri.clone(),
|
||||
CachedIssuerCrl::Pending(f.bytes.clone()),
|
||||
)
|
||||
CachedIssuerCrl::Pending(bytes),
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
.collect::<Result<_, String>>()?;
|
||||
|
||||
let mut out: Vec<DiscoveredChildCaInstance> = Vec::new();
|
||||
let mut audits: Vec<ObjectAuditEntry> = Vec::new();
|
||||
@@ -798,7 +905,9 @@ fn discover_children_from_fresh_snapshot_with_audit<P: PublicationPointData>(
|
||||
continue;
|
||||
}
|
||||
cer_seen = cer_seen.saturating_add(1);
|
||||
let child_der = f.bytes.as_slice();
|
||||
let child_der = f
|
||||
.bytes()
|
||||
.map_err(|e| format!("child certificate bytes load failed: {e}"))?;
|
||||
|
||||
let tdecode = std::time::Instant::now();
|
||||
let child_cert = match crate::data_model::rc::ResourceCertificate::decode_der(child_der) {
|
||||
@@ -1232,7 +1341,10 @@ fn select_issuer_crl_from_snapshot<'a>(
|
||||
for u in crldp_uris {
|
||||
let s = u.as_str();
|
||||
if let Some(f) = pack.files.iter().find(|f| f.rsync_uri == s) {
|
||||
return Ok((f.rsync_uri.as_str(), f.bytes.as_slice()));
|
||||
let bytes = f
|
||||
.bytes()
|
||||
.map_err(|e| format!("snapshot CRL bytes load failed: {e}"))?;
|
||||
return Ok((f.rsync_uri.as_str(), bytes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1285,7 +1397,8 @@ fn repo_sync_failure_phase_label(
|
||||
original_notification_uri: Option<&str>,
|
||||
effective_notification_uri: Option<&str>,
|
||||
) -> &'static str {
|
||||
if attempted_rrdp && original_notification_uri.is_some() && effective_notification_uri.is_some() {
|
||||
if attempted_rrdp && original_notification_uri.is_some() && effective_notification_uri.is_some()
|
||||
{
|
||||
"rrdp_failed_rsync_failed"
|
||||
} else if attempted_rrdp
|
||||
&& original_notification_uri.is_some()
|
||||
@@ -1367,7 +1480,7 @@ fn build_publication_point_audit_from_snapshot(
|
||||
if !f.rsync_uri.ends_with(".crl") {
|
||||
continue;
|
||||
}
|
||||
let ok = RpkixCrl::decode_der(&f.bytes).is_ok();
|
||||
let ok = f.bytes().ok().and_then(|bytes| RpkixCrl::decode_der(bytes).ok()).is_some();
|
||||
audit_by_uri.insert(
|
||||
f.rsync_uri.clone(),
|
||||
ObjectAuditEntry {
|
||||
@@ -1567,15 +1680,15 @@ fn build_publication_point_audit_from_vcir(
|
||||
manifest_rsync_uri: ca.manifest_rsync_uri.clone(),
|
||||
publication_point_rsync_uri: ca.publication_point_rsync_uri.clone(),
|
||||
rrdp_notification_uri: ca.rrdp_notification_uri.clone(),
|
||||
source: source_label(source),
|
||||
repo_sync_source: repo_sync_source.map(ToString::to_string),
|
||||
repo_sync_phase: repo_sync_phase.map(ToString::to_string),
|
||||
repo_sync_duration_ms,
|
||||
repo_sync_error: repo_sync_error.map(ToString::to_string),
|
||||
repo_terminal_state: terminal_state_label(source).to_string(),
|
||||
this_update_rfc3339_utc: vcir
|
||||
.validated_manifest_meta
|
||||
.validated_manifest_this_update
|
||||
source: source_label(source),
|
||||
repo_sync_source: repo_sync_source.map(ToString::to_string),
|
||||
repo_sync_phase: repo_sync_phase.map(ToString::to_string),
|
||||
repo_sync_duration_ms,
|
||||
repo_sync_error: repo_sync_error.map(ToString::to_string),
|
||||
repo_terminal_state: terminal_state_label(source).to_string(),
|
||||
this_update_rfc3339_utc: vcir
|
||||
.validated_manifest_meta
|
||||
.validated_manifest_this_update
|
||||
.rfc3339_utc
|
||||
.clone(),
|
||||
next_update_rfc3339_utc: vcir
|
||||
@@ -2129,15 +2242,42 @@ fn persist_vcir_for_fresh_result(
|
||||
discovered_children: &[DiscoveredChildCaInstance],
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<(), String> {
|
||||
if objects.stats.publication_point_dropped {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let embedded_evidence = collect_vcir_embedded_evidence(pack, objects)?;
|
||||
persist_vcir_non_repository_evidence(store, ca, &embedded_evidence)
|
||||
.map_err(|e| format!("store VCIR audit evidence failed: {e}"))?;
|
||||
|
||||
let vcir = build_vcir_from_fresh_result(
|
||||
persist_vcir_for_fresh_result_with_timing(
|
||||
store,
|
||||
ca,
|
||||
pack,
|
||||
objects,
|
||||
warnings,
|
||||
child_audits,
|
||||
discovered_children,
|
||||
validation_time,
|
||||
)
|
||||
.map(|_timing| ())
|
||||
}
|
||||
|
||||
fn persist_vcir_for_fresh_result_with_timing(
|
||||
store: &RocksStore,
|
||||
ca: &CaInstanceHandle,
|
||||
pack: &PublicationPointSnapshot,
|
||||
objects: &crate::validation::objects::ObjectsOutput,
|
||||
warnings: &[Warning],
|
||||
child_audits: &[ObjectAuditEntry],
|
||||
discovered_children: &[DiscoveredChildCaInstance],
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<PersistVcirTimingBreakdown, String> {
|
||||
let mut timing = PersistVcirTimingBreakdown::default();
|
||||
|
||||
if objects.stats.publication_point_dropped {
|
||||
return Ok(timing);
|
||||
}
|
||||
|
||||
let embedded_store_started = std::time::Instant::now();
|
||||
persist_vcir_non_repository_evidence(store, ca)
|
||||
.map_err(|e| format!("store VCIR audit evidence failed: {e}"))?;
|
||||
timing.embedded_store_ms = embedded_store_started.elapsed().as_millis() as u64;
|
||||
|
||||
let build_vcir_started = std::time::Instant::now();
|
||||
let (vcir, build_vcir_timing) = build_vcir_from_fresh_result_with_timing(
|
||||
ca,
|
||||
pack,
|
||||
objects,
|
||||
@@ -2145,17 +2285,23 @@ fn persist_vcir_for_fresh_result(
|
||||
child_audits,
|
||||
discovered_children,
|
||||
validation_time,
|
||||
&embedded_evidence,
|
||||
)?;
|
||||
timing.build_vcir_ms = build_vcir_started.elapsed().as_millis() as u64;
|
||||
timing.build_vcir = build_vcir_timing;
|
||||
|
||||
let previous_load_started = std::time::Instant::now();
|
||||
let previous = store
|
||||
.get_vcir(&pack.manifest_rsync_uri)
|
||||
.map_err(|e| format!("load existing VCIR failed: {e}"))?;
|
||||
timing.previous_load_ms = previous_load_started.elapsed().as_millis() as u64;
|
||||
|
||||
let replace_vcir_started = std::time::Instant::now();
|
||||
store
|
||||
.replace_vcir_and_audit_rule_indexes(previous.as_ref(), &vcir)
|
||||
.map_err(|e| format!("store VCIR and audit rule index failed: {e}"))?;
|
||||
timing.replace_vcir_ms = replace_vcir_started.elapsed().as_millis() as u64;
|
||||
|
||||
Ok(())
|
||||
Ok(timing)
|
||||
}
|
||||
|
||||
fn build_vcir_from_fresh_result(
|
||||
@@ -2166,21 +2312,56 @@ fn build_vcir_from_fresh_result(
|
||||
child_audits: &[ObjectAuditEntry],
|
||||
discovered_children: &[DiscoveredChildCaInstance],
|
||||
validation_time: time::OffsetDateTime,
|
||||
embedded_evidence: &[VcirEmbeddedEvidence],
|
||||
) -> Result<ValidatedCaInstanceResult, String> {
|
||||
build_vcir_from_fresh_result_with_timing(
|
||||
ca,
|
||||
pack,
|
||||
objects,
|
||||
warnings,
|
||||
child_audits,
|
||||
discovered_children,
|
||||
validation_time,
|
||||
)
|
||||
.map(|(vcir, _timing)| vcir)
|
||||
}
|
||||
|
||||
fn build_vcir_from_fresh_result_with_timing(
|
||||
ca: &CaInstanceHandle,
|
||||
pack: &PublicationPointSnapshot,
|
||||
objects: &crate::validation::objects::ObjectsOutput,
|
||||
warnings: &[Warning],
|
||||
child_audits: &[ObjectAuditEntry],
|
||||
discovered_children: &[DiscoveredChildCaInstance],
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<(ValidatedCaInstanceResult, BuildVcirTimingBreakdown), String> {
|
||||
let mut timing = BuildVcirTimingBreakdown::default();
|
||||
|
||||
let select_crl_started = std::time::Instant::now();
|
||||
let current_crl = select_manifest_current_crl_from_snapshot(pack)?;
|
||||
timing.select_crl_ms = select_crl_started.elapsed().as_millis() as u64;
|
||||
|
||||
let current_ca_decode_started = std::time::Instant::now();
|
||||
let ca_cert = ResourceCertificate::decode_der(&ca.ca_certificate_der)
|
||||
.map_err(|e| format!("decode current CA certificate failed: {e}"))?;
|
||||
timing.current_ca_decode_ms = current_ca_decode_started.elapsed().as_millis() as u64;
|
||||
|
||||
let local_outputs_started = std::time::Instant::now();
|
||||
let local_outputs = build_vcir_local_outputs(ca, pack, objects)?;
|
||||
timing.local_outputs_ms = local_outputs_started.elapsed().as_millis() as u64;
|
||||
|
||||
let child_entries_started = std::time::Instant::now();
|
||||
let child_entries = build_vcir_child_entries(discovered_children, validation_time)?;
|
||||
timing.child_entries_ms = child_entries_started.elapsed().as_millis() as u64;
|
||||
|
||||
let related_artifacts_started = std::time::Instant::now();
|
||||
let related_artifacts = build_vcir_related_artifacts(
|
||||
ca,
|
||||
pack,
|
||||
current_crl.file.rsync_uri.as_str(),
|
||||
objects,
|
||||
child_audits,
|
||||
embedded_evidence,
|
||||
);
|
||||
timing.related_artifacts_ms = related_artifacts_started.elapsed().as_millis() as u64;
|
||||
let accepted_object_count = related_artifacts
|
||||
.iter()
|
||||
.filter(|artifact| artifact.validation_status == VcirArtifactValidationStatus::Accepted)
|
||||
@@ -2207,6 +2388,7 @@ fn build_vcir_from_fresh_result(
|
||||
.ok_or_else(|| "current CA certificate missing AuthorityKeyIdentifier".to_string())?,
|
||||
);
|
||||
|
||||
let struct_build_started = std::time::Instant::now();
|
||||
let vcir = ValidatedCaInstanceResult {
|
||||
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
|
||||
parent_manifest_rsync_uri: ca.parent_manifest_rsync_uri.clone(),
|
||||
@@ -2264,7 +2446,8 @@ fn build_vcir_from_fresh_result(
|
||||
},
|
||||
};
|
||||
vcir.validate_internal().map_err(|e| e.to_string())?;
|
||||
Ok(vcir)
|
||||
timing.struct_build_ms = struct_build_started.elapsed().as_millis() as u64;
|
||||
Ok((vcir, timing))
|
||||
}
|
||||
|
||||
struct CurrentCrlRef<'a> {
|
||||
@@ -2290,7 +2473,10 @@ fn select_manifest_current_crl_from_snapshot(
|
||||
.iter()
|
||||
.find(|candidate| candidate.rsync_uri == *uri)
|
||||
{
|
||||
let crl = RpkixCrl::decode_der(&file.bytes)
|
||||
let crl = RpkixCrl::decode_der(
|
||||
file.bytes()
|
||||
.map_err(|e| format!("load current CRL bytes for VCIR failed: {e}"))?,
|
||||
)
|
||||
.map_err(|e| format!("decode current CRL for VCIR failed: {e}"))?;
|
||||
return Ok(CurrentCrlRef { file, crl });
|
||||
}
|
||||
@@ -2329,7 +2515,10 @@ fn build_vcir_local_outputs(
|
||||
for file in &pack.files {
|
||||
let source_object_hash = sha256_hex_from_32(&file.sha256);
|
||||
if accepted_roa_uris.contains(file.rsync_uri.as_str()) {
|
||||
let roa = RoaObject::decode_der(&file.bytes)
|
||||
let roa = RoaObject::decode_der(
|
||||
file.bytes()
|
||||
.map_err(|e| format!("load accepted ROA bytes for VCIR failed: {e}"))?,
|
||||
)
|
||||
.map_err(|e| format!("decode accepted ROA for VCIR failed: {e}"))?;
|
||||
let ee = &roa.signed_object.signed_data.certificates[0];
|
||||
let source_ee_cert_hash = sha256_hex(ee.raw_der.as_slice());
|
||||
@@ -2368,7 +2557,10 @@ fn build_vcir_local_outputs(
|
||||
});
|
||||
}
|
||||
} else if accepted_aspa_uris.contains(file.rsync_uri.as_str()) {
|
||||
let aspa = AspaObject::decode_der(&file.bytes)
|
||||
let aspa = AspaObject::decode_der(
|
||||
file.bytes()
|
||||
.map_err(|e| format!("load accepted ASPA bytes for VCIR failed: {e}"))?,
|
||||
)
|
||||
.map_err(|e| format!("decode accepted ASPA for VCIR failed: {e}"))?;
|
||||
let ee = &aspa.signed_object.signed_data.certificates[0];
|
||||
let source_ee_cert_hash = sha256_hex(ee.raw_der.as_slice());
|
||||
@@ -2489,98 +2681,9 @@ fn build_vcir_child_entries(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct VcirEmbeddedEvidence {
|
||||
artifact: VcirRelatedArtifact,
|
||||
raw_entry: RawByHashEntry,
|
||||
}
|
||||
|
||||
fn collect_vcir_embedded_evidence(
|
||||
pack: &PublicationPointSnapshot,
|
||||
objects: &crate::validation::objects::ObjectsOutput,
|
||||
) -> Result<Vec<VcirEmbeddedEvidence>, String> {
|
||||
let mut evidence = Vec::new();
|
||||
let mut seen_hashes = HashSet::new();
|
||||
|
||||
let manifest = ManifestObject::decode_der(&pack.manifest_bytes)
|
||||
.map_err(|e| format!("decode manifest for VCIR embedded evidence failed: {e}"))?;
|
||||
if let Some(ee) = manifest.signed_object.signed_data.certificates.first() {
|
||||
let ee_hash = sha256_hex(ee.raw_der.as_slice());
|
||||
if seen_hashes.insert(ee_hash.clone()) {
|
||||
evidence.push(VcirEmbeddedEvidence {
|
||||
artifact: VcirRelatedArtifact {
|
||||
artifact_role: VcirArtifactRole::EeCert,
|
||||
artifact_kind: VcirArtifactKind::Cer,
|
||||
uri: None,
|
||||
sha256: ee_hash.clone(),
|
||||
object_type: Some("cer".to_string()),
|
||||
validation_status: VcirArtifactValidationStatus::Accepted,
|
||||
},
|
||||
raw_entry: embedded_raw_entry(ee_hash, ee.raw_der.to_vec()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let accepted_uris: HashSet<&str> = objects
|
||||
.audit
|
||||
.iter()
|
||||
.filter(|entry| matches!(entry.result, AuditObjectResult::Ok))
|
||||
.map(|entry| entry.rsync_uri.as_str())
|
||||
.collect();
|
||||
|
||||
for file in &pack.files {
|
||||
let Some(kind) = signed_object_kind_from_uri(file.rsync_uri.as_str()) else {
|
||||
continue;
|
||||
};
|
||||
if !accepted_uris.contains(file.rsync_uri.as_str()) {
|
||||
continue;
|
||||
}
|
||||
let ee_der = match kind {
|
||||
VcirArtifactKind::Roa => RoaObject::decode_der(&file.bytes)
|
||||
.map_err(|e| format!("decode accepted ROA for VCIR embedded evidence failed: {e}"))?
|
||||
.signed_object
|
||||
.signed_data
|
||||
.certificates
|
||||
.first()
|
||||
.map(|cert| cert.raw_der.to_vec()),
|
||||
VcirArtifactKind::Aspa => AspaObject::decode_der(&file.bytes)
|
||||
.map_err(|e| {
|
||||
format!("decode accepted ASPA for VCIR embedded evidence failed: {e}")
|
||||
})?
|
||||
.signed_object
|
||||
.signed_data
|
||||
.certificates
|
||||
.first()
|
||||
.map(|cert| cert.raw_der.to_vec()),
|
||||
_ => None,
|
||||
};
|
||||
let Some(ee_der) = ee_der else {
|
||||
continue;
|
||||
};
|
||||
let ee_hash = sha256_hex(ee_der.as_slice());
|
||||
if !seen_hashes.insert(ee_hash.clone()) {
|
||||
continue;
|
||||
}
|
||||
evidence.push(VcirEmbeddedEvidence {
|
||||
artifact: VcirRelatedArtifact {
|
||||
artifact_role: VcirArtifactRole::EeCert,
|
||||
artifact_kind: VcirArtifactKind::Cer,
|
||||
uri: None,
|
||||
sha256: ee_hash.clone(),
|
||||
object_type: Some("cer".to_string()),
|
||||
validation_status: VcirArtifactValidationStatus::Accepted,
|
||||
},
|
||||
raw_entry: embedded_raw_entry(ee_hash, ee_der),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(evidence)
|
||||
}
|
||||
|
||||
fn persist_vcir_non_repository_evidence(
|
||||
store: &RocksStore,
|
||||
ca: &CaInstanceHandle,
|
||||
embedded_evidence: &[VcirEmbeddedEvidence],
|
||||
) -> Result<(), String> {
|
||||
let current_ca_hash = sha256_hex(&ca.ca_certificate_der);
|
||||
let mut current_ca_entry =
|
||||
@@ -2591,10 +2694,6 @@ fn persist_vcir_non_repository_evidence(
|
||||
current_ca_entry.object_type = Some("cer".to_string());
|
||||
current_ca_entry.encoding = Some("der".to_string());
|
||||
upsert_raw_by_hash_entry(store, current_ca_entry)?;
|
||||
|
||||
for evidence in embedded_evidence {
|
||||
upsert_raw_by_hash_entry(store, evidence.raw_entry.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2641,32 +2740,12 @@ fn upsert_raw_by_hash_entry(store: &RocksStore, entry: RawByHashEntry) -> Result
|
||||
}
|
||||
}
|
||||
|
||||
fn embedded_raw_entry(sha256_hex: String, bytes: Vec<u8>) -> RawByHashEntry {
|
||||
let mut entry = RawByHashEntry::from_bytes(sha256_hex, bytes);
|
||||
entry.object_type = Some("cer".to_string());
|
||||
entry.encoding = Some("der".to_string());
|
||||
entry
|
||||
}
|
||||
|
||||
fn signed_object_kind_from_uri(uri: &str) -> Option<VcirArtifactKind> {
|
||||
if uri.ends_with(".roa") {
|
||||
Some(VcirArtifactKind::Roa)
|
||||
} else if uri.ends_with(".asa") {
|
||||
Some(VcirArtifactKind::Aspa)
|
||||
} else if uri.ends_with(".gbr") {
|
||||
Some(VcirArtifactKind::Gbr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn build_vcir_related_artifacts(
|
||||
ca: &CaInstanceHandle,
|
||||
pack: &PublicationPointSnapshot,
|
||||
current_crl_rsync_uri: &str,
|
||||
objects: &crate::validation::objects::ObjectsOutput,
|
||||
child_audits: &[ObjectAuditEntry],
|
||||
embedded_evidence: &[VcirEmbeddedEvidence],
|
||||
) -> Vec<VcirRelatedArtifact> {
|
||||
let mut audit_by_uri: HashMap<&str, AuditObjectResult> = HashMap::new();
|
||||
for entry in child_audits.iter().chain(objects.audit.iter()) {
|
||||
@@ -2711,11 +2790,6 @@ fn build_vcir_related_artifacts(
|
||||
});
|
||||
}
|
||||
|
||||
artifacts.extend(
|
||||
embedded_evidence
|
||||
.iter()
|
||||
.map(|evidence| evidence.artifact.clone()),
|
||||
);
|
||||
artifacts
|
||||
}
|
||||
|
||||
@@ -3498,7 +3572,7 @@ authorityKeyIdentifier = keyid:always
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_and_persist_vcir_embedded_evidence_for_real_signed_objects() {
|
||||
fn persist_vcir_non_repository_evidence_stores_current_ca_cert_only() {
|
||||
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");
|
||||
@@ -3519,13 +3593,6 @@ authorityKeyIdentifier = keyid:always
|
||||
"expected local outputs from signed objects"
|
||||
);
|
||||
|
||||
let evidence =
|
||||
collect_vcir_embedded_evidence(&pack, &objects).expect("collect embedded evidence");
|
||||
assert!(
|
||||
evidence.len() >= 2,
|
||||
"expected manifest EE and signed-object EE evidence"
|
||||
);
|
||||
|
||||
let store_dir = tempfile::tempdir().expect("store dir");
|
||||
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
||||
let ca = CaInstanceHandle {
|
||||
@@ -3543,7 +3610,7 @@ authorityKeyIdentifier = keyid:always
|
||||
publication_point_rsync_uri: pack.publication_point_rsync_uri.clone(),
|
||||
rrdp_notification_uri: None,
|
||||
};
|
||||
persist_vcir_non_repository_evidence(&store, &ca, &evidence)
|
||||
persist_vcir_non_repository_evidence(&store, &ca)
|
||||
.expect("persist embedded evidence");
|
||||
|
||||
let issuer_hash = sha256_hex(&issuer_ca_der);
|
||||
@@ -3557,14 +3624,16 @@ authorityKeyIdentifier = keyid:always
|
||||
.iter()
|
||||
.any(|uri| uri.ends_with("BfycW4hQb3wNP4YsiJW-1n6fjro.cer"))
|
||||
);
|
||||
for entry in &evidence {
|
||||
assert!(
|
||||
store
|
||||
.get_raw_by_hash_entry(&entry.raw_entry.sha256_hex)
|
||||
.expect("load evidence raw entry")
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
let first_output = objects
|
||||
.local_outputs_cache
|
||||
.first()
|
||||
.expect("first local output");
|
||||
assert!(
|
||||
store
|
||||
.get_raw_by_hash_entry(&first_output.source_ee_cert_hash)
|
||||
.expect("load source ee raw")
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3794,24 +3863,12 @@ authorityKeyIdentifier = keyid:always
|
||||
},
|
||||
],
|
||||
};
|
||||
let embedded = vec![VcirEmbeddedEvidence {
|
||||
artifact: VcirRelatedArtifact {
|
||||
artifact_role: VcirArtifactRole::EeCert,
|
||||
artifact_kind: VcirArtifactKind::Cer,
|
||||
uri: None,
|
||||
sha256: sha256_hex(b"embedded-ee"),
|
||||
object_type: Some("cer".to_string()),
|
||||
validation_status: VcirArtifactValidationStatus::Accepted,
|
||||
},
|
||||
raw_entry: embedded_raw_entry(sha256_hex(b"embedded-ee"), vec![1u8, 2, 3]),
|
||||
}];
|
||||
let artifacts = build_vcir_related_artifacts(
|
||||
&ca,
|
||||
&pack,
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
&objects,
|
||||
&[],
|
||||
&embedded,
|
||||
);
|
||||
assert!(
|
||||
artifacts
|
||||
@@ -3841,9 +3898,12 @@ authorityKeyIdentifier = keyid:always
|
||||
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref()
|
||||
== Some("rsync://example.test/repo/issuer/extra.bin")
|
||||
&& artifact.artifact_kind == VcirArtifactKind::Other));
|
||||
assert!(artifacts.iter().any(
|
||||
|artifact| artifact.uri.is_none() && artifact.sha256 == sha256_hex(b"embedded-ee")
|
||||
));
|
||||
assert!(
|
||||
!artifacts
|
||||
.iter()
|
||||
.any(|artifact| artifact.uri.is_none() && artifact.sha256 == sha256_hex(b"embedded-ee")),
|
||||
"embedded EE cert artifacts should no longer be persisted separately"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -4046,7 +4106,8 @@ authorityKeyIdentifier = keyid:always
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: false,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
repo_sync_runtime: None,
|
||||
current_repo_index: None,
|
||||
repo_sync_runtime: None,
|
||||
};
|
||||
|
||||
// For this fixture-driven smoke, we provide the correct issuer CA certificate (the CA for
|
||||
@@ -4108,17 +4169,6 @@ authorityKeyIdentifier = keyid:always
|
||||
.expect("audit rule index exists");
|
||||
assert_eq!(audit_rule.manifest_rsync_uri, manifest_rsync_uri);
|
||||
assert_eq!(audit_rule.output_id, first_vrp.output_id);
|
||||
assert!(vcir.related_artifacts.iter().any(|artifact| {
|
||||
artifact.artifact_role == VcirArtifactRole::EeCert
|
||||
&& artifact.artifact_kind == VcirArtifactKind::Cer
|
||||
}));
|
||||
let ee_entry = store
|
||||
.get_raw_by_hash_entry(&first_vrp.source_ee_cert_hash)
|
||||
.expect("get source ee raw")
|
||||
.expect("source ee raw exists");
|
||||
assert_eq!(ee_entry.object_type.as_deref(), Some("cer"));
|
||||
assert_eq!(ee_entry.encoding.as_deref(), Some("der"));
|
||||
|
||||
let trace = crate::audit_trace::trace_rule_to_root(
|
||||
&store,
|
||||
crate::storage::AuditRuleKind::Roa,
|
||||
@@ -4215,7 +4265,8 @@ authorityKeyIdentifier = keyid:always
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: true,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
repo_sync_runtime: None,
|
||||
current_repo_index: None,
|
||||
repo_sync_runtime: None,
|
||||
};
|
||||
|
||||
let first = runner.run_publication_point(&handle).expect("first run ok");
|
||||
@@ -4323,7 +4374,8 @@ authorityKeyIdentifier = keyid:always
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: true,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
repo_sync_runtime: None,
|
||||
current_repo_index: None,
|
||||
repo_sync_runtime: None,
|
||||
};
|
||||
|
||||
let first = runner.run_publication_point(&handle).expect("first run ok");
|
||||
@@ -4434,7 +4486,8 @@ authorityKeyIdentifier = keyid:always
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: true,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
repo_sync_runtime: None,
|
||||
current_repo_index: None,
|
||||
repo_sync_runtime: None,
|
||||
};
|
||||
|
||||
let first = runner.run_publication_point(&handle).expect("first run ok");
|
||||
@@ -4517,7 +4570,8 @@ authorityKeyIdentifier = keyid:always
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: false,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
repo_sync_runtime: None,
|
||||
current_repo_index: None,
|
||||
repo_sync_runtime: None,
|
||||
};
|
||||
let first = ok_runner
|
||||
.run_publication_point(&handle)
|
||||
@@ -4543,7 +4597,8 @@ authorityKeyIdentifier = keyid:always
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: false,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
repo_sync_runtime: None,
|
||||
current_repo_index: None,
|
||||
repo_sync_runtime: None,
|
||||
};
|
||||
let second = bad_runner
|
||||
.run_publication_point(&handle)
|
||||
@@ -5728,7 +5783,8 @@ authorityKeyIdentifier = keyid:always
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: false,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
repo_sync_runtime: None,
|
||||
current_repo_index: None,
|
||||
repo_sync_runtime: None,
|
||||
};
|
||||
let first = runner_rrdp
|
||||
.run_publication_point(&handle)
|
||||
@@ -5757,7 +5813,8 @@ authorityKeyIdentifier = keyid:always
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: true,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
repo_sync_runtime: None,
|
||||
current_repo_index: None,
|
||||
repo_sync_runtime: None,
|
||||
};
|
||||
let third = runner_rsync
|
||||
.run_publication_point(&handle)
|
||||
|
||||
Reference in New Issue
Block a user