ic_backup/policy/ic_observation/
mod.rs1use crate::model::{
4 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
5 ic_lifecycle_reply::{IcLifecycleReply, IcLifecycleReplyError},
6 ic_observation::{IcObservationRequest, IcObservationRequestError, IcObservationResponse},
7 ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
8 ic_snapshot_reply::{IcSnapshotReply, IcSnapshotReplyError},
9 operation_plan::PlanContextRecord,
10};
11use thiserror::Error;
12
13#[derive(Debug)]
15pub enum IcObservationReplyView<'a> {
16 Inventory(IcSnapshotReply<'a>),
18 Status(IcLifecycleReply<'a>),
20}
21
22#[derive(Debug)]
24pub struct IcObservationResponseView<'a> {
25 response: &'a IcObservationResponse,
26 reply: IcObservationReplyView<'a>,
27}
28impl<'a> IcObservationResponseView<'a> {
29 #[must_use]
31 pub const fn response(&self) -> &'a IcObservationResponse {
32 self.response
33 }
34 #[must_use]
36 pub const fn reply(&self) -> &IcObservationReplyView<'a> {
37 &self.reply
38 }
39}
40
41pub fn validate_response<'a>(
51 request: &IcObservationRequest<'a>,
52 journal: &AttemptJournalRecord,
53 response: &'a IcObservationResponse,
54) -> Result<IcObservationResponseView<'a>, IcObservationAssociationError> {
55 request.validate_journal(journal)?;
56 validate_association(
57 &ObservationAssociation {
58 authority: request.authority(),
59 mutation_attempt: request.mutation_attempt(),
60 observation_attempt: request.observation_attempt(),
61 payload: request.payload(),
62 context: request.plan().context(),
63 },
64 response,
65 )
66}
67
68pub(crate) struct ObservationAssociation<'binding, 'payload> {
69 pub authority: &'binding AttemptAuthorityRecord,
70 pub mutation_attempt: u32,
71 pub observation_attempt: u32,
72 pub payload: &'payload IcManagementRequestRecord,
73 pub context: &'binding PlanContextRecord,
74}
75
76pub(crate) fn validate_association<'a>(
77 binding: &ObservationAssociation<'_, 'a>,
78 response: &'a IcObservationResponse,
79) -> Result<IcObservationResponseView<'a>, IcObservationAssociationError> {
80 let input = response.input();
81 if input.authority != binding.authority.digest() {
82 return Err(IcObservationAssociationError::AuthorityMismatch);
83 }
84 if input.mutation_attempt != binding.mutation_attempt
85 || input.observation_attempt != binding.observation_attempt
86 {
87 return Err(IcObservationAssociationError::AttemptMismatch);
88 }
89 if input.request != binding.payload.digest() {
90 return Err(IcObservationAssociationError::RequestMismatch);
91 }
92 if &input.context != binding.context {
93 return Err(IcObservationAssociationError::ContextMismatch);
94 }
95 if input.target != binding.payload.target() {
96 return Err(IcObservationAssociationError::TargetMismatch);
97 }
98 let reply = match binding.payload.method() {
99 IcManagementMethodRecord::ListCanisterSnapshots => IcObservationReplyView::Inventory(
100 IcSnapshotReply::decode(binding.payload, &input.reply)?,
101 ),
102 _ => {
104 IcObservationReplyView::Status(IcLifecycleReply::decode(binding.payload, &input.reply)?)
105 }
106 };
107 Ok(IcObservationResponseView { response, reply })
108}
109
110#[derive(Debug, Error)]
112pub enum IcObservationAssociationError {
113 #[error(transparent)]
115 Reservation(#[from] IcObservationRequestError),
116 #[error("IC observation response authority mismatch")]
118 AuthorityMismatch,
119 #[error("IC observation response attempt mismatch")]
121 AttemptMismatch,
122 #[error("IC observation response request mismatch")]
124 RequestMismatch,
125 #[error("IC observation response context mismatch")]
127 ContextMismatch,
128 #[error("IC observation response target mismatch")]
130 TargetMismatch,
131 #[error(transparent)]
133 Inventory(#[from] IcSnapshotReplyError),
134 #[error(transparent)]
136 Status(#[from] IcLifecycleReplyError),
137}
138
139#[cfg(test)]
140mod tests;