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 snapshot_inventory_delta::SnapshotInventoryDeltaError,
21 },
22};
23use thiserror::Error;
24
25#[cfg(test)]
26use crate::model::ic_observation::IcCaptureAttribution;
27
28#[derive(Debug)]
30pub struct IcCaptureSettlementView<'a> {
31 observation: IcObservationResponseView<'a>,
32 baseline: &'a IcSnapshotReply<'a>,
33 settlement: &'a IcCaptureSettlement,
34 captured_snapshot: Option<usize>,
35 outcome: ObservationOutcomeRecord,
36}
37impl<'a> IcCaptureSettlementView<'a> {
38 #[must_use]
40 pub const fn outcome(&self) -> ObservationOutcomeRecord {
41 self.outcome
42 }
43 #[must_use]
45 pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
46 self.baseline
47 }
48 #[must_use]
50 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
51 &self.observation
52 }
53 #[must_use]
55 pub const fn settlement(&self) -> &'a IcCaptureSettlement {
56 self.settlement
57 }
58 #[must_use]
60 pub fn captured_snapshot(&self) -> Option<&IcSnapshotInfo> {
61 self.observation.inventory_snapshot(self.captured_snapshot?)
62 }
63}
64
65pub fn validate_capture_settlement<'a>(
81 request: &IcObservationRequest<'a>,
82 journal: &AttemptJournalRecord,
83 baseline: &'a IcSnapshotReply<'a>,
84 response: &'a IcObservationResponse,
85 challenge: &ArtifactChecksumRecord,
86 settlement: &'a IcCaptureSettlement,
87) -> Result<IcCaptureSettlementView<'a>, IcCaptureSettlementError> {
88 if request.mutation().method() != Method::TakeCanisterSnapshot
89 || request.payload().method() != Method::ListCanisterSnapshots
90 {
91 return Err(IcCaptureSettlementError::UnsupportedMethod);
92 }
93 let observation = validate_response(request, journal, response)?;
94 let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
95 return Err(IcCaptureSettlementError::UnsupportedMethod);
96 };
97 let authority = request.authority().digest();
98 let baseline_digest = baseline.digest();
99 let inventory_digest = inventory.digest();
100 let candidates = validate_pair::<IcCaptureSettlementError>(
101 request.mutation().target(),
102 baseline,
103 inventory,
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 &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 )?;
123 let (outcome, captured_snapshot) = admitted_outcome(
124 inventory,
125 &candidates,
126 InventoryAttribution::from(&settlement.attribution),
127 )
128 .map_err(|()| IcCaptureSettlementError::NotNewCandidate)?;
129 Ok(IcCaptureSettlementView {
130 observation,
131 baseline,
132 settlement,
133 captured_snapshot,
134 outcome,
135 })
136}
137
138#[derive(Debug, Error)]
140pub enum IcCaptureSettlementError {
141 #[error("capture settlement requires original capture and list observation")]
143 UnsupportedMethod,
144 #[error(transparent)]
146 Observation(#[from] IcObservationAssociationError),
147 #[error(transparent)]
149 Baseline(#[from] SnapshotInventoryDeltaError),
150 #[error("capture settlement original authority differs")]
152 AuthorityMismatch,
153 #[error("capture settlement attempt identities differ")]
155 AttemptMismatch,
156 #[error("capture settlement challenge differs")]
158 ChallengeMismatch,
159 #[error("capture settlement original baseline differs")]
161 BaselineMismatch,
162 #[error("capture settlement current inventory differs")]
164 InventoryMismatch,
165 #[error("capture settlement observation evidence differs")]
167 ObservationEvidenceMismatch,
168 #[error("capture settlement ID is not a new candidate")]
170 NotNewCandidate,
171}
172
173impl From<InventorySettlementMismatch> for IcCaptureSettlementError {
174 fn from(value: InventorySettlementMismatch) -> Self {
175 match value {
176 InventorySettlementMismatch::Authority => Self::AuthorityMismatch,
177 InventorySettlementMismatch::Attempt => Self::AttemptMismatch,
178 InventorySettlementMismatch::Challenge => Self::ChallengeMismatch,
179 InventorySettlementMismatch::Baseline => Self::BaselineMismatch,
180 InventorySettlementMismatch::Inventory => Self::InventoryMismatch,
181 InventorySettlementMismatch::ObservationEvidence => Self::ObservationEvidenceMismatch,
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests;