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