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 crate::model::inventory_settlement::IcInventoryAttribution;
4
5use super::{
6    settlement_identity::{SettlementIdentity, SettlementIdentityMismatch},
7    snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
8};
9use crate::model::{
10    artifacts::ArtifactChecksumRecord,
11    attempt_journal::ObservationOutcomeRecord,
12    ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
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// A snapshot index exists only for an explicitly attributed Applied claim.
98#[derive(Debug)]
99pub(crate) enum InventorySettlementOutcome {
100    Applied(usize),
101    NotApplied,
102    Uncertain,
103}
104
105impl InventorySettlementOutcome {
106    pub const fn outcome(&self) -> ObservationOutcomeRecord {
107        match self {
108            Self::Applied(_) => ObservationOutcomeRecord::Applied,
109            Self::NotApplied => ObservationOutcomeRecord::NotApplied,
110            Self::Uncertain => ObservationOutcomeRecord::Uncertain,
111        }
112    }
113
114    pub const fn snapshot_index(&self) -> Option<usize> {
115        match self {
116            Self::Applied(index) => Some(*index),
117            Self::NotApplied | Self::Uncertain => None,
118        }
119    }
120}
121
122pub(crate) fn admitted_outcome(
123    inventory: &IcSnapshotReply<'_>,
124    candidates: &[&IcSnapshotInfo],
125    attribution: &IcInventoryAttribution,
126) -> Result<InventorySettlementOutcome, ()> {
127    match attribution {
128        IcInventoryAttribution::Applied { snapshot_id, .. } => {
129            new_candidate_index(inventory, candidates, snapshot_id)
130                .map(InventorySettlementOutcome::Applied)
131                .ok_or(())
132        }
133        IcInventoryAttribution::NotApplied { .. } => Ok(InventorySettlementOutcome::NotApplied),
134        IcInventoryAttribution::Unresolved { .. } => Ok(InventorySettlementOutcome::Uncertain),
135    }
136}
137
138pub(crate) fn new_candidate_index(
139    inventory: &IcSnapshotReply<'_>,
140    candidates: &[&IcSnapshotInfo],
141    snapshot_id: &[u8],
142) -> Option<usize> {
143    if !candidates
144        .iter()
145        .any(|snapshot| snapshot.id() == snapshot_id)
146    {
147        return None;
148    }
149    inventory
150        .snapshots()
151        .binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id))
152        .ok()
153}