20260711 收紧验证缓存复用边界
This commit is contained in:
commit
08281fa231
218
src/storage.rs
218
src/storage.rs
@ -19,7 +19,7 @@ pub use config::{
|
|||||||
ALL_COLUMN_FAMILY_NAMES, CF_CHILD_CERTIFICATE_CACHE_PROJECTION, CF_MANIFEST_REPLAY_META,
|
ALL_COLUMN_FAMILY_NAMES, CF_CHILD_CERTIFICATE_CACHE_PROJECTION, CF_MANIFEST_REPLAY_META,
|
||||||
CF_PUBLICATION_POINT_CACHE_PROJECTION, CF_RAW_BY_HASH, CF_REPOSITORY_VIEW,
|
CF_PUBLICATION_POINT_CACHE_PROJECTION, CF_RAW_BY_HASH, CF_REPOSITORY_VIEW,
|
||||||
CF_ROA_CACHE_PROJECTION, CF_RRDP_SOURCE, CF_RRDP_SOURCE_MEMBER, CF_RRDP_URI_OWNER,
|
CF_ROA_CACHE_PROJECTION, CF_RRDP_SOURCE, CF_RRDP_SOURCE_MEMBER, CF_RRDP_URI_OWNER,
|
||||||
CF_TRANSPORT_PREFETCH, CF_VCIR, column_family_descriptors,
|
CF_TRANSPORT_PREFETCH, CF_VCIR, CF_VCIR_FAILED_FETCH_REUSE_IDENTITY, column_family_descriptors,
|
||||||
};
|
};
|
||||||
use keys::*;
|
use keys::*;
|
||||||
use pack::compute_sha256_32;
|
use pack::compute_sha256_32;
|
||||||
@ -585,6 +585,56 @@ impl VcirInstanceGate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct VcirFailedFetchReuseIdentity {
|
||||||
|
#[serde(rename = "c")]
|
||||||
|
#[serde(with = "serde_bytes_32")]
|
||||||
|
pub current_ca_sha256: [u8; 32],
|
||||||
|
#[serde(rename = "t")]
|
||||||
|
#[serde(with = "serde_bytes_32")]
|
||||||
|
pub ta_context_digest: [u8; 32],
|
||||||
|
#[serde(rename = "p")]
|
||||||
|
#[serde(with = "serde_bytes_32")]
|
||||||
|
pub parent_context_digest: [u8; 32],
|
||||||
|
#[serde(rename = "f")]
|
||||||
|
#[serde(with = "serde_bytes_32")]
|
||||||
|
pub policy_fingerprint: [u8; 32],
|
||||||
|
#[serde(rename = "nb")]
|
||||||
|
pub effective_not_before: PackTime,
|
||||||
|
#[serde(rename = "nu")]
|
||||||
|
pub effective_until: PackTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VcirFailedFetchReuseIdentity {
|
||||||
|
pub fn validate_internal(&self) -> StorageResult<()> {
|
||||||
|
let effective_not_before = parse_time(
|
||||||
|
"vcir_failed_fetch_reuse_identity.effective_not_before",
|
||||||
|
&self.effective_not_before,
|
||||||
|
)?;
|
||||||
|
let effective_until = parse_time(
|
||||||
|
"vcir_failed_fetch_reuse_identity.effective_until",
|
||||||
|
&self.effective_until,
|
||||||
|
)?;
|
||||||
|
if effective_not_before >= effective_until {
|
||||||
|
return Err(StorageError::InvalidData {
|
||||||
|
entity: "vcir_failed_fetch_reuse_identity.effective_window",
|
||||||
|
detail: "effective_not_before must be before effective_until".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains_validation_time(&self, validation_time: time::OffsetDateTime) -> bool {
|
||||||
|
let Ok(effective_not_before) = self.effective_not_before.parse() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Ok(effective_until) = self.effective_until.parse() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
validation_time >= effective_not_before && validation_time < effective_until
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct VcirChildEntry {
|
pub struct VcirChildEntry {
|
||||||
pub child_manifest_rsync_uri: String,
|
pub child_manifest_rsync_uri: String,
|
||||||
@ -1062,6 +1112,7 @@ pub struct RoaCacheObjectMeta {
|
|||||||
pub source_object_hash: [u8; 32],
|
pub source_object_hash: [u8; 32],
|
||||||
pub ee_serial: Vec<u8>,
|
pub ee_serial: Vec<u8>,
|
||||||
pub crl_uri: String,
|
pub crl_uri: String,
|
||||||
|
pub earliest_safe_reuse_time: PackTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -1127,6 +1178,8 @@ pub struct RoaCacheObjectProjection {
|
|||||||
pub ee_serial: Option<Vec<u8>>,
|
pub ee_serial: Option<Vec<u8>>,
|
||||||
#[serde(rename = "c", default, skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "c", default, skip_serializing_if = "Option::is_none")]
|
||||||
pub crl_uri: Option<String>,
|
pub crl_uri: Option<String>,
|
||||||
|
#[serde(rename = "n", default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub earliest_safe_reuse_time_unix: Option<i64>,
|
||||||
#[serde(rename = "x")]
|
#[serde(rename = "x")]
|
||||||
pub outputs_effective_until_unix: i64,
|
pub outputs_effective_until_unix: i64,
|
||||||
#[serde(rename = "o")]
|
#[serde(rename = "o")]
|
||||||
@ -1150,6 +1203,15 @@ impl RoaCacheObjectProjection {
|
|||||||
if let Some(crl_uri) = &self.crl_uri {
|
if let Some(crl_uri) = &self.crl_uri {
|
||||||
validate_non_empty("roa_cache_projection.entries[].crl_uri", crl_uri)?;
|
validate_non_empty("roa_cache_projection.entries[].crl_uri", crl_uri)?;
|
||||||
}
|
}
|
||||||
|
if let Some(earliest_safe_reuse_time_unix) = self.earliest_safe_reuse_time_unix
|
||||||
|
&& earliest_safe_reuse_time_unix >= self.outputs_effective_until_unix
|
||||||
|
{
|
||||||
|
return Err(StorageError::InvalidData {
|
||||||
|
entity: "roa_cache_projection.entries[].effective_window",
|
||||||
|
detail: "earliest_safe_reuse_time_unix must be before outputs_effective_until_unix"
|
||||||
|
.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
if self.outputs.is_empty() {
|
if self.outputs.is_empty() {
|
||||||
return Err(StorageError::InvalidData {
|
return Err(StorageError::InvalidData {
|
||||||
entity: "roa_cache_projection.entries[]",
|
entity: "roa_cache_projection.entries[]",
|
||||||
@ -1867,6 +1929,9 @@ impl RoaCacheProjection {
|
|||||||
vcir: &ValidatedCaInstanceResult,
|
vcir: &ValidatedCaInstanceResult,
|
||||||
context: Option<&RoaCacheProjectionContext>,
|
context: Option<&RoaCacheProjectionContext>,
|
||||||
) -> StorageResult<Option<Self>> {
|
) -> StorageResult<Option<Self>> {
|
||||||
|
let Some(context) = context else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
let mut issuer_ca_sha256_hex = None;
|
let mut issuer_ca_sha256_hex = None;
|
||||||
let mut crl_sha256_by_uri = Vec::new();
|
let mut crl_sha256_by_uri = Vec::new();
|
||||||
for artifact in &vcir.related_artifacts {
|
for artifact in &vcir.related_artifacts {
|
||||||
@ -1893,13 +1958,18 @@ impl RoaCacheProjection {
|
|||||||
}
|
}
|
||||||
crl_sha256_by_uri.sort_by(|left, right| left.uri.cmp(&right.uri));
|
crl_sha256_by_uri.sort_by(|left, right| left.uri.cmp(&right.uri));
|
||||||
|
|
||||||
let meta_by_uri = context.map(|context| {
|
let meta_by_uri = context
|
||||||
context
|
.object_meta
|
||||||
.object_meta
|
.iter()
|
||||||
.iter()
|
.map(|meta| (meta.source_object_uri.as_str(), meta))
|
||||||
.map(|meta| (meta.source_object_uri.as_str(), meta))
|
.collect::<HashMap<_, _>>();
|
||||||
.collect::<HashMap<_, _>>()
|
let vcir_validation_time =
|
||||||
});
|
vcir.last_successful_validation_time
|
||||||
|
.parse()
|
||||||
|
.map_err(|detail| StorageError::InvalidData {
|
||||||
|
entity: "roa_cache_projection.entries[].earliest_safe_reuse_time_unix",
|
||||||
|
detail,
|
||||||
|
})?;
|
||||||
let mut entries: Vec<RoaCacheObjectProjection> = Vec::new();
|
let mut entries: Vec<RoaCacheObjectProjection> = Vec::new();
|
||||||
let mut entry_index_by_uri: HashMap<String, usize> = HashMap::new();
|
let mut entry_index_by_uri: HashMap<String, usize> = HashMap::new();
|
||||||
for output in &vcir.local_outputs {
|
for output in &vcir.local_outputs {
|
||||||
@ -1915,23 +1985,26 @@ impl RoaCacheProjection {
|
|||||||
entity: "roa_cache_projection.entries[].outputs_effective_until_unix",
|
entity: "roa_cache_projection.entries[].outputs_effective_until_unix",
|
||||||
detail,
|
detail,
|
||||||
})?;
|
})?;
|
||||||
let meta = meta_by_uri
|
let Some(meta) = meta_by_uri.get(output.source_object_uri.as_str()).copied() else {
|
||||||
.as_ref()
|
|
||||||
.and_then(|meta| meta.get(output.source_object_uri.as_str()).copied());
|
|
||||||
if context.is_some() && meta.is_none() {
|
|
||||||
continue;
|
continue;
|
||||||
|
};
|
||||||
|
if meta.source_object_hash != output.source_object_hash {
|
||||||
|
return Err(StorageError::InvalidData {
|
||||||
|
entity: "roa_cache_projection.entries[]",
|
||||||
|
detail: format!(
|
||||||
|
"metadata source object hash mismatch for {}",
|
||||||
|
output.source_object_uri
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if let Some(meta) = meta {
|
let earliest_safe_reuse_time_unix = meta
|
||||||
if meta.source_object_hash != output.source_object_hash {
|
.earliest_safe_reuse_time
|
||||||
return Err(StorageError::InvalidData {
|
.parse()
|
||||||
entity: "roa_cache_projection.entries[]",
|
.map(|time| time.max(vcir_validation_time).unix_timestamp())
|
||||||
detail: format!(
|
.map_err(|detail| StorageError::InvalidData {
|
||||||
"metadata source object hash mismatch for {}",
|
entity: "roa_cache_projection.entries[].earliest_safe_reuse_time_unix",
|
||||||
output.source_object_uri
|
detail,
|
||||||
),
|
})?;
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(entry_index) = entry_index_by_uri.get(output.source_object_uri.as_str()) {
|
if let Some(entry_index) = entry_index_by_uri.get(output.source_object_uri.as_str()) {
|
||||||
let entry = &mut entries[*entry_index];
|
let entry = &mut entries[*entry_index];
|
||||||
if entry.source_object_hash != output.source_object_hash {
|
if entry.source_object_hash != output.source_object_hash {
|
||||||
@ -1952,8 +2025,9 @@ impl RoaCacheProjection {
|
|||||||
entries.push(RoaCacheObjectProjection {
|
entries.push(RoaCacheObjectProjection {
|
||||||
source_object_uri: output.source_object_uri.clone(),
|
source_object_uri: output.source_object_uri.clone(),
|
||||||
source_object_hash: output.source_object_hash,
|
source_object_hash: output.source_object_hash,
|
||||||
ee_serial: meta.map(|meta| meta.ee_serial.clone()),
|
ee_serial: Some(meta.ee_serial.clone()),
|
||||||
crl_uri: meta.map(|meta| meta.crl_uri.clone()),
|
crl_uri: Some(meta.crl_uri.clone()),
|
||||||
|
earliest_safe_reuse_time_unix: Some(earliest_safe_reuse_time_unix),
|
||||||
outputs_effective_until_unix: projected_output_effective_until,
|
outputs_effective_until_unix: projected_output_effective_until,
|
||||||
outputs: vec![projected_output],
|
outputs: vec![projected_output],
|
||||||
});
|
});
|
||||||
@ -1968,8 +2042,8 @@ impl RoaCacheProjection {
|
|||||||
manifest_rsync_uri: vcir.manifest_rsync_uri.clone(),
|
manifest_rsync_uri: vcir.manifest_rsync_uri.clone(),
|
||||||
instance_effective_until: vcir.instance_gate.instance_effective_until.clone(),
|
instance_effective_until: vcir.instance_gate.instance_effective_until.clone(),
|
||||||
issuer_ca_sha256_hex,
|
issuer_ca_sha256_hex,
|
||||||
parent_context_digest: context.map(|context| context.parent_context_digest),
|
parent_context_digest: Some(context.parent_context_digest),
|
||||||
policy_fingerprint: context.map(|context| context.policy_fingerprint),
|
policy_fingerprint: Some(context.policy_fingerprint),
|
||||||
crl_sha256_by_uri,
|
crl_sha256_by_uri,
|
||||||
entries,
|
entries,
|
||||||
};
|
};
|
||||||
@ -2774,6 +2848,24 @@ fn write_roa_cache_projection_to_batch(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_vcir_failed_fetch_reuse_identity_to_batch(
|
||||||
|
failed_fetch_reuse_identity_cf: &ColumnFamily,
|
||||||
|
batch: &mut WriteBatch,
|
||||||
|
manifest_rsync_uri: &str,
|
||||||
|
failed_fetch_reuse_identity: Option<&VcirFailedFetchReuseIdentity>,
|
||||||
|
) -> StorageResult<()> {
|
||||||
|
let key = vcir_failed_fetch_reuse_identity_key(manifest_rsync_uri);
|
||||||
|
match failed_fetch_reuse_identity {
|
||||||
|
Some(identity) => {
|
||||||
|
identity.validate_internal()?;
|
||||||
|
let value = encode_cbor(identity, "vcir_failed_fetch_reuse_identity")?;
|
||||||
|
batch.put_cf(failed_fetch_reuse_identity_cf, key.as_bytes(), value);
|
||||||
|
}
|
||||||
|
None => batch.delete_cf(failed_fetch_reuse_identity_cf, key.as_bytes()),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn write_publication_point_cache_projection_to_batch(
|
fn write_publication_point_cache_projection_to_batch(
|
||||||
projection_cf: &ColumnFamily,
|
projection_cf: &ColumnFamily,
|
||||||
batch: &mut WriteBatch,
|
batch: &mut WriteBatch,
|
||||||
@ -3267,6 +3359,19 @@ impl RocksStore {
|
|||||||
self.put_vcir_with_publication_point_cache_projection(vcir, None)
|
self.put_vcir_with_publication_point_cache_projection(vcir, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn put_vcir_with_failed_fetch_reuse_identity(
|
||||||
|
&self,
|
||||||
|
vcir: &ValidatedCaInstanceResult,
|
||||||
|
failed_fetch_reuse_identity: &VcirFailedFetchReuseIdentity,
|
||||||
|
) -> StorageResult<()> {
|
||||||
|
self.put_vcir_with_projection_action(
|
||||||
|
vcir,
|
||||||
|
None,
|
||||||
|
PublicationPointCacheProjectionWriteAction::Keep,
|
||||||
|
Some(failed_fetch_reuse_identity),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn put_vcir_with_publication_point_cache_projection(
|
pub fn put_vcir_with_publication_point_cache_projection(
|
||||||
&self,
|
&self,
|
||||||
vcir: &ValidatedCaInstanceResult,
|
vcir: &ValidatedCaInstanceResult,
|
||||||
@ -3288,6 +3393,7 @@ impl RocksStore {
|
|||||||
vcir,
|
vcir,
|
||||||
roa_cache_context,
|
roa_cache_context,
|
||||||
publication_point_projection_action,
|
publication_point_projection_action,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3296,9 +3402,11 @@ impl RocksStore {
|
|||||||
vcir: &ValidatedCaInstanceResult,
|
vcir: &ValidatedCaInstanceResult,
|
||||||
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
||||||
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
||||||
|
failed_fetch_reuse_identity: Option<&VcirFailedFetchReuseIdentity>,
|
||||||
) -> StorageResult<()> {
|
) -> StorageResult<()> {
|
||||||
vcir.validate_internal()?;
|
vcir.validate_internal()?;
|
||||||
let vcir_cf = self.cf(CF_VCIR)?;
|
let vcir_cf = self.cf(CF_VCIR)?;
|
||||||
|
let failed_fetch_reuse_identity_cf = self.cf(CF_VCIR_FAILED_FETCH_REUSE_IDENTITY)?;
|
||||||
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
||||||
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
||||||
let pp_projection_cf = self.cf(CF_PUBLICATION_POINT_CACHE_PROJECTION)?;
|
let pp_projection_cf = self.cf(CF_PUBLICATION_POINT_CACHE_PROJECTION)?;
|
||||||
@ -3308,6 +3416,12 @@ impl RocksStore {
|
|||||||
let key = vcir_key(&vcir.manifest_rsync_uri);
|
let key = vcir_key(&vcir.manifest_rsync_uri);
|
||||||
let value = encode_cbor(vcir, "vcir")?;
|
let value = encode_cbor(vcir, "vcir")?;
|
||||||
batch.put_cf(vcir_cf, key.as_bytes(), value);
|
batch.put_cf(vcir_cf, key.as_bytes(), value);
|
||||||
|
write_vcir_failed_fetch_reuse_identity_to_batch(
|
||||||
|
failed_fetch_reuse_identity_cf,
|
||||||
|
&mut batch,
|
||||||
|
&vcir.manifest_rsync_uri,
|
||||||
|
failed_fetch_reuse_identity,
|
||||||
|
)?;
|
||||||
let replay_key = manifest_replay_meta_key(&replay_meta.manifest_rsync_uri);
|
let replay_key = manifest_replay_meta_key(&replay_meta.manifest_rsync_uri);
|
||||||
let replay_value = encode_cbor(&replay_meta, "manifest_replay_meta")?;
|
let replay_value = encode_cbor(&replay_meta, "manifest_replay_meta")?;
|
||||||
batch.put_cf(replay_cf, replay_key.as_bytes(), replay_value);
|
batch.put_cf(replay_cf, replay_key.as_bytes(), replay_value);
|
||||||
@ -3371,6 +3485,21 @@ impl RocksStore {
|
|||||||
vcir: &ValidatedCaInstanceResult,
|
vcir: &ValidatedCaInstanceResult,
|
||||||
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
||||||
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
||||||
|
) -> StorageResult<VcirReplaceTimingBreakdown> {
|
||||||
|
self.replace_vcir_manifest_replay_meta_and_projection_action_with_failed_fetch_reuse_identity(
|
||||||
|
vcir,
|
||||||
|
roa_cache_context,
|
||||||
|
publication_point_projection_action,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn replace_vcir_manifest_replay_meta_and_projection_action_with_failed_fetch_reuse_identity(
|
||||||
|
&self,
|
||||||
|
vcir: &ValidatedCaInstanceResult,
|
||||||
|
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
||||||
|
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
||||||
|
failed_fetch_reuse_identity: Option<&VcirFailedFetchReuseIdentity>,
|
||||||
) -> StorageResult<VcirReplaceTimingBreakdown> {
|
) -> StorageResult<VcirReplaceTimingBreakdown> {
|
||||||
let mut timing = VcirReplaceTimingBreakdown {
|
let mut timing = VcirReplaceTimingBreakdown {
|
||||||
rss_before_kb: process_vm_rss_kb(),
|
rss_before_kb: process_vm_rss_kb(),
|
||||||
@ -3385,6 +3514,7 @@ impl RocksStore {
|
|||||||
|
|
||||||
let batch_build_started = std::time::Instant::now();
|
let batch_build_started = std::time::Instant::now();
|
||||||
let vcir_cf = self.cf(CF_VCIR)?;
|
let vcir_cf = self.cf(CF_VCIR)?;
|
||||||
|
let failed_fetch_reuse_identity_cf = self.cf(CF_VCIR_FAILED_FETCH_REUSE_IDENTITY)?;
|
||||||
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
||||||
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
||||||
let pp_projection_cf = self.cf(CF_PUBLICATION_POINT_CACHE_PROJECTION)?;
|
let pp_projection_cf = self.cf(CF_PUBLICATION_POINT_CACHE_PROJECTION)?;
|
||||||
@ -3397,6 +3527,12 @@ impl RocksStore {
|
|||||||
timing.vcir_value_bytes = vcir_value.len() as u64;
|
timing.vcir_value_bytes = vcir_value.len() as u64;
|
||||||
batch.put_cf(vcir_cf, vcir_key.as_bytes(), vcir_value);
|
batch.put_cf(vcir_cf, vcir_key.as_bytes(), vcir_value);
|
||||||
timing.rss_after_vcir_encode_kb = process_vm_rss_kb();
|
timing.rss_after_vcir_encode_kb = process_vm_rss_kb();
|
||||||
|
write_vcir_failed_fetch_reuse_identity_to_batch(
|
||||||
|
failed_fetch_reuse_identity_cf,
|
||||||
|
&mut batch,
|
||||||
|
&vcir.manifest_rsync_uri,
|
||||||
|
failed_fetch_reuse_identity,
|
||||||
|
)?;
|
||||||
|
|
||||||
let replay_meta_encode_started = std::time::Instant::now();
|
let replay_meta_encode_started = std::time::Instant::now();
|
||||||
let replay_meta = ManifestReplayMeta::from_vcir(vcir);
|
let replay_meta = ManifestReplayMeta::from_vcir(vcir);
|
||||||
@ -3465,6 +3601,27 @@ impl RocksStore {
|
|||||||
Ok(Some(vcir))
|
Ok(Some(vcir))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_vcir_failed_fetch_reuse_identity(
|
||||||
|
&self,
|
||||||
|
manifest_rsync_uri: &str,
|
||||||
|
) -> StorageResult<Option<VcirFailedFetchReuseIdentity>> {
|
||||||
|
let cf = self.cf(CF_VCIR_FAILED_FETCH_REUSE_IDENTITY)?;
|
||||||
|
let key = vcir_failed_fetch_reuse_identity_key(manifest_rsync_uri);
|
||||||
|
let Some(bytes) = self
|
||||||
|
.db
|
||||||
|
.get_cf(cf, key.as_bytes())
|
||||||
|
.map_err(|e| StorageError::RocksDb(e.to_string()))?
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let identity = decode_cbor::<VcirFailedFetchReuseIdentity>(
|
||||||
|
&bytes,
|
||||||
|
"vcir_failed_fetch_reuse_identity",
|
||||||
|
)?;
|
||||||
|
identity.validate_internal()?;
|
||||||
|
Ok(Some(identity))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_manifest_replay_meta(
|
pub fn get_manifest_replay_meta(
|
||||||
&self,
|
&self,
|
||||||
manifest_rsync_uri: &str,
|
manifest_rsync_uri: &str,
|
||||||
@ -4308,11 +4465,18 @@ impl RocksStore {
|
|||||||
|
|
||||||
pub fn delete_vcir(&self, manifest_rsync_uri: &str) -> StorageResult<()> {
|
pub fn delete_vcir(&self, manifest_rsync_uri: &str) -> StorageResult<()> {
|
||||||
let vcir_cf = self.cf(CF_VCIR)?;
|
let vcir_cf = self.cf(CF_VCIR)?;
|
||||||
|
let failed_fetch_reuse_identity_cf = self.cf(CF_VCIR_FAILED_FETCH_REUSE_IDENTITY)?;
|
||||||
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
||||||
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
||||||
let mut batch = WriteBatch::default();
|
let mut batch = WriteBatch::default();
|
||||||
let key = vcir_key(manifest_rsync_uri);
|
let key = vcir_key(manifest_rsync_uri);
|
||||||
batch.delete_cf(vcir_cf, key.as_bytes());
|
batch.delete_cf(vcir_cf, key.as_bytes());
|
||||||
|
let failed_fetch_reuse_identity_key =
|
||||||
|
vcir_failed_fetch_reuse_identity_key(manifest_rsync_uri);
|
||||||
|
batch.delete_cf(
|
||||||
|
failed_fetch_reuse_identity_cf,
|
||||||
|
failed_fetch_reuse_identity_key.as_bytes(),
|
||||||
|
);
|
||||||
let replay_key = manifest_replay_meta_key(manifest_rsync_uri);
|
let replay_key = manifest_replay_meta_key(manifest_rsync_uri);
|
||||||
batch.delete_cf(replay_cf, replay_key.as_bytes());
|
batch.delete_cf(replay_cf, replay_key.as_bytes());
|
||||||
let projection_key = roa_cache_projection_key(manifest_rsync_uri);
|
let projection_key = roa_cache_projection_key(manifest_rsync_uri);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ pub const CF_REPOSITORY_VIEW: &str = "repository_view";
|
|||||||
pub const CF_RAW_BY_HASH: &str = "raw_by_hash";
|
pub const CF_RAW_BY_HASH: &str = "raw_by_hash";
|
||||||
pub const CF_RAW_BLOB: &str = "raw_blob";
|
pub const CF_RAW_BLOB: &str = "raw_blob";
|
||||||
pub const CF_VCIR: &str = "vcir";
|
pub const CF_VCIR: &str = "vcir";
|
||||||
|
pub const CF_VCIR_FAILED_FETCH_REUSE_IDENTITY: &str = "vcir_failed_fetch_reuse_identity";
|
||||||
pub const CF_MANIFEST_REPLAY_META: &str = "manifest_replay_meta";
|
pub const CF_MANIFEST_REPLAY_META: &str = "manifest_replay_meta";
|
||||||
pub const CF_ROA_CACHE_PROJECTION: &str = "roa_cache_projection";
|
pub const CF_ROA_CACHE_PROJECTION: &str = "roa_cache_projection";
|
||||||
pub const CF_PUBLICATION_POINT_CACHE_PROJECTION: &str = "publication_point_cache_projection";
|
pub const CF_PUBLICATION_POINT_CACHE_PROJECTION: &str = "publication_point_cache_projection";
|
||||||
@ -18,6 +19,7 @@ pub const ALL_COLUMN_FAMILY_NAMES: &[&str] = &[
|
|||||||
CF_RAW_BY_HASH,
|
CF_RAW_BY_HASH,
|
||||||
CF_RAW_BLOB,
|
CF_RAW_BLOB,
|
||||||
CF_VCIR,
|
CF_VCIR,
|
||||||
|
CF_VCIR_FAILED_FETCH_REUSE_IDENTITY,
|
||||||
CF_MANIFEST_REPLAY_META,
|
CF_MANIFEST_REPLAY_META,
|
||||||
CF_ROA_CACHE_PROJECTION,
|
CF_ROA_CACHE_PROJECTION,
|
||||||
CF_PUBLICATION_POINT_CACHE_PROJECTION,
|
CF_PUBLICATION_POINT_CACHE_PROJECTION,
|
||||||
@ -32,6 +34,8 @@ pub(super) const REPOSITORY_VIEW_KEY_PREFIX: &str = "repo_view:";
|
|||||||
pub(super) const RAW_BY_HASH_KEY_PREFIX: &str = "rawbyhash:";
|
pub(super) const RAW_BY_HASH_KEY_PREFIX: &str = "rawbyhash:";
|
||||||
pub(super) const RAW_BLOB_KEY_PREFIX: &str = "rawblob:";
|
pub(super) const RAW_BLOB_KEY_PREFIX: &str = "rawblob:";
|
||||||
pub(super) const VCIR_KEY_PREFIX: &str = "vcir:";
|
pub(super) const VCIR_KEY_PREFIX: &str = "vcir:";
|
||||||
|
pub(super) const VCIR_FAILED_FETCH_REUSE_IDENTITY_KEY_PREFIX: &str =
|
||||||
|
"vcir_failed_fetch_reuse_identity:";
|
||||||
pub(super) const MANIFEST_REPLAY_META_KEY_PREFIX: &str = "manifest_replay_meta:";
|
pub(super) const MANIFEST_REPLAY_META_KEY_PREFIX: &str = "manifest_replay_meta:";
|
||||||
pub(super) const ROA_CACHE_PROJECTION_KEY_PREFIX: &str = "roa_cache_projection:";
|
pub(super) const ROA_CACHE_PROJECTION_KEY_PREFIX: &str = "roa_cache_projection:";
|
||||||
pub(super) const PUBLICATION_POINT_CACHE_PROJECTION_KEY_PREFIX: &str =
|
pub(super) const PUBLICATION_POINT_CACHE_PROJECTION_KEY_PREFIX: &str =
|
||||||
|
|||||||
@ -26,6 +26,10 @@ pub(super) fn vcir_key(manifest_rsync_uri: &str) -> String {
|
|||||||
format!("{VCIR_KEY_PREFIX}{manifest_rsync_uri}")
|
format!("{VCIR_KEY_PREFIX}{manifest_rsync_uri}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn vcir_failed_fetch_reuse_identity_key(manifest_rsync_uri: &str) -> String {
|
||||||
|
format!("{VCIR_FAILED_FETCH_REUSE_IDENTITY_KEY_PREFIX}{manifest_rsync_uri}")
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn manifest_replay_meta_key(manifest_rsync_uri: &str) -> String {
|
pub(super) fn manifest_replay_meta_key(manifest_rsync_uri: &str) -> String {
|
||||||
format!("{MANIFEST_REPLAY_META_KEY_PREFIX}{manifest_rsync_uri}")
|
format!("{MANIFEST_REPLAY_META_KEY_PREFIX}{manifest_rsync_uri}")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -284,6 +284,28 @@ fn sample_vcir(manifest_rsync_uri: &str) -> ValidatedCaInstanceResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn roa_cache_projection_context(vcir: &ValidatedCaInstanceResult) -> RoaCacheProjectionContext {
|
||||||
|
let roa_output = vcir
|
||||||
|
.local_outputs
|
||||||
|
.iter()
|
||||||
|
.find(|output| {
|
||||||
|
output.output_type == VcirOutputType::Vrp
|
||||||
|
&& output.source_object_type == VcirSourceObjectType::Roa
|
||||||
|
})
|
||||||
|
.expect("sample VCIR has a ROA output");
|
||||||
|
RoaCacheProjectionContext {
|
||||||
|
parent_context_digest: [0x31; 32],
|
||||||
|
policy_fingerprint: [0x32; 32],
|
||||||
|
object_meta: vec![RoaCacheObjectMeta {
|
||||||
|
source_object_uri: roa_output.source_object_uri.clone(),
|
||||||
|
source_object_hash: roa_output.source_object_hash,
|
||||||
|
ee_serial: vec![0x01],
|
||||||
|
crl_uri: vcir.current_crl_rsync_uri.clone(),
|
||||||
|
earliest_safe_reuse_time: vcir.last_successful_validation_time.clone(),
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vcir_ccr_manifest_projection_validate_accepts_valid_projection() {
|
fn vcir_ccr_manifest_projection_validate_accepts_valid_projection() {
|
||||||
let projection = sample_ccr_manifest_projection(
|
let projection = sample_ccr_manifest_projection(
|
||||||
@ -332,7 +354,8 @@ fn vcir_ccr_manifest_projection_validate_rejects_invalid_fields() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn roa_cache_projection_from_vcir_keeps_only_roa_vrp_outputs() {
|
fn roa_cache_projection_from_vcir_keeps_only_roa_vrp_outputs() {
|
||||||
let vcir = sample_vcir("rsync://example.test/repo/current.mft");
|
let vcir = sample_vcir("rsync://example.test/repo/current.mft");
|
||||||
let projection = RoaCacheProjection::from_vcir(&vcir)
|
let context = roa_cache_projection_context(&vcir);
|
||||||
|
let projection = RoaCacheProjection::from_vcir_with_context(&vcir, Some(&context))
|
||||||
.expect("projection build")
|
.expect("projection build")
|
||||||
.expect("projection exists");
|
.expect("projection exists");
|
||||||
|
|
||||||
@ -366,7 +389,8 @@ fn roa_cache_projection_groups_multiple_outputs_by_roa_uri() {
|
|||||||
vcir.local_outputs.push(second);
|
vcir.local_outputs.push(second);
|
||||||
vcir.summary.local_vrp_count = 2;
|
vcir.summary.local_vrp_count = 2;
|
||||||
|
|
||||||
let projection = RoaCacheProjection::from_vcir(&vcir)
|
let context = roa_cache_projection_context(&vcir);
|
||||||
|
let projection = RoaCacheProjection::from_vcir_with_context(&vcir, Some(&context))
|
||||||
.expect("projection build")
|
.expect("projection build")
|
||||||
.expect("projection exists");
|
.expect("projection exists");
|
||||||
|
|
||||||
@ -748,7 +772,8 @@ fn roa_cache_projection_rejects_duplicate_uri_with_different_hash() {
|
|||||||
vcir.local_outputs.push(duplicate);
|
vcir.local_outputs.push(duplicate);
|
||||||
vcir.summary.local_vrp_count = 2;
|
vcir.summary.local_vrp_count = 2;
|
||||||
|
|
||||||
let err = RoaCacheProjection::from_vcir(&vcir)
|
let context = roa_cache_projection_context(&vcir);
|
||||||
|
let err = RoaCacheProjection::from_vcir_with_context(&vcir, Some(&context))
|
||||||
.expect_err("same ROA URI with different hash must fail");
|
.expect_err("same ROA URI with different hash must fail");
|
||||||
assert!(err.to_string().contains("source object hash mismatch"));
|
assert!(err.to_string().contains("source object hash mismatch"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -506,6 +506,7 @@ pub struct CachedRoaValidationResult {
|
|||||||
source_object_hash: [u8; 32],
|
source_object_hash: [u8; 32],
|
||||||
ee_serial: Option<Vec<u8>>,
|
ee_serial: Option<Vec<u8>>,
|
||||||
crl_uri: Option<String>,
|
crl_uri: Option<String>,
|
||||||
|
earliest_safe_reuse_time_unix: i64,
|
||||||
outputs_effective_until_unix: i64,
|
outputs_effective_until_unix: i64,
|
||||||
outputs: Vec<VcirLocalOutput>,
|
outputs: Vec<VcirLocalOutput>,
|
||||||
}
|
}
|
||||||
@ -543,6 +544,12 @@ impl RoaValidationCacheView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for entry in &projection.entries {
|
for entry in &projection.entries {
|
||||||
|
let Some(earliest_safe_reuse_time_unix) = entry.earliest_safe_reuse_time_unix else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if time::OffsetDateTime::from_unix_timestamp(earliest_safe_reuse_time_unix).is_err() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let outputs = entry
|
let outputs = entry
|
||||||
.outputs
|
.outputs
|
||||||
.iter()
|
.iter()
|
||||||
@ -563,6 +570,7 @@ impl RoaValidationCacheView {
|
|||||||
source_object_hash: entry.source_object_hash,
|
source_object_hash: entry.source_object_hash,
|
||||||
ee_serial: entry.ee_serial.clone(),
|
ee_serial: entry.ee_serial.clone(),
|
||||||
crl_uri: entry.crl_uri.clone(),
|
crl_uri: entry.crl_uri.clone(),
|
||||||
|
earliest_safe_reuse_time_unix,
|
||||||
outputs_effective_until_unix: entry.outputs_effective_until_unix,
|
outputs_effective_until_unix: entry.outputs_effective_until_unix,
|
||||||
outputs,
|
outputs,
|
||||||
},
|
},
|
||||||
@ -674,7 +682,9 @@ impl RoaValidationCacheView {
|
|||||||
if cached.outputs.is_empty() {
|
if cached.outputs.is_empty() {
|
||||||
return_entry_gate!(RoaCacheLookupResult::Miss);
|
return_entry_gate!(RoaCacheLookupResult::Miss);
|
||||||
}
|
}
|
||||||
if cached.outputs_effective_until_unix <= validation_time.unix_timestamp() {
|
if validation_time.unix_timestamp() < cached.earliest_safe_reuse_time_unix
|
||||||
|
|| cached.outputs_effective_until_unix <= validation_time.unix_timestamp()
|
||||||
|
{
|
||||||
return_entry_gate!(RoaCacheLookupResult::ExpiredBlocked);
|
return_entry_gate!(RoaCacheLookupResult::ExpiredBlocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -760,6 +770,10 @@ impl RoaValidationCacheView {
|
|||||||
source_object_hash: file.sha256,
|
source_object_hash: file.sha256,
|
||||||
ee_serial: ee_serial.clone(),
|
ee_serial: ee_serial.clone(),
|
||||||
crl_uri: crl_uri.to_string(),
|
crl_uri: crl_uri.to_string(),
|
||||||
|
earliest_safe_reuse_time: PackTime::from_utc_offset_datetime(
|
||||||
|
time::OffsetDateTime::from_unix_timestamp(cached.earliest_safe_reuse_time_unix)
|
||||||
|
.expect("cached ROA safe reuse time must be valid"),
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
metrics.materialize_nanos = metrics
|
metrics.materialize_nanos = metrics
|
||||||
@ -821,6 +835,14 @@ fn crl_valid_at_time(
|
|||||||
validation_time >= this_update && validation_time < next_update
|
validation_time >= this_update && validation_time < next_update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn roa_cache_earliest_safe_reuse_time(
|
||||||
|
ee_not_before: time::OffsetDateTime,
|
||||||
|
crl_this_update: time::OffsetDateTime,
|
||||||
|
validation_time: time::OffsetDateTime,
|
||||||
|
) -> PackTime {
|
||||||
|
PackTime::from_utc_offset_datetime(ee_not_before.max(crl_this_update).max(validation_time))
|
||||||
|
}
|
||||||
|
|
||||||
fn record_roa_cache_block(
|
fn record_roa_cache_block(
|
||||||
stats: &mut RoaValidationCacheStats,
|
stats: &mut RoaValidationCacheStats,
|
||||||
lookup_result: &RoaCacheLookupResult,
|
lookup_result: &RoaCacheLookupResult,
|
||||||
@ -2736,6 +2758,11 @@ fn process_roa_with_issuer(
|
|||||||
source_object_hash: file.sha256,
|
source_object_hash: file.sha256,
|
||||||
ee_serial: BigUnsigned::from_biguint(&ee.resource_cert.tbs.serial_number).bytes_be,
|
ee_serial: BigUnsigned::from_biguint(&ee.resource_cert.tbs.serial_number).bytes_be,
|
||||||
crl_uri: issuer_crl_rsync_uri.to_string(),
|
crl_uri: issuer_crl_rsync_uri.to_string(),
|
||||||
|
earliest_safe_reuse_time: roa_cache_earliest_safe_reuse_time(
|
||||||
|
ee.resource_cert.tbs.validity_not_before,
|
||||||
|
verified_crl.crl.this_update.utc,
|
||||||
|
validation_time,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
if !collect_vcir_local_outputs {
|
if !collect_vcir_local_outputs {
|
||||||
return Ok((vrps, Vec::new(), Some(cache_object_meta)));
|
return Ok((vrps, Vec::new(), Some(cache_object_meta)));
|
||||||
@ -2866,6 +2893,11 @@ fn process_roa_with_issuer_parallel_cached(
|
|||||||
source_object_hash: file.sha256,
|
source_object_hash: file.sha256,
|
||||||
ee_serial: BigUnsigned::from_biguint(&ee.resource_cert.tbs.serial_number).bytes_be,
|
ee_serial: BigUnsigned::from_biguint(&ee.resource_cert.tbs.serial_number).bytes_be,
|
||||||
crl_uri: issuer_crl_rsync_uri.clone(),
|
crl_uri: issuer_crl_rsync_uri.clone(),
|
||||||
|
earliest_safe_reuse_time: roa_cache_earliest_safe_reuse_time(
|
||||||
|
ee.resource_cert.tbs.validity_not_before,
|
||||||
|
verified_crl.crl.this_update.utc,
|
||||||
|
validation_time,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
if !collect_vcir_local_outputs {
|
if !collect_vcir_local_outputs {
|
||||||
return Ok((vrps, Vec::new(), Some(cache_object_meta)));
|
return Ok((vrps, Vec::new(), Some(cache_object_meta)));
|
||||||
@ -3908,6 +3940,7 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: vec![0x01],
|
ee_serial: vec![0x01],
|
||||||
crl_uri: TEST_CRL_URI.to_string(),
|
crl_uri: TEST_CRL_URI.to_string(),
|
||||||
|
earliest_safe_reuse_time: vcir.last_successful_validation_time.clone(),
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -4299,7 +4332,7 @@ mod tests {
|
|||||||
issuer_der,
|
issuer_der,
|
||||||
crl_hash,
|
crl_hash,
|
||||||
roa_hash,
|
roa_hash,
|
||||||
fixed_time("2026-06-04T00:00:00Z"),
|
fixed_time("2026-06-04T01:00:00Z"),
|
||||||
fixed_time("2026-06-08T00:00:00Z"),
|
fixed_time("2026-06-08T00:00:00Z"),
|
||||||
);
|
);
|
||||||
let projection = sample_roa_cache_projection(&vcir, roa_hash);
|
let projection = sample_roa_cache_projection(&vcir, roa_hash);
|
||||||
@ -4594,6 +4627,7 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: None,
|
crl_uri: None,
|
||||||
|
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output.clone()],
|
outputs: vec![output.clone()],
|
||||||
},
|
},
|
||||||
@ -4610,6 +4644,7 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: None,
|
ee_serial: None,
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
|
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output.clone()],
|
outputs: vec![output.clone()],
|
||||||
},
|
},
|
||||||
@ -4626,6 +4661,7 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
|
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output.clone()],
|
outputs: vec![output.clone()],
|
||||||
},
|
},
|
||||||
@ -4652,6 +4688,7 @@ mod tests {
|
|||||||
source_object_hash: [0xff; 32],
|
source_object_hash: [0xff; 32],
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
|
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output.clone()],
|
outputs: vec![output.clone()],
|
||||||
},
|
},
|
||||||
@ -4668,6 +4705,7 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
|
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: Vec::new(),
|
outputs: Vec::new(),
|
||||||
},
|
},
|
||||||
@ -4688,6 +4726,7 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
|
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output],
|
outputs: vec![output],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -33,8 +33,9 @@ use crate::storage::{
|
|||||||
PublicationPointCacheProjectionWriteAction, RawByHashEntry, RoaCacheProjectionContext,
|
PublicationPointCacheProjectionWriteAction, RawByHashEntry, RoaCacheProjectionContext,
|
||||||
RocksStore, ValidatedCaInstanceResult, VcirArtifactKind, VcirArtifactRole,
|
RocksStore, ValidatedCaInstanceResult, VcirArtifactKind, VcirArtifactRole,
|
||||||
VcirArtifactValidationStatus, VcirAuditSummary, VcirCcrManifestProjection, VcirChildEntry,
|
VcirArtifactValidationStatus, VcirAuditSummary, VcirCcrManifestProjection, VcirChildEntry,
|
||||||
VcirInstanceGate, VcirLocalOutput, VcirLocalOutputPayload, VcirOutputType, VcirRelatedArtifact,
|
VcirFailedFetchReuseIdentity, VcirInstanceGate, VcirLocalOutput, VcirLocalOutputPayload,
|
||||||
VcirReplaceTimingBreakdown, VcirSourceObjectType, VcirSummary,
|
VcirOutputType, VcirRelatedArtifact, VcirReplaceTimingBreakdown, VcirSourceObjectType,
|
||||||
|
VcirSummary,
|
||||||
};
|
};
|
||||||
use crate::sync::repo::{
|
use crate::sync::repo::{
|
||||||
sync_publication_point, sync_publication_point_replay, sync_publication_point_replay_delta,
|
sync_publication_point, sync_publication_point_replay, sync_publication_point_replay_delta,
|
||||||
@ -1751,6 +1752,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
|||||||
self.store,
|
self.store,
|
||||||
ca,
|
ca,
|
||||||
&fresh_err,
|
&fresh_err,
|
||||||
|
self.policy,
|
||||||
self.validation_time,
|
self.validation_time,
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("failed fetch VCIR projection failed: {e}"))?;
|
.map_err(|e| format!("failed fetch VCIR projection failed: {e}"))?;
|
||||||
@ -4256,6 +4258,7 @@ fn project_current_instance_vcir_on_failed_fetch(
|
|||||||
store: &RocksStore,
|
store: &RocksStore,
|
||||||
ca: &CaInstanceHandle,
|
ca: &CaInstanceHandle,
|
||||||
fresh_err: &ManifestFreshError,
|
fresh_err: &ManifestFreshError,
|
||||||
|
policy: &Policy,
|
||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
) -> Result<VcirReuseProjection, String> {
|
) -> Result<VcirReuseProjection, String> {
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
@ -4264,80 +4267,57 @@ fn project_current_instance_vcir_on_failed_fetch(
|
|||||||
.get_vcir(&ca.manifest_rsync_uri)
|
.get_vcir(&ca.manifest_rsync_uri)
|
||||||
.map_err(|e| format!("load VCIR failed: {e}"))?
|
.map_err(|e| format!("load VCIR failed: {e}"))?
|
||||||
else {
|
else {
|
||||||
warnings.push(
|
return Ok(failed_fetch_no_cache_projection(
|
||||||
Warning::new(format!("manifest failed fetch: {fresh_err}"))
|
ca,
|
||||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
fresh_err,
|
||||||
.with_context(&ca.manifest_rsync_uri),
|
None,
|
||||||
);
|
"no latest validated result for current CA instance; no cached output reused",
|
||||||
warnings.push(
|
));
|
||||||
Warning::new(
|
|
||||||
"no latest validated result for current CA instance; no cached output reused",
|
|
||||||
)
|
|
||||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
|
||||||
.with_context(&ca.manifest_rsync_uri),
|
|
||||||
);
|
|
||||||
return Ok(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,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !vcir.audit_summary.failed_fetch_eligible {
|
if !vcir.audit_summary.failed_fetch_eligible {
|
||||||
warnings.push(
|
return Ok(failed_fetch_no_cache_projection(
|
||||||
Warning::new(format!("manifest failed fetch: {fresh_err}"))
|
ca,
|
||||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
fresh_err,
|
||||||
.with_context(&ca.manifest_rsync_uri),
|
Some(vcir),
|
||||||
);
|
"latest VCIR is not marked failed-fetch eligible; no cached output reused",
|
||||||
warnings.push(
|
));
|
||||||
Warning::new(
|
|
||||||
"latest VCIR is not marked failed-fetch eligible; no cached output reused",
|
|
||||||
)
|
|
||||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
|
||||||
.with_context(&ca.manifest_rsync_uri),
|
|
||||||
);
|
|
||||||
return Ok(VcirReuseProjection {
|
|
||||||
source: PublicationPointSource::FailedFetchNoCache,
|
|
||||||
vcir: Some(vcir),
|
|
||||||
ccr_manifest_projection: None,
|
|
||||||
snapshot: None,
|
|
||||||
objects: empty_objects_output(),
|
|
||||||
child_audits: Vec::new(),
|
|
||||||
discovered_children: Vec::new(),
|
|
||||||
warnings,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let instance_effective_until =
|
let reuse_identity = match store.get_vcir_failed_fetch_reuse_identity(&ca.manifest_rsync_uri) {
|
||||||
parse_snapshot_time_value(&vcir.instance_gate.instance_effective_until)?;
|
Ok(Some(identity)) => identity,
|
||||||
if validation_time > instance_effective_until {
|
Ok(None) => {
|
||||||
warnings.push(
|
return Ok(failed_fetch_no_cache_projection(
|
||||||
Warning::new(format!("manifest failed fetch: {fresh_err}"))
|
ca,
|
||||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
fresh_err,
|
||||||
.with_context(&ca.manifest_rsync_uri),
|
Some(vcir),
|
||||||
);
|
"latest VCIR reuse identity is missing; no cached output or child reused",
|
||||||
warnings.push(
|
));
|
||||||
Warning::new(
|
}
|
||||||
"latest VCIR instance_gate expired; current instance contributes no cached output",
|
Err(error) => {
|
||||||
)
|
return Ok(failed_fetch_no_cache_projection(
|
||||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
ca,
|
||||||
.with_context(&ca.manifest_rsync_uri),
|
fresh_err,
|
||||||
);
|
Some(vcir),
|
||||||
return Ok(VcirReuseProjection {
|
&format!(
|
||||||
source: PublicationPointSource::FailedFetchNoCache,
|
"latest VCIR reuse identity is invalid ({error}); no cached output or child reused"
|
||||||
vcir: Some(vcir),
|
),
|
||||||
ccr_manifest_projection: None,
|
));
|
||||||
snapshot: None,
|
}
|
||||||
objects: empty_objects_output(),
|
};
|
||||||
child_audits: Vec::new(),
|
if !failed_fetch_reuse_identity_matches_current(
|
||||||
discovered_children: Vec::new(),
|
&reuse_identity,
|
||||||
warnings,
|
&vcir,
|
||||||
});
|
ca,
|
||||||
|
policy,
|
||||||
|
validation_time,
|
||||||
|
) {
|
||||||
|
return Ok(failed_fetch_no_cache_projection(
|
||||||
|
ca,
|
||||||
|
fresh_err,
|
||||||
|
Some(vcir),
|
||||||
|
"latest VCIR reuse identity does not match the current CA context or time window; no cached output or child reused",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ccr_manifest_projection = reuse_ccr_manifest_projection_from_vcir(ca, &vcir)?;
|
let ccr_manifest_projection = reuse_ccr_manifest_projection_from_vcir(ca, &vcir)?;
|
||||||
@ -4367,6 +4347,73 @@ fn project_current_instance_vcir_on_failed_fetch(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn failed_fetch_no_cache_projection(
|
||||||
|
ca: &CaInstanceHandle,
|
||||||
|
fresh_err: &ManifestFreshError,
|
||||||
|
vcir: Option<ValidatedCaInstanceResult>,
|
||||||
|
reason: &str,
|
||||||
|
) -> VcirReuseProjection {
|
||||||
|
let warnings = vec![
|
||||||
|
Warning::new(format!("manifest failed fetch: {fresh_err}"))
|
||||||
|
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||||
|
.with_context(&ca.manifest_rsync_uri),
|
||||||
|
Warning::new(reason)
|
||||||
|
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||||
|
.with_context(&ca.manifest_rsync_uri),
|
||||||
|
];
|
||||||
|
VcirReuseProjection {
|
||||||
|
source: PublicationPointSource::FailedFetchNoCache,
|
||||||
|
vcir,
|
||||||
|
ccr_manifest_projection: None,
|
||||||
|
snapshot: None,
|
||||||
|
objects: empty_objects_output(),
|
||||||
|
child_audits: Vec::new(),
|
||||||
|
discovered_children: Vec::new(),
|
||||||
|
warnings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn failed_fetch_reuse_identity_for_fresh_result(
|
||||||
|
ca: &CaInstanceHandle,
|
||||||
|
policy: &Policy,
|
||||||
|
validation_time: time::OffsetDateTime,
|
||||||
|
effective_until: PackTime,
|
||||||
|
) -> Result<VcirFailedFetchReuseIdentity, String> {
|
||||||
|
let current_ca_sha256 = ca.ca_certificate_sha256_32().ok_or_else(|| {
|
||||||
|
"current CA certificate hash unavailable for VCIR reuse identity".to_string()
|
||||||
|
})?;
|
||||||
|
let identity = VcirFailedFetchReuseIdentity {
|
||||||
|
current_ca_sha256,
|
||||||
|
ta_context_digest: ta_context_digest_for_ca(ca),
|
||||||
|
parent_context_digest: parent_context_digest_for_ca(ca),
|
||||||
|
policy_fingerprint: publication_point_cache_policy_fingerprint(policy),
|
||||||
|
effective_not_before: PackTime::from_utc_offset_datetime(validation_time),
|
||||||
|
effective_until,
|
||||||
|
};
|
||||||
|
identity
|
||||||
|
.validate_internal()
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
|
Ok(identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn failed_fetch_reuse_identity_matches_current(
|
||||||
|
cached: &VcirFailedFetchReuseIdentity,
|
||||||
|
vcir: &ValidatedCaInstanceResult,
|
||||||
|
ca: &CaInstanceHandle,
|
||||||
|
policy: &Policy,
|
||||||
|
validation_time: time::OffsetDateTime,
|
||||||
|
) -> bool {
|
||||||
|
let Some(current_ca_sha256) = ca.ca_certificate_sha256_32() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
cached.current_ca_sha256 == current_ca_sha256
|
||||||
|
&& cached.ta_context_digest == ta_context_digest_for_ca(ca)
|
||||||
|
&& cached.parent_context_digest == parent_context_digest_for_ca(ca)
|
||||||
|
&& cached.policy_fingerprint == publication_point_cache_policy_fingerprint(policy)
|
||||||
|
&& cached.effective_until == vcir.instance_gate.instance_effective_until
|
||||||
|
&& cached.contains_validation_time(validation_time)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn reconstruct_snapshot_from_vcir(
|
fn reconstruct_snapshot_from_vcir(
|
||||||
store: &RocksStore,
|
store: &RocksStore,
|
||||||
@ -5280,8 +5327,14 @@ fn persist_vcir_for_fresh_result_with_timing(
|
|||||||
.expect("publication point projection must exist when guard is not active"),
|
.expect("publication point projection must exist when guard is not active"),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let failed_fetch_reuse_identity = failed_fetch_reuse_identity_for_fresh_result(
|
||||||
|
ca,
|
||||||
|
policy,
|
||||||
|
validation_time,
|
||||||
|
vcir.instance_gate.instance_effective_until.clone(),
|
||||||
|
)?;
|
||||||
let replace_timing = store
|
let replace_timing = store
|
||||||
.replace_vcir_manifest_replay_meta_and_projection_action(
|
.replace_vcir_manifest_replay_meta_and_projection_action_with_failed_fetch_reuse_identity(
|
||||||
&vcir,
|
&vcir,
|
||||||
Some(&RoaCacheProjectionContext {
|
Some(&RoaCacheProjectionContext {
|
||||||
parent_context_digest: parent_context_digest_for_ca(ca),
|
parent_context_digest: parent_context_digest_for_ca(ca),
|
||||||
@ -5289,6 +5342,7 @@ fn persist_vcir_for_fresh_result_with_timing(
|
|||||||
object_meta: objects.roa_cache_object_meta.clone(),
|
object_meta: objects.roa_cache_object_meta.clone(),
|
||||||
}),
|
}),
|
||||||
publication_point_cache_projection_action,
|
publication_point_cache_projection_action,
|
||||||
|
Some(&failed_fetch_reuse_identity),
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("store VCIR and manifest replay meta failed: {e}"))?;
|
.map_err(|e| format!("store VCIR and manifest replay meta failed: {e}"))?;
|
||||||
timing.replace_vcir_ms = replace_vcir_started.elapsed().as_millis() as u64;
|
timing.replace_vcir_ms = replace_vcir_started.elapsed().as_millis() as u64;
|
||||||
|
|||||||
@ -753,6 +753,28 @@ fn sample_vcir_for_projection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn put_vcir_for_failed_fetch_reuse(
|
||||||
|
store: &RocksStore,
|
||||||
|
ca: &CaInstanceHandle,
|
||||||
|
policy: &Policy,
|
||||||
|
vcir: &ValidatedCaInstanceResult,
|
||||||
|
) {
|
||||||
|
let validation_time = vcir
|
||||||
|
.last_successful_validation_time
|
||||||
|
.parse()
|
||||||
|
.expect("parse VCIR validation time");
|
||||||
|
let identity = failed_fetch_reuse_identity_for_fresh_result(
|
||||||
|
ca,
|
||||||
|
policy,
|
||||||
|
validation_time,
|
||||||
|
vcir.instance_gate.instance_effective_until.clone(),
|
||||||
|
)
|
||||||
|
.expect("build VCIR failed-fetch reuse identity");
|
||||||
|
store
|
||||||
|
.put_vcir_with_failed_fetch_reuse_identity(vcir, &identity)
|
||||||
|
.expect("put reusable VCIR");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn never_http_fetcher_returns_error() {
|
fn never_http_fetcher_returns_error() {
|
||||||
let f = NeverHttpFetcher;
|
let f = NeverHttpFetcher;
|
||||||
@ -3827,7 +3849,6 @@ fn project_current_instance_vcir_reuses_local_outputs_and_restores_children() {
|
|||||||
let repo_bytes_db = store_dir.path().join("repo-bytes.db");
|
let repo_bytes_db = store_dir.path().join("repo-bytes.db");
|
||||||
let store = RocksStore::open_with_external_repo_bytes(&main_db, &repo_bytes_db)
|
let store = RocksStore::open_with_external_repo_bytes(&main_db, &repo_bytes_db)
|
||||||
.expect("open rocksdb with external repo bytes");
|
.expect("open rocksdb with external repo bytes");
|
||||||
store.put_vcir(&vcir).expect("put vcir");
|
|
||||||
store
|
store
|
||||||
.put_blob_bytes_batch(&[(child_cert_hash.clone(), g.child_ca_der.clone())])
|
.put_blob_bytes_batch(&[(child_cert_hash.clone(), g.child_ca_der.clone())])
|
||||||
.expect("put child cert repo bytes");
|
.expect("put child cert repo bytes");
|
||||||
@ -3852,6 +3873,8 @@ fn project_current_instance_vcir_reuses_local_outputs_and_restores_children() {
|
|||||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
||||||
rrdp_notification_uri: Some("https://example.test/notify.xml".to_string()),
|
rrdp_notification_uri: Some("https://example.test/notify.xml".to_string()),
|
||||||
};
|
};
|
||||||
|
let policy = Policy::default();
|
||||||
|
put_vcir_for_failed_fetch_reuse(&store, &ca, &policy, &vcir);
|
||||||
|
|
||||||
let projection = project_current_instance_vcir_on_failed_fetch(
|
let projection = project_current_instance_vcir_on_failed_fetch(
|
||||||
&store,
|
&store,
|
||||||
@ -3859,6 +3882,7 @@ fn project_current_instance_vcir_reuses_local_outputs_and_restores_children() {
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
|
&policy,
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project vcir");
|
.expect("project vcir");
|
||||||
@ -3950,6 +3974,7 @@ fn project_current_instance_vcir_returns_no_output_when_instance_gate_expired()
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
|
&Policy::default(),
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project vcir");
|
.expect("project vcir");
|
||||||
@ -3975,7 +4000,6 @@ fn project_current_instance_vcir_keeps_real_fresh_validation_warning() {
|
|||||||
let repo_bytes_db = store_dir.path().join("repo-bytes.db");
|
let repo_bytes_db = store_dir.path().join("repo-bytes.db");
|
||||||
let store = RocksStore::open_with_external_repo_bytes(&main_db, &repo_bytes_db)
|
let store = RocksStore::open_with_external_repo_bytes(&main_db, &repo_bytes_db)
|
||||||
.expect("open rocksdb with external repo bytes");
|
.expect("open rocksdb with external repo bytes");
|
||||||
store.put_vcir(&vcir).expect("put vcir");
|
|
||||||
store
|
store
|
||||||
.put_blob_bytes_batch(&[(child_cert_hash, b"child-cert".to_vec())])
|
.put_blob_bytes_batch(&[(child_cert_hash, b"child-cert".to_vec())])
|
||||||
.expect("put child cert repo bytes");
|
.expect("put child cert repo bytes");
|
||||||
@ -3993,6 +4017,8 @@ fn project_current_instance_vcir_keeps_real_fresh_validation_warning() {
|
|||||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
||||||
rrdp_notification_uri: None,
|
rrdp_notification_uri: None,
|
||||||
};
|
};
|
||||||
|
let policy = Policy::default();
|
||||||
|
put_vcir_for_failed_fetch_reuse(&store, &ca, &policy, &vcir);
|
||||||
|
|
||||||
let projection = project_current_instance_vcir_on_failed_fetch(
|
let projection = project_current_instance_vcir_on_failed_fetch(
|
||||||
&store,
|
&store,
|
||||||
@ -4000,6 +4026,7 @@ fn project_current_instance_vcir_keeps_real_fresh_validation_warning() {
|
|||||||
&ManifestFreshError::HashMismatch {
|
&ManifestFreshError::HashMismatch {
|
||||||
rsync_uri: "rsync://example.test/repo/issuer/a.roa".to_string(),
|
rsync_uri: "rsync://example.test/repo/issuer/a.roa".to_string(),
|
||||||
},
|
},
|
||||||
|
&policy,
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project vcir");
|
.expect("project vcir");
|
||||||
@ -4048,6 +4075,7 @@ fn project_current_instance_vcir_returns_no_output_when_latest_result_missing()
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
|
&Policy::default(),
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project without cached vcir");
|
.expect("project without cached vcir");
|
||||||
@ -4099,6 +4127,7 @@ fn project_current_instance_vcir_returns_no_output_when_latest_result_is_ineligi
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
|
&Policy::default(),
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project ineligible vcir");
|
.expect("project ineligible vcir");
|
||||||
@ -4128,7 +4157,6 @@ fn project_current_instance_vcir_rejects_mismatched_ccr_projection_uri() {
|
|||||||
|
|
||||||
let store_dir = tempfile::tempdir().expect("store dir");
|
let store_dir = tempfile::tempdir().expect("store dir");
|
||||||
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
||||||
store.put_vcir(&vcir).expect("put vcir");
|
|
||||||
|
|
||||||
let ca = CaInstanceHandle {
|
let ca = CaInstanceHandle {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
@ -4143,6 +4171,8 @@ fn project_current_instance_vcir_rejects_mismatched_ccr_projection_uri() {
|
|||||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
||||||
rrdp_notification_uri: None,
|
rrdp_notification_uri: None,
|
||||||
};
|
};
|
||||||
|
let policy = Policy::default();
|
||||||
|
put_vcir_for_failed_fetch_reuse(&store, &ca, &policy, &vcir);
|
||||||
|
|
||||||
let err = project_current_instance_vcir_on_failed_fetch(
|
let err = project_current_instance_vcir_on_failed_fetch(
|
||||||
&store,
|
&store,
|
||||||
@ -4150,6 +4180,7 @@ fn project_current_instance_vcir_rejects_mismatched_ccr_projection_uri() {
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
|
&policy,
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user