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 crate::policy::settlement_identity::{SettlementIdentity, SettlementIdentityMismatch};
16use thiserror::Error;
17
18#[derive(Debug)]
20pub struct IcLifecycleSettlementView<'a> {
21 observation: IcObservationResponseView<'a>,
22 settlement: &'a IcLifecycleSettlement,
23 outcome: ObservationOutcomeRecord,
24}
25impl<'a> IcLifecycleSettlementView<'a> {
26 #[must_use]
28 pub const fn outcome(&self) -> ObservationOutcomeRecord {
29 self.outcome
30 }
31 #[must_use]
33 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
34 &self.observation
35 }
36 #[must_use]
38 pub const fn settlement(&self) -> &'a IcLifecycleSettlement {
39 self.settlement
40 }
41}
42
43pub fn validate_lifecycle_settlement<'a>(
59 request: &IcObservationRequest<'a>,
60 journal: &AttemptJournalRecord,
61 response: &'a IcObservationResponse,
62 challenge: &ArtifactChecksumRecord,
63 settlement: &'a IcLifecycleSettlement,
64) -> Result<IcLifecycleSettlementView<'a>, IcLifecycleSettlementError> {
65 if !matches!(
66 request.mutation().method(),
67 Method::StopCanister | Method::StartCanister | Method::LoadCanisterSnapshot
68 ) || request.payload().method() != Method::CanisterStatus
69 {
70 return Err(IcLifecycleSettlementError::UnsupportedMethod);
71 }
72 let observation = validate_response(request, journal, response)?;
73 SettlementIdentity {
74 authority: &settlement.authority,
75 mutation_attempt: settlement.mutation_attempt,
76 observation_attempt: settlement.observation_attempt,
77 challenge: &settlement.challenge,
78 }
79 .compare(&SettlementIdentity {
80 authority: &request.authority().digest(),
81 mutation_attempt: request.mutation_attempt(),
82 observation_attempt: request.observation_attempt(),
83 challenge,
84 })?;
85 let IcObservationReplyView::Status(status) = observation.reply() else {
86 return Err(IcLifecycleSettlementError::UnsupportedMethod);
87 };
88 if settlement.status != status.digest() {
89 return Err(IcLifecycleSettlementError::StatusMismatch);
90 }
91 if settlement.observation_evidence != response.input().evidence {
92 return Err(IcLifecycleSettlementError::ObservationEvidenceMismatch);
93 }
94 let outcome = match &settlement.attribution {
95 IcLifecycleAttribution::Applied { .. } => ObservationOutcomeRecord::Applied,
96 IcLifecycleAttribution::NotApplied { .. } => ObservationOutcomeRecord::NotApplied,
97 IcLifecycleAttribution::Unresolved { .. } => ObservationOutcomeRecord::Uncertain,
98 };
99 Ok(IcLifecycleSettlementView {
100 observation,
101 settlement,
102 outcome,
103 })
104}
105
106#[derive(Debug, Error)]
108pub enum IcLifecycleSettlementError {
109 #[error("lifecycle settlement requires original stop/start/load and status observation")]
111 UnsupportedMethod,
112 #[error(transparent)]
114 Observation(#[from] IcObservationAssociationError),
115 #[error("lifecycle settlement original authority differs")]
117 AuthorityMismatch,
118 #[error("lifecycle settlement attempt identities differ")]
120 AttemptMismatch,
121 #[error("lifecycle settlement challenge differs")]
123 ChallengeMismatch,
124 #[error("lifecycle settlement status evidence differs")]
126 StatusMismatch,
127 #[error("lifecycle settlement observation evidence differs")]
129 ObservationEvidenceMismatch,
130}
131
132impl From<SettlementIdentityMismatch> for IcLifecycleSettlementError {
133 fn from(value: SettlementIdentityMismatch) -> Self {
134 match value {
135 SettlementIdentityMismatch::Authority => Self::AuthorityMismatch,
136 SettlementIdentityMismatch::Attempt => Self::AttemptMismatch,
137 SettlementIdentityMismatch::Challenge => Self::ChallengeMismatch,
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests;