ic_backup/policy/download_integrity/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 download_journal::{ArtifactStateRecord, DownloadJournalRecord},
6 operation_plan::OperationPlanRecord,
7};
8use thiserror::Error;
9
10pub fn validate<'a>(
21 plan: &OperationPlanRecord,
22 journal: &'a DownloadJournalRecord,
23) -> Result<&'a ArtifactChecksumRecord, DownloadIntegrityPolicyError> {
24 if plan.digest().hash() != journal.intent() {
25 return Err(DownloadIntegrityPolicyError::IntentMismatch);
26 }
27 if plan.selected_targets().len() != journal.artifacts().len()
28 || plan
29 .selected_targets()
30 .iter()
31 .zip(journal.artifacts())
32 .any(|(target, artifact)| target != artifact.canister_id())
33 {
34 return Err(DownloadIntegrityPolicyError::TargetSetMismatch);
35 }
36 let artifact = &journal.artifacts()[0];
37 let checksum = artifact
38 .checksum()
39 .filter(|_| artifact.state() == ArtifactStateRecord::Durable)
40 .ok_or_else(|| DownloadIntegrityPolicyError::NonDurableArtifact {
41 canister_id: artifact.canister_id().to_owned(),
42 state: artifact.state(),
43 })?;
44 Ok(checksum)
45}
46
47#[derive(Debug, Error, Eq, PartialEq)]
49pub enum DownloadIntegrityPolicyError {
50 #[error("download journal differs from original plan intent")]
52 IntentMismatch,
53 #[error("download journal differs from the exact selected target set")]
55 TargetSetMismatch,
56 #[error("artifact for {canister_id} lacks durable checksum evidence ({state:?})")]
58 NonDurableArtifact {
59 canister_id: String,
61 state: ArtifactStateRecord,
63 },
64}
65
66#[cfg(test)]
67mod tests;