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 match self.observation.reply() {
62 IcObservationReplyView::Inventory(reply) => self
63 .captured_snapshot
64 .map(|index| &reply.snapshots()[index]),
65 IcObservationReplyView::Status(_) => None,
66 }
67 }
68}
69
70pub fn validate_capture_settlement<'a>(
86 request: &IcObservationRequest<'a>,
87 journal: &AttemptJournalRecord,
88 baseline: &'a IcSnapshotReply<'a>,
89 response: &'a IcObservationResponse,
90 challenge: &ArtifactChecksumRecord,
91 settlement: &'a IcCaptureSettlement,
92) -> Result<IcCaptureSettlementView<'a>, IcCaptureSettlementError> {
93 if request.mutation().method() != Method::TakeCanisterSnapshot
94 || request.payload().method() != Method::ListCanisterSnapshots
95 {
96 return Err(IcCaptureSettlementError::UnsupportedMethod);
97 }
98 let observation = validate_response(request, journal, response)?;
99 let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
100 return Err(IcCaptureSettlementError::UnsupportedMethod);
101 };
102 let authority = request.authority().digest();
103 let baseline_digest = baseline.digest();
104 let inventory_digest = inventory.digest();
105 let candidates = validate_pair::<IcCaptureSettlementError>(
106 request.mutation().target(),
107 baseline,
108 inventory,
109 &InventorySettlementIdentity {
110 authority: &settlement.authority,
111 mutation_attempt: settlement.mutation_attempt,
112 observation_attempt: settlement.observation_attempt,
113 challenge: &settlement.challenge,
114 baseline: &settlement.baseline,
115 inventory: &settlement.inventory,
116 observation_evidence: &settlement.observation_evidence,
117 },
118 &InventorySettlementIdentity {
119 authority: &authority,
120 mutation_attempt: request.mutation_attempt(),
121 observation_attempt: request.observation_attempt(),
122 challenge,
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;