Skip to main content

ic_backup/policy/inventory_settlement/
mod.rs

1//! Common exact identity and explicit new-candidate checks for inventory settlements.
2
3use super::{
4    settlement_identity::{SettlementIdentity, SettlementIdentityMismatch},
5    snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
6};
7use crate::model::{
8    artifacts::ArtifactChecksumRecord,
9    attempt_journal::ObservationOutcomeRecord,
10    ic_observation::IcCaptureAttribution,
11    ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
12    ic_snapshot_upload_observation::IcSnapshotUploadAttribution,
13};
14
15pub(crate) struct InventorySettlementIdentity<'a> {
16    pub operation: SettlementIdentity<'a>,
17    pub baseline: &'a ArtifactChecksumRecord,
18    pub inventory: &'a ArtifactChecksumRecord,
19    pub observation_evidence: &'a ArtifactChecksumRecord,
20}
21
22impl InventorySettlementIdentity<'_> {
23    pub fn compare(&self, expected: &Self) -> Result<(), InventorySettlementMismatch> {
24        self.operation.compare(&expected.operation)?;
25        if self.baseline != expected.baseline {
26            return Err(InventorySettlementMismatch::Baseline);
27        }
28        if self.inventory != expected.inventory {
29            return Err(InventorySettlementMismatch::Inventory);
30        }
31        if self.observation_evidence != expected.observation_evidence {
32            return Err(InventorySettlementMismatch::ObservationEvidence);
33        }
34        Ok(())
35    }
36}
37
38/// Passive settlement claims differ from their independently qualified original evidence.
39#[derive(Debug, thiserror::Error, Eq, PartialEq)]
40pub enum InventorySettlementMismatch {
41    /// Full original operation authority differs.
42    #[error("inventory settlement authority differs")]
43    Authority,
44    /// Original mutation or observation attempt differs.
45    #[error("inventory settlement attempts differ")]
46    Attempt,
47    /// Caller-owned qualification challenge differs.
48    #[error("inventory settlement challenge differs")]
49    Challenge,
50    /// Exact retained baseline request and raw evidence differ.
51    #[error("inventory settlement baseline differs")]
52    Baseline,
53    /// Exact current inventory request and raw evidence differ.
54    #[error("inventory settlement inventory differs")]
55    Inventory,
56    /// Retained opaque observation evidence differs.
57    #[error("inventory settlement observation evidence differs")]
58    ObservationEvidence,
59}
60
61impl From<SettlementIdentityMismatch> for InventorySettlementMismatch {
62    fn from(value: SettlementIdentityMismatch) -> Self {
63        match value {
64            SettlementIdentityMismatch::Authority => Self::Authority,
65            SettlementIdentityMismatch::Attempt => Self::Attempt,
66            SettlementIdentityMismatch::Challenge => Self::Challenge,
67        }
68    }
69}
70
71pub(crate) fn validate_pair<'a, E>(
72    target: &str,
73    baseline: &IcSnapshotReply<'_>,
74    inventory: &'a IcSnapshotReply<'_>,
75    claimed: &InventorySettlementIdentity<'_>,
76    operation: &SettlementIdentity<'_>,
77    observation_evidence: &ArtifactChecksumRecord,
78) -> Result<Vec<&'a IcSnapshotInfo>, E>
79where
80    E: From<SnapshotInventoryDeltaError> + From<InventorySettlementMismatch>,
81{
82    let candidates = compare_inventories(target, baseline, inventory)?;
83    claimed.compare(&InventorySettlementIdentity {
84        operation: SettlementIdentity {
85            authority: operation.authority,
86            mutation_attempt: operation.mutation_attempt,
87            observation_attempt: operation.observation_attempt,
88            challenge: operation.challenge,
89        },
90        baseline: &baseline.digest(),
91        inventory: &inventory.digest(),
92        observation_evidence,
93    })?;
94    Ok(candidates)
95}
96
97#[derive(Clone, Copy)]
98pub(crate) enum InventoryAttribution<'a> {
99    Applied(&'a [u8]),
100    NotApplied,
101    Unresolved,
102}
103
104impl<'a> From<&'a IcCaptureAttribution> for InventoryAttribution<'a> {
105    fn from(value: &'a IcCaptureAttribution) -> Self {
106        match value {
107            IcCaptureAttribution::Applied { snapshot_id, .. } => Self::Applied(snapshot_id),
108            IcCaptureAttribution::NotApplied { .. } => Self::NotApplied,
109            IcCaptureAttribution::Unresolved { .. } => Self::Unresolved,
110        }
111    }
112}
113
114impl<'a> From<&'a IcSnapshotUploadAttribution> for InventoryAttribution<'a> {
115    fn from(value: &'a IcSnapshotUploadAttribution) -> Self {
116        match value {
117            IcSnapshotUploadAttribution::Applied { snapshot_id, .. } => Self::Applied(snapshot_id),
118            IcSnapshotUploadAttribution::NotApplied { .. } => Self::NotApplied,
119            IcSnapshotUploadAttribution::Unresolved { .. } => Self::Unresolved,
120        }
121    }
122}
123
124pub(crate) fn admitted_outcome(
125    inventory: &IcSnapshotReply<'_>,
126    candidates: &[&IcSnapshotInfo],
127    attribution: InventoryAttribution<'_>,
128) -> Result<(ObservationOutcomeRecord, Option<usize>), ()> {
129    match attribution {
130        InventoryAttribution::Applied(snapshot_id) => {
131            new_candidate_index(inventory, candidates, snapshot_id)
132                .map(|index| (ObservationOutcomeRecord::Applied, Some(index)))
133                .ok_or(())
134        }
135        InventoryAttribution::NotApplied => Ok((ObservationOutcomeRecord::NotApplied, None)),
136        InventoryAttribution::Unresolved => Ok((ObservationOutcomeRecord::Uncertain, None)),
137    }
138}
139
140pub(crate) fn new_candidate_index(
141    inventory: &IcSnapshotReply<'_>,
142    candidates: &[&IcSnapshotInfo],
143    snapshot_id: &[u8],
144) -> Option<usize> {
145    if !candidates
146        .iter()
147        .any(|snapshot| snapshot.id() == snapshot_id)
148    {
149        return None;
150    }
151    inventory
152        .snapshots()
153        .binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id))
154        .ok()
155}