ic_backup/policy/ic_snapshot_upload_observation/settlement/
mod.rs1use super::validate_response;
3use crate::{
4 model::{
5 artifacts::ArtifactChecksumRecord,
6 attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
7 ic_observation::IcObservationResponse,
8 ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
9 ic_snapshot_upload::IcSnapshotUploadError,
10 ic_snapshot_upload_observation::{
11 IcSnapshotUploadAttribution, IcSnapshotUploadObservationRequest,
12 IcSnapshotUploadSettlement,
13 },
14 },
15 policy::{
16 ic_observation::{
17 IcObservationAssociationError, IcObservationReplyView, IcObservationResponseView,
18 },
19 snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
20 },
21};
22use thiserror::Error;
23
24#[derive(Debug)]
26pub struct IcSnapshotUploadSettlementView<'a> {
27 observation: IcObservationResponseView<'a>,
28 baseline: &'a IcSnapshotReply<'a>,
29 settlement: &'a IcSnapshotUploadSettlement,
30 allocated_snapshot: Option<usize>,
31 outcome: ObservationOutcomeRecord,
32}
33impl<'a> IcSnapshotUploadSettlementView<'a> {
34 #[must_use]
36 pub const fn outcome(&self) -> ObservationOutcomeRecord {
37 self.outcome
38 }
39 #[must_use]
41 pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
42 self.baseline
43 }
44 #[must_use]
46 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
47 &self.observation
48 }
49 #[must_use]
51 pub const fn settlement(&self) -> &'a IcSnapshotUploadSettlement {
52 self.settlement
53 }
54 #[must_use]
56 pub fn allocated_snapshot(&self) -> Option<&IcSnapshotInfo> {
57 match self.observation.reply() {
58 IcObservationReplyView::Inventory(reply) => self
59 .allocated_snapshot
60 .map(|index| &reply.snapshots()[index]),
61 IcObservationReplyView::Status(_) => None,
62 }
63 }
64}
65
66pub fn validate_settlement<'a>(
79 request: &IcSnapshotUploadObservationRequest<'a, '_>,
80 journal: &AttemptJournalRecord,
81 baseline: &'a IcSnapshotReply<'a>,
82 response: &'a IcObservationResponse,
83 challenge: &ArtifactChecksumRecord,
84 settlement: &'a IcSnapshotUploadSettlement,
85) -> Result<IcSnapshotUploadSettlementView<'a>, IcSnapshotUploadSettlementError> {
86 let observation = validate_response(request, journal, response)?;
87 let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
88 return Err(IcSnapshotUploadSettlementError::WrongInventory);
89 };
90 let candidates = compare_inventories(request.payload().target(), baseline, inventory)?;
91 if settlement.authority != request.authority().digest() {
92 return Err(IcSnapshotUploadSettlementError::AuthorityMismatch);
93 }
94 if settlement.mutation_attempt != request.mutation_attempt()
95 || settlement.observation_attempt != request.observation_attempt()
96 {
97 return Err(IcSnapshotUploadSettlementError::AttemptMismatch);
98 }
99 if &settlement.challenge != challenge {
100 return Err(IcSnapshotUploadSettlementError::ChallengeMismatch);
101 }
102 if settlement.baseline != baseline.digest() {
103 return Err(IcSnapshotUploadSettlementError::BaselineMismatch);
104 }
105 if settlement.inventory != inventory.digest() {
106 return Err(IcSnapshotUploadSettlementError::InventoryMismatch);
107 }
108 if settlement.observation_evidence != response.input().evidence {
109 return Err(IcSnapshotUploadSettlementError::ObservationEvidenceMismatch);
110 }
111 let (outcome, allocated_snapshot) = match &settlement.attribution {
112 IcSnapshotUploadAttribution::Applied { snapshot_id, .. } => {
113 request.mutation().validate_data_destination(snapshot_id)?;
114 if !candidates
115 .iter()
116 .any(|snapshot| snapshot.id() == snapshot_id)
117 {
118 return Err(IcSnapshotUploadSettlementError::NotNewCandidate);
119 }
120 let index = inventory
121 .snapshots()
122 .binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id.as_slice()))
123 .map_err(|_| IcSnapshotUploadSettlementError::NotNewCandidate)?;
124 (ObservationOutcomeRecord::Applied, Some(index))
125 }
126 IcSnapshotUploadAttribution::NotApplied { .. } => {
127 (ObservationOutcomeRecord::NotApplied, None)
128 }
129 IcSnapshotUploadAttribution::Unresolved { .. } => {
130 (ObservationOutcomeRecord::Uncertain, None)
131 }
132 };
133 Ok(IcSnapshotUploadSettlementView {
134 observation,
135 baseline,
136 settlement,
137 allocated_snapshot,
138 outcome,
139 })
140}
141
142#[derive(Debug, Error)]
144pub enum IcSnapshotUploadSettlementError {
145 #[error(transparent)]
147 Observation(#[from] IcObservationAssociationError),
148 #[error(transparent)]
150 Baseline(#[from] SnapshotInventoryDeltaError),
151 #[error(transparent)]
153 Destination(#[from] IcSnapshotUploadError),
154 #[error("metadata settlement requires snapshot inventory")]
156 WrongInventory,
157 #[error("metadata settlement authority differs")]
159 AuthorityMismatch,
160 #[error("metadata settlement attempts differ")]
162 AttemptMismatch,
163 #[error("metadata settlement challenge differs")]
165 ChallengeMismatch,
166 #[error("metadata settlement baseline differs")]
168 BaselineMismatch,
169 #[error("metadata settlement inventory differs")]
171 InventoryMismatch,
172 #[error("metadata settlement observation evidence differs")]
174 ObservationEvidenceMismatch,
175 #[error("metadata settlement ID is not a new candidate")]
177 NotNewCandidate,
178}
179
180#[cfg(test)]
181mod tests;