use crate::model::{
artifacts::ArtifactChecksumRecord,
download_journal::{ArtifactStateRecord, DownloadArtifactRecord, DownloadJournalRecord},
operation_plan::OperationPlanRecord,
};
use thiserror::Error;
#[derive(Debug)]
pub struct DurableDownloadArtifactView<'a> {
artifact: &'a DownloadArtifactRecord,
checksum: &'a ArtifactChecksumRecord,
}
impl<'a> DurableDownloadArtifactView<'a> {
#[must_use]
pub const fn artifact(&self) -> &'a DownloadArtifactRecord {
self.artifact
}
#[must_use]
pub const fn checksum(&self) -> &'a ArtifactChecksumRecord {
self.checksum
}
}
#[derive(Debug)]
pub struct DurableDownloadView<'a> {
plan: &'a OperationPlanRecord,
journal: &'a DownloadJournalRecord,
artifacts: Vec<DurableDownloadArtifactView<'a>>,
}
impl<'a> DurableDownloadView<'a> {
#[must_use]
pub const fn plan(&self) -> &'a OperationPlanRecord {
self.plan
}
#[must_use]
pub const fn journal(&self) -> &'a DownloadJournalRecord {
self.journal
}
#[must_use]
pub fn artifacts(&self) -> &[DurableDownloadArtifactView<'a>] {
&self.artifacts
}
}
pub fn validate<'a>(
plan: &'a OperationPlanRecord,
journal: &'a DownloadJournalRecord,
) -> Result<DurableDownloadView<'a>, DownloadIntegrityPolicyError> {
if plan.digest().hash() != journal.intent() {
return Err(DownloadIntegrityPolicyError::IntentMismatch);
}
if plan.selected_targets().len() != journal.artifacts().len()
|| plan
.selected_targets()
.iter()
.zip(journal.artifacts())
.any(|(target, artifact)| target != artifact.canister_id())
{
return Err(DownloadIntegrityPolicyError::TargetSetMismatch);
}
let artifacts = journal
.artifacts()
.iter()
.map(|artifact| {
let checksum = artifact
.checksum()
.filter(|_| artifact.state() == ArtifactStateRecord::Durable)
.ok_or_else(|| DownloadIntegrityPolicyError::NonDurableArtifact {
canister_id: artifact.canister_id().to_owned(),
state: artifact.state(),
})?;
Ok(DurableDownloadArtifactView { artifact, checksum })
})
.collect::<Result<Vec<_>, DownloadIntegrityPolicyError>>()?;
Ok(DurableDownloadView {
plan,
journal,
artifacts,
})
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum DownloadIntegrityPolicyError {
#[error("download journal differs from original plan intent")]
IntentMismatch,
#[error("download journal differs from the exact selected target set")]
TargetSetMismatch,
#[error("artifact for {canister_id} lacks durable checksum evidence ({state:?})")]
NonDurableArtifact {
canister_id: String,
state: ArtifactStateRecord,
},
}
#[cfg(test)]
mod tests;