ic_backup/policy/inventory_settlement/
mod.rs1use 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#[derive(Debug, thiserror::Error, Eq, PartialEq)]
40pub enum InventorySettlementMismatch {
41 #[error("inventory settlement authority differs")]
43 Authority,
44 #[error("inventory settlement attempts differ")]
46 Attempt,
47 #[error("inventory settlement challenge differs")]
49 Challenge,
50 #[error("inventory settlement baseline differs")]
52 Baseline,
53 #[error("inventory settlement inventory differs")]
55 Inventory,
56 #[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
124#[derive(Debug)]
126pub(crate) enum InventorySettlementOutcome {
127 Applied(usize),
128 NotApplied,
129 Uncertain,
130}
131
132impl InventorySettlementOutcome {
133 pub const fn outcome(&self) -> ObservationOutcomeRecord {
134 match self {
135 Self::Applied(_) => ObservationOutcomeRecord::Applied,
136 Self::NotApplied => ObservationOutcomeRecord::NotApplied,
137 Self::Uncertain => ObservationOutcomeRecord::Uncertain,
138 }
139 }
140
141 pub const fn snapshot_index(&self) -> Option<usize> {
142 match self {
143 Self::Applied(index) => Some(*index),
144 Self::NotApplied | Self::Uncertain => None,
145 }
146 }
147}
148
149pub(crate) fn admitted_outcome(
150 inventory: &IcSnapshotReply<'_>,
151 candidates: &[&IcSnapshotInfo],
152 attribution: InventoryAttribution<'_>,
153) -> Result<InventorySettlementOutcome, ()> {
154 match attribution {
155 InventoryAttribution::Applied(snapshot_id) => {
156 new_candidate_index(inventory, candidates, snapshot_id)
157 .map(InventorySettlementOutcome::Applied)
158 .ok_or(())
159 }
160 InventoryAttribution::NotApplied => Ok(InventorySettlementOutcome::NotApplied),
161 InventoryAttribution::Unresolved => Ok(InventorySettlementOutcome::Uncertain),
162 }
163}
164
165pub(crate) fn new_candidate_index(
166 inventory: &IcSnapshotReply<'_>,
167 candidates: &[&IcSnapshotInfo],
168 snapshot_id: &[u8],
169) -> Option<usize> {
170 if !candidates
171 .iter()
172 .any(|snapshot| snapshot.id() == snapshot_id)
173 {
174 return None;
175 }
176 inventory
177 .snapshots()
178 .binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id))
179 .ok()
180}