ic_backup/policy/ic_observation/capture_settlement/
mod.rs1use crate::model::inventory_settlement::IcInventorySettlement;
4
5use super::{
6 IcObservationAssociationError, IcObservationReplyView, IcObservationResponseView,
7 validate_response,
8};
9use crate::{
10 model::{
11 artifacts::ArtifactChecksumRecord,
12 attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
13 ic_observation::{IcObservationRequest, IcObservationResponse},
14 ic_request::IcManagementMethodRecord as Method,
15 ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
16 },
17 policy::{
18 inventory_settlement::{
19 InventorySettlementIdentity, InventorySettlementMismatch, InventorySettlementOutcome,
20 admitted_outcome, validate_pair,
21 },
22 settlement_identity::SettlementIdentity,
23 snapshot_inventory_delta::SnapshotInventoryDeltaError,
24 },
25};
26use thiserror::Error;
27
28#[derive(Debug)]
30pub struct IcCaptureSettlementView<'a> {
31 observation: IcObservationResponseView<'a>,
32 baseline: &'a IcSnapshotReply<'a>,
33 settlement: &'a IcInventorySettlement,
34 resolution: InventorySettlementOutcome,
35}
36impl<'a> IcCaptureSettlementView<'a> {
37 #[must_use]
39 pub const fn outcome(&self) -> ObservationOutcomeRecord {
40 self.resolution.outcome()
41 }
42 #[must_use]
44 pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
45 self.baseline
46 }
47 #[must_use]
49 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
50 &self.observation
51 }
52 #[must_use]
54 pub const fn settlement(&self) -> &'a IcInventorySettlement {
55 self.settlement
56 }
57 #[must_use]
59 pub fn captured_snapshot(&self) -> Option<&IcSnapshotInfo> {
60 self.observation
61 .inventory_snapshot(self.resolution.snapshot_index()?)
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 IcInventorySettlement,
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 candidates = validate_pair::<IcCaptureSettlementError>(
99 request.mutation().target(),
100 baseline,
101 inventory,
102 &InventorySettlementIdentity {
103 operation: SettlementIdentity {
104 authority: &settlement.authority,
105 mutation_attempt: settlement.mutation_attempt,
106 observation_attempt: settlement.observation_attempt,
107 challenge: &settlement.challenge,
108 },
109 baseline: &settlement.baseline,
110 inventory: &settlement.inventory,
111 observation_evidence: &settlement.observation_evidence,
112 },
113 &SettlementIdentity {
114 authority: &authority,
115 mutation_attempt: request.mutation_attempt(),
116 observation_attempt: request.observation_attempt(),
117 challenge,
118 },
119 &response.input().evidence,
120 )?;
121 let resolution = admitted_outcome(inventory, &candidates, &settlement.attribution)
122 .map_err(|()| IcCaptureSettlementError::NotNewCandidate)?;
123 Ok(IcCaptureSettlementView {
124 observation,
125 baseline,
126 settlement,
127 resolution,
128 })
129}
130
131#[derive(Debug, Error)]
133pub enum IcCaptureSettlementError {
134 #[error("capture settlement requires original capture and list observation")]
136 UnsupportedMethod,
137 #[error(transparent)]
139 Observation(#[from] IcObservationAssociationError),
140 #[error(transparent)]
142 Baseline(#[from] SnapshotInventoryDeltaError),
143 #[error(transparent)]
145 Identity(#[from] InventorySettlementMismatch),
146 #[error("capture settlement ID is not a new candidate")]
148 NotNewCandidate,
149}
150
151#[cfg(test)]
152mod tests;