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 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}