ic_backup/policy/ic_observation/capture_settlement/
mod.rs1use super::{
4 IcObservationAssociationError, IcObservationReplyView, IcObservationResponseView,
5 validate_response,
6};
7use crate::{
8 model::{
9 artifacts::ArtifactChecksumRecord,
10 attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
11 ic_observation::{
12 IcCaptureAttribution, IcCaptureSettlement, IcObservationRequest, IcObservationResponse,
13 },
14 ic_request::IcManagementMethodRecord as Method,
15 ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
16 },
17 policy::{
18 inventory_settlement::{
19 InventorySettlementIdentity, InventorySettlementMismatch, new_candidate_index,
20 },
21 snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
22 },
23};
24use thiserror::Error;
25
26#[derive(Debug)]
28pub struct IcCaptureSettlementView<'a> {
29 observation: IcObservationResponseView<'a>,
30 baseline: &'a IcSnapshotReply<'a>,
31 settlement: &'a IcCaptureSettlement,
32 captured_snapshot: Option<usize>,
33 outcome: ObservationOutcomeRecord,
34}
35impl<'a> IcCaptureSettlementView<'a> {
36 #[must_use]
38 pub const fn outcome(&self) -> ObservationOutcomeRecord {
39 self.outcome
40 }
41 #[must_use]
43 pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
44 self.baseline
45 }
46 #[must_use]
48 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
49 &self.observation
50 }
51 #[must_use]
53 pub const fn settlement(&self) -> &'a IcCaptureSettlement {
54 self.settlement
55 }
56 #[must_use]
58 pub fn captured_snapshot(&self) -> Option<&IcSnapshotInfo> {
59 match self.observation.reply() {
60 IcObservationReplyView::Inventory(reply) => self
61 .captured_snapshot
62 .map(|index| &reply.snapshots()[index]),
63 IcObservationReplyView::Status(_) => None,
64 }
65 }
66}
67
68pub fn validate_capture_settlement<'a>(
84 request: &IcObservationRequest<'a>,
85 journal: &AttemptJournalRecord,
86 baseline: &'a IcSnapshotReply<'a>,
87 response: &'a IcObservationResponse,
88 challenge: &ArtifactChecksumRecord,
89 settlement: &'a IcCaptureSettlement,
90) -> Result<IcCaptureSettlementView<'a>, IcCaptureSettlementError> {
91 if request.mutation().method() != Method::TakeCanisterSnapshot
92 || request.payload().method() != Method::ListCanisterSnapshots
93 {
94 return Err(IcCaptureSettlementError::UnsupportedMethod);
95 }
96 let observation = validate_response(request, journal, response)?;
97 let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
98 return Err(IcCaptureSettlementError::UnsupportedMethod);
99 };
100 let candidates = compare_inventories(request.mutation().target(), baseline, inventory)?;
101 let authority = request.authority().digest();
102 let baseline_digest = baseline.digest();
103 let inventory_digest = inventory.digest();
104 InventorySettlementIdentity {
105 authority: &settlement.authority,
106 mutation_attempt: settlement.mutation_attempt,
107 observation_attempt: settlement.observation_attempt,
108 challenge: &settlement.challenge,
109 baseline: &settlement.baseline,
110 inventory: &settlement.inventory,
111 observation_evidence: &settlement.observation_evidence,
112 }
113 .compare(&InventorySettlementIdentity {
114 authority: &authority,
115 mutation_attempt: request.mutation_attempt(),
116 observation_attempt: request.observation_attempt(),
117 challenge,
118 baseline: &baseline_digest,
119 inventory: &inventory_digest,
120 observation_evidence: &response.input().evidence,
121 })?;
122 let (outcome, captured_snapshot) = match &settlement.attribution {
123 IcCaptureAttribution::Applied { snapshot_id, .. } => {
124 let index = new_candidate_index(inventory, &candidates, snapshot_id)
125 .ok_or(IcCaptureSettlementError::NotNewCandidate)?;
126 (ObservationOutcomeRecord::Applied, Some(index))
127 }
128 IcCaptureAttribution::NotApplied { .. } => (ObservationOutcomeRecord::NotApplied, None),
129 IcCaptureAttribution::Unresolved { .. } => (ObservationOutcomeRecord::Uncertain, None),
130 };
131 Ok(IcCaptureSettlementView {
132 observation,
133 baseline,
134 settlement,
135 captured_snapshot,
136 outcome,
137 })
138}
139
140#[derive(Debug, Error)]
142pub enum IcCaptureSettlementError {
143 #[error("capture settlement requires original capture and list observation")]
145 UnsupportedMethod,
146 #[error(transparent)]
148 Observation(#[from] IcObservationAssociationError),
149 #[error(transparent)]
151 Baseline(#[from] SnapshotInventoryDeltaError),
152 #[error("capture settlement original authority differs")]
154 AuthorityMismatch,
155 #[error("capture settlement attempt identities differ")]
157 AttemptMismatch,
158 #[error("capture settlement challenge differs")]
160 ChallengeMismatch,
161 #[error("capture settlement original baseline differs")]
163 BaselineMismatch,
164 #[error("capture settlement current inventory differs")]
166 InventoryMismatch,
167 #[error("capture settlement observation evidence differs")]
169 ObservationEvidenceMismatch,
170 #[error("capture settlement ID is not a new candidate")]
172 NotNewCandidate,
173}
174
175impl From<InventorySettlementMismatch> for IcCaptureSettlementError {
176 fn from(value: InventorySettlementMismatch) -> Self {
177 match value {
178 InventorySettlementMismatch::Authority => Self::AuthorityMismatch,
179 InventorySettlementMismatch::Attempt => Self::AttemptMismatch,
180 InventorySettlementMismatch::Challenge => Self::ChallengeMismatch,
181 InventorySettlementMismatch::Baseline => Self::BaselineMismatch,
182 InventorySettlementMismatch::Inventory => Self::InventoryMismatch,
183 InventorySettlementMismatch::ObservationEvidence => Self::ObservationEvidenceMismatch,
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests;