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