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::snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
18};
19use thiserror::Error;
20
21#[derive(Debug)]
23pub struct IcCaptureSettlementView<'a> {
24 observation: IcObservationResponseView<'a>,
25 baseline: &'a IcSnapshotReply<'a>,
26 settlement: &'a IcCaptureSettlement,
27 captured_snapshot: Option<usize>,
28 outcome: ObservationOutcomeRecord,
29}
30impl<'a> IcCaptureSettlementView<'a> {
31 #[must_use]
33 pub const fn outcome(&self) -> ObservationOutcomeRecord {
34 self.outcome
35 }
36 #[must_use]
38 pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
39 self.baseline
40 }
41 #[must_use]
43 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
44 &self.observation
45 }
46 #[must_use]
48 pub const fn settlement(&self) -> &'a IcCaptureSettlement {
49 self.settlement
50 }
51 #[must_use]
53 pub fn captured_snapshot(&self) -> Option<&IcSnapshotInfo> {
54 match self.observation.reply() {
55 IcObservationReplyView::Inventory(reply) => self
56 .captured_snapshot
57 .map(|index| &reply.snapshots()[index]),
58 IcObservationReplyView::Status(_) => None,
59 }
60 }
61}
62
63pub fn validate_capture_settlement<'a>(
79 request: &IcObservationRequest<'a>,
80 journal: &AttemptJournalRecord,
81 baseline: &'a IcSnapshotReply<'a>,
82 response: &'a IcObservationResponse,
83 challenge: &ArtifactChecksumRecord,
84 settlement: &'a IcCaptureSettlement,
85) -> Result<IcCaptureSettlementView<'a>, IcCaptureSettlementError> {
86 if request.mutation().method() != Method::TakeCanisterSnapshot
87 || request.payload().method() != Method::ListCanisterSnapshots
88 {
89 return Err(IcCaptureSettlementError::UnsupportedMethod);
90 }
91 let observation = validate_response(request, journal, response)?;
92 let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
93 return Err(IcCaptureSettlementError::UnsupportedMethod);
94 };
95 let candidates = compare_inventories(request.mutation().target(), baseline, inventory)?;
96 if settlement.authority != request.authority().digest() {
97 return Err(IcCaptureSettlementError::AuthorityMismatch);
98 }
99 if settlement.mutation_attempt != request.mutation_attempt()
100 || settlement.observation_attempt != request.observation_attempt()
101 {
102 return Err(IcCaptureSettlementError::AttemptMismatch);
103 }
104 if &settlement.challenge != challenge {
105 return Err(IcCaptureSettlementError::ChallengeMismatch);
106 }
107 if settlement.baseline != baseline.digest() {
108 return Err(IcCaptureSettlementError::BaselineMismatch);
109 }
110 if settlement.inventory != inventory.digest() {
111 return Err(IcCaptureSettlementError::InventoryMismatch);
112 }
113 if settlement.observation_evidence != response.input().evidence {
114 return Err(IcCaptureSettlementError::ObservationEvidenceMismatch);
115 }
116 let (outcome, captured_snapshot) = match &settlement.attribution {
117 IcCaptureAttribution::Applied { snapshot_id, .. } => {
118 if !candidates
119 .iter()
120 .any(|snapshot| snapshot.id() == snapshot_id)
121 {
122 return Err(IcCaptureSettlementError::NotNewCandidate);
123 }
124 let index = inventory
125 .snapshots()
126 .binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id.as_slice()))
127 .map_err(|_| IcCaptureSettlementError::NotNewCandidate)?;
128 (ObservationOutcomeRecord::Applied, Some(index))
129 }
130 IcCaptureAttribution::NotApplied { .. } => (ObservationOutcomeRecord::NotApplied, None),
131 IcCaptureAttribution::Unresolved { .. } => (ObservationOutcomeRecord::Uncertain, None),
132 };
133 Ok(IcCaptureSettlementView {
134 observation,
135 baseline,
136 settlement,
137 captured_snapshot,
138 outcome,
139 })
140}
141
142#[derive(Debug, Error)]
144pub enum IcCaptureSettlementError {
145 #[error("capture settlement requires original capture and list observation")]
147 UnsupportedMethod,
148 #[error(transparent)]
150 Observation(#[from] IcObservationAssociationError),
151 #[error(transparent)]
153 Baseline(#[from] SnapshotInventoryDeltaError),
154 #[error("capture settlement original authority differs")]
156 AuthorityMismatch,
157 #[error("capture settlement attempt identities differ")]
159 AttemptMismatch,
160 #[error("capture settlement challenge differs")]
162 ChallengeMismatch,
163 #[error("capture settlement original baseline differs")]
165 BaselineMismatch,
166 #[error("capture settlement current inventory differs")]
168 InventoryMismatch,
169 #[error("capture settlement observation evidence differs")]
171 ObservationEvidenceMismatch,
172 #[error("capture settlement ID is not a new candidate")]
174 NotNewCandidate,
175}
176
177#[cfg(test)]
178mod tests;