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