ic_backup/policy/ic_observation/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
6 ic_lifecycle_reply::{IcLifecycleReply, IcLifecycleReplyError},
7 ic_observation::{
8 IcObservationRequest, IcObservationRequestError, IcObservationResponse,
9 IcObservationResponseInput,
10 },
11 ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
12 ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply, IcSnapshotReplyError},
13 operation_plan::PlanContextRecord,
14};
15use thiserror::Error;
16
17#[derive(Debug)]
19pub enum IcObservationReplyView<'a> {
20 Inventory(IcSnapshotReply<'a>),
22 Status(IcLifecycleReply<'a>),
24}
25
26#[derive(Debug)]
28pub struct IcObservationResponseView<'a> {
29 response: &'a IcObservationResponse,
30 reply: IcObservationReplyView<'a>,
31}
32impl<'a> IcObservationResponseView<'a> {
33 #[must_use]
35 pub const fn response(&self) -> &'a IcObservationResponse {
36 self.response
37 }
38 #[must_use]
40 pub const fn reply(&self) -> &IcObservationReplyView<'a> {
41 &self.reply
42 }
43
44 pub(super) fn inventory_snapshot(&self, index: usize) -> Option<&IcSnapshotInfo> {
47 match &self.reply {
48 IcObservationReplyView::Inventory(reply) => reply.snapshots().get(index),
49 IcObservationReplyView::Status(_) => None,
50 }
51 }
52}
53
54pub fn validate_response<'a>(
64 request: &IcObservationRequest<'a>,
65 journal: &AttemptJournalRecord,
66 response: &'a IcObservationResponse,
67) -> Result<IcObservationResponseView<'a>, IcObservationAssociationError> {
68 request.validate_journal(journal)?;
69 validate_association(
70 &ObservationAssociation {
71 authority: request.authority(),
72 mutation_attempt: request.mutation_attempt(),
73 observation_attempt: request.observation_attempt(),
74 payload: request.payload(),
75 context: request.plan().context(),
76 },
77 response,
78 )
79}
80
81pub(crate) struct ObservationAssociation<'binding, 'payload> {
82 pub authority: &'binding AttemptAuthorityRecord,
83 pub mutation_attempt: u32,
84 pub observation_attempt: u32,
85 pub payload: &'payload IcManagementRequestRecord,
86 pub context: &'binding PlanContextRecord,
87}
88
89pub(crate) fn validate_association<'a>(
90 binding: &ObservationAssociation<'_, 'a>,
91 response: &'a IcObservationResponse,
92) -> Result<IcObservationResponseView<'a>, IcObservationAssociationError> {
93 validate_claims(
94 &ObservationClaims {
95 authority: binding.authority,
96 mutation_attempt: binding.mutation_attempt,
97 observation_attempt: binding.observation_attempt,
98 request: binding.payload.digest(),
99 context: binding.context,
100 target: binding.payload.target(),
101 },
102 response.input(),
103 )?;
104 let reply = match binding.payload.method() {
105 IcManagementMethodRecord::ListCanisterSnapshots => IcObservationReplyView::Inventory(
106 IcSnapshotReply::decode(binding.payload, &response.input().reply)?,
107 ),
108 _ => IcObservationReplyView::Status(IcLifecycleReply::decode(
110 binding.payload,
111 &response.input().reply,
112 )?),
113 };
114 Ok(IcObservationResponseView { response, reply })
115}
116
117pub(crate) struct ObservationClaims<'a> {
118 pub authority: &'a AttemptAuthorityRecord,
119 pub mutation_attempt: u32,
120 pub observation_attempt: u32,
121 pub request: ArtifactChecksumRecord,
122 pub context: &'a PlanContextRecord,
123 pub target: &'a str,
124}
125
126pub(crate) fn validate_claims(
127 binding: &ObservationClaims<'_>,
128 input: &IcObservationResponseInput,
129) -> Result<(), IcObservationAssociationError> {
130 if input.authority != binding.authority.digest() {
131 return Err(IcObservationAssociationError::AuthorityMismatch);
132 }
133 if input.mutation_attempt != binding.mutation_attempt
134 || input.observation_attempt != binding.observation_attempt
135 {
136 return Err(IcObservationAssociationError::AttemptMismatch);
137 }
138 if input.request != binding.request {
139 return Err(IcObservationAssociationError::RequestMismatch);
140 }
141 if &input.context != binding.context {
142 return Err(IcObservationAssociationError::ContextMismatch);
143 }
144 if input.target != binding.target {
145 return Err(IcObservationAssociationError::TargetMismatch);
146 }
147 Ok(())
148}
149
150#[derive(Debug, Error)]
152pub enum IcObservationAssociationError {
153 #[error(transparent)]
155 Reservation(#[from] IcObservationRequestError),
156 #[error("IC observation response authority mismatch")]
158 AuthorityMismatch,
159 #[error("IC observation response attempt mismatch")]
161 AttemptMismatch,
162 #[error("IC observation response request mismatch")]
164 RequestMismatch,
165 #[error("IC observation response context mismatch")]
167 ContextMismatch,
168 #[error("IC observation response target mismatch")]
170 TargetMismatch,
171 #[error(transparent)]
173 Inventory(#[from] IcSnapshotReplyError),
174 #[error(transparent)]
176 Status(#[from] IcLifecycleReplyError),
177}
178
179mod capture_settlement;
180pub use capture_settlement::{
181 IcCaptureSettlementError, IcCaptureSettlementView, validate_capture_settlement,
182};
183
184mod settlement;
185pub use settlement::{
186 IcLifecycleSettlementError, IcLifecycleSettlementView, validate_lifecycle_settlement,
187};
188
189#[cfg(test)]
190mod tests;