ic_backup/policy/ic_observation/settlement/
mod.rs1use super::{
4 IcObservationAssociationError, IcObservationReplyView, IcObservationResponseView,
5 validate_response,
6};
7use crate::model::{
8 artifacts::ArtifactChecksumRecord,
9 attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
10 ic_observation::{
11 IcLifecycleAttribution, IcLifecycleSettlement, IcObservationRequest, IcObservationResponse,
12 },
13 ic_request::IcManagementMethodRecord as Method,
14};
15use thiserror::Error;
16
17#[derive(Debug)]
19pub struct IcLifecycleSettlementView<'a> {
20 observation: IcObservationResponseView<'a>,
21 settlement: &'a IcLifecycleSettlement,
22 outcome: ObservationOutcomeRecord,
23}
24impl<'a> IcLifecycleSettlementView<'a> {
25 #[must_use]
27 pub const fn outcome(&self) -> ObservationOutcomeRecord {
28 self.outcome
29 }
30 #[must_use]
32 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
33 &self.observation
34 }
35 #[must_use]
37 pub const fn settlement(&self) -> &'a IcLifecycleSettlement {
38 self.settlement
39 }
40}
41
42pub fn validate_lifecycle_settlement<'a>(
58 request: &IcObservationRequest<'a>,
59 journal: &AttemptJournalRecord,
60 response: &'a IcObservationResponse,
61 challenge: &ArtifactChecksumRecord,
62 settlement: &'a IcLifecycleSettlement,
63) -> Result<IcLifecycleSettlementView<'a>, IcLifecycleSettlementError> {
64 if !matches!(
65 request.mutation().method(),
66 Method::StopCanister | Method::StartCanister | Method::LoadCanisterSnapshot
67 ) || request.payload().method() != Method::CanisterStatus
68 {
69 return Err(IcLifecycleSettlementError::UnsupportedMethod);
70 }
71 let observation = validate_response(request, journal, response)?;
72 if settlement.authority != request.authority().digest() {
73 return Err(IcLifecycleSettlementError::AuthorityMismatch);
74 }
75 if settlement.mutation_attempt != request.mutation_attempt()
76 || settlement.observation_attempt != request.observation_attempt()
77 {
78 return Err(IcLifecycleSettlementError::AttemptMismatch);
79 }
80 if &settlement.challenge != challenge {
81 return Err(IcLifecycleSettlementError::ChallengeMismatch);
82 }
83 let IcObservationReplyView::Status(status) = observation.reply() else {
84 return Err(IcLifecycleSettlementError::UnsupportedMethod);
85 };
86 if settlement.status != status.digest() {
87 return Err(IcLifecycleSettlementError::StatusMismatch);
88 }
89 if settlement.observation_evidence != response.input().evidence {
90 return Err(IcLifecycleSettlementError::ObservationEvidenceMismatch);
91 }
92 let outcome = match &settlement.attribution {
93 IcLifecycleAttribution::Applied { .. } => ObservationOutcomeRecord::Applied,
94 IcLifecycleAttribution::NotApplied { .. } => ObservationOutcomeRecord::NotApplied,
95 IcLifecycleAttribution::Unresolved { .. } => ObservationOutcomeRecord::Uncertain,
96 };
97 Ok(IcLifecycleSettlementView {
98 observation,
99 settlement,
100 outcome,
101 })
102}
103
104#[derive(Debug, Error)]
106pub enum IcLifecycleSettlementError {
107 #[error("lifecycle settlement requires original stop/start/load and status observation")]
109 UnsupportedMethod,
110 #[error(transparent)]
112 Observation(#[from] IcObservationAssociationError),
113 #[error("lifecycle settlement original authority differs")]
115 AuthorityMismatch,
116 #[error("lifecycle settlement attempt identities differ")]
118 AttemptMismatch,
119 #[error("lifecycle settlement challenge differs")]
121 ChallengeMismatch,
122 #[error("lifecycle settlement status evidence differs")]
124 StatusMismatch,
125 #[error("lifecycle settlement observation evidence differs")]
127 ObservationEvidenceMismatch,
128}
129
130#[cfg(test)]
131mod tests;