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    expected: &InventorySettlementIdentity<'_>,
77) -> Result<Vec<&'a IcSnapshotInfo>, E>
78where
79    E: From<SnapshotInventoryDeltaError> + From<InventorySettlementMismatch>,
80{
81    let candidates = compare_inventories(target, baseline, inventory)?;
82    claimed.compare(expected)?;
83    Ok(candidates)
84}
85
86#[derive(Clone, Copy)]
87pub(crate) enum InventoryAttribution<'a> {
88    Applied(&'a [u8]),
89    NotApplied,
90    Unresolved,
91}
92
93impl<'a> From<&'a IcCaptureAttribution> for InventoryAttribution<'a> {
94    fn from(value: &'a IcCaptureAttribution) -> Self {
95        match value {
96            IcCaptureAttribution::Applied { snapshot_id, .. } => Self::Applied(snapshot_id),
97            IcCaptureAttribution::NotApplied { .. } => Self::NotApplied,
98            IcCaptureAttribution::Unresolved { .. } => Self::Unresolved,
99        }
100    }
101}
102
103impl<'a> From<&'a IcSnapshotUploadAttribution> for InventoryAttribution<'a> {
104    fn from(value: &'a IcSnapshotUploadAttribution) -> Self {
105        match value {
106            IcSnapshotUploadAttribution::Applied { snapshot_id, .. } => Self::Applied(snapshot_id),
107            IcSnapshotUploadAttribution::NotApplied { .. } => Self::NotApplied,
108            IcSnapshotUploadAttribution::Unresolved { .. } => Self::Unresolved,
109        }
110    }
111}
112
113pub(crate) fn admitted_outcome(
114    inventory: &IcSnapshotReply<'_>,
115    candidates: &[&IcSnapshotInfo],
116    attribution: InventoryAttribution<'_>,
117) -> Result<(ObservationOutcomeRecord, Option<usize>), ()> {
118    match attribution {
119        InventoryAttribution::Applied(snapshot_id) => {
120            new_candidate_index(inventory, candidates, snapshot_id)
121                .map(|index| (ObservationOutcomeRecord::Applied, Some(index)))
122                .ok_or(())
123        }
124        InventoryAttribution::NotApplied => Ok((ObservationOutcomeRecord::NotApplied, None)),
125        InventoryAttribution::Unresolved => Ok((ObservationOutcomeRecord::Uncertain, None)),
126    }
127}
128
129pub(crate) fn new_candidate_index(
130    inventory: &IcSnapshotReply<'_>,
131    candidates: &[&IcSnapshotInfo],
132    snapshot_id: &[u8],
133) -> Option<usize> {
134    if !candidates
135        .iter()
136        .any(|snapshot| snapshot.id() == snapshot_id)
137    {
138        return None;
139    }
140    inventory
141        .snapshots()
142        .binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id))
143        .ok()
144}