ic_backup/model/ic_observation/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord, MAX_OPERATION_ATTEMPTS},
6 ic_lifecycle_reply::MAX_IC_LIFECYCLE_REPLY_BYTES,
7 ic_request::{IcManagementRequestRecord, IcRequestError},
8 ic_snapshot_reply::MAX_IC_SNAPSHOT_REPLY_BYTES,
9 operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
10};
11use std::fmt;
12use thiserror::Error;
13
14pub const MAX_IC_OBSERVATION_REPLY_BYTES: usize =
16 if MAX_IC_SNAPSHOT_REPLY_BYTES < MAX_IC_LIFECYCLE_REPLY_BYTES {
17 MAX_IC_SNAPSHOT_REPLY_BYTES
18 } else {
19 MAX_IC_LIFECYCLE_REPLY_BYTES
20 };
21
22#[derive(Debug)]
28pub struct IcObservationRequest<'a> {
29 plan: &'a OperationPlanRecord,
30 mutation: &'a IcManagementRequestRecord,
31 payload: &'a IcManagementRequestRecord,
32 authority: AttemptAuthorityRecord,
33 mutation_attempt: u32,
34 observation_attempt: u32,
35}
36
37impl<'a> IcObservationRequest<'a> {
38 pub fn new(
45 plan: &'a OperationPlanRecord,
46 operation_sequence: u64,
47 journal: &AttemptJournalRecord,
48 mutation: &'a IcManagementRequestRecord,
49 payload: &'a IcManagementRequestRecord,
50 ) -> Result<Self, IcObservationRequestError> {
51 let authority = plan.attempt_authority(operation_sequence)?;
52 if journal.authority() != &authority {
53 return Err(IcObservationRequestError::AuthorityMismatch);
54 }
55 mutation.validate_mutation_binding(authority.binding())?;
56 payload.validate_observation_binding(authority.binding(), &payload.digest())?;
57 let (mutation_attempt, observation_attempt) = pending_attempts(journal)?;
58 let request = Self {
59 plan,
60 mutation,
61 payload,
62 authority,
63 mutation_attempt,
64 observation_attempt,
65 };
66 request.validate_journal(journal)?;
67 Ok(request)
68 }
69 #[must_use]
71 pub const fn plan(&self) -> &OperationPlanRecord {
72 self.plan
73 }
74 #[must_use]
76 pub const fn mutation(&self) -> &'a IcManagementRequestRecord {
77 self.mutation
78 }
79 #[must_use]
81 pub const fn payload(&self) -> &'a IcManagementRequestRecord {
82 self.payload
83 }
84 #[must_use]
86 pub const fn authority(&self) -> &AttemptAuthorityRecord {
87 &self.authority
88 }
89 #[must_use]
91 pub const fn mutation_attempt(&self) -> u32 {
92 self.mutation_attempt
93 }
94 #[must_use]
96 pub const fn observation_attempt(&self) -> u32 {
97 self.observation_attempt
98 }
99 pub fn validate_journal(
103 &self,
104 journal: &AttemptJournalRecord,
105 ) -> Result<(), IcObservationRequestError> {
106 ObservationReservation {
107 authority: &self.authority,
108 mutation_attempt: self.mutation_attempt,
109 observation_attempt: self.observation_attempt,
110 request: self.payload.digest(),
111 }
112 .validate(journal)
113 }
114}
115
116pub(crate) struct ObservationReservation<'a> {
117 pub authority: &'a AttemptAuthorityRecord,
118 pub mutation_attempt: u32,
119 pub observation_attempt: u32,
120 pub request: ArtifactChecksumRecord,
121}
122
123pub(crate) fn pending_attempts(
125 journal: &AttemptJournalRecord,
126) -> Result<(u32, u32), IcObservationRequestError> {
127 let current = journal.view();
128 Ok((
129 current
130 .pending_mutation
131 .ok_or(IcObservationRequestError::NoPendingMutation)?,
132 current
133 .pending_observation
134 .ok_or(IcObservationRequestError::NoPendingObservation)?,
135 ))
136}
137
138impl ObservationReservation<'_> {
139 pub(crate) fn validate(
140 &self,
141 journal: &AttemptJournalRecord,
142 ) -> Result<(), IcObservationRequestError> {
143 if journal.authority() != self.authority {
144 return Err(IcObservationRequestError::AuthorityMismatch);
145 }
146 let current = journal.view();
147 if current.pending_mutation != Some(self.mutation_attempt) {
148 return Err(IcObservationRequestError::MutationMismatch);
149 }
150 if current.pending_observation != Some(self.observation_attempt) {
151 return Err(IcObservationRequestError::ObservationMismatch);
152 }
153 if journal.pending_observation_request() != Some(self.request.hash()) {
154 return Err(IcObservationRequestError::RequestMismatch);
155 }
156 Ok(())
157 }
158}
159
160#[derive(Clone)]
162pub struct IcObservationResponseInput {
163 pub authority: ArtifactChecksumRecord,
165 pub mutation_attempt: u32,
167 pub observation_attempt: u32,
169 pub request: ArtifactChecksumRecord,
171 pub context: PlanContextRecord,
173 pub target: String,
175 pub reply: Vec<u8>,
177 pub evidence: ArtifactChecksumRecord,
179}
180
181#[derive(Clone)]
183pub struct IcObservationResponse {
184 input: IcObservationResponseInput,
185}
186impl IcObservationResponse {
187 pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
191 validate_response_input(&mut input, MAX_IC_OBSERVATION_REPLY_BYTES)?;
192 Ok(Self { input })
193 }
194 #[must_use]
196 pub const fn input(&self) -> &IcObservationResponseInput {
197 &self.input
198 }
199}
200impl fmt::Debug for IcObservationResponse {
201 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
202 fmt_response_input(&self.input, formatter, "IcObservationResponse")
203 }
204}
205
206pub(crate) fn validate_response_input(
207 input: &mut IcObservationResponseInput,
208 max_reply_bytes: usize,
209) -> Result<(), IcObservationResponseError> {
210 if input.mutation_attempt == 0
211 || input.observation_attempt <= input.mutation_attempt
212 || input.observation_attempt > MAX_OPERATION_ATTEMPTS
213 {
214 return Err(IcObservationResponseError::InvalidAttempts);
215 }
216 if input.reply.len() > max_reply_bytes {
217 return Err(IcObservationResponseError::ReplyTooLarge);
218 }
219 input.target = crate::model::principal::canonical_text(&input.target)
220 .ok_or(IcObservationResponseError::InvalidTarget)?;
221 Ok(())
222}
223
224pub(crate) fn fmt_response_input(
225 input: &IcObservationResponseInput,
226 formatter: &mut fmt::Formatter<'_>,
227 name: &str,
228) -> fmt::Result {
229 formatter
230 .debug_struct(name)
231 .field("authority", &input.authority)
232 .field("mutation_attempt", &input.mutation_attempt)
233 .field("observation_attempt", &input.observation_attempt)
234 .field("request", &input.request)
235 .field("target", &input.target)
236 .field("reply_bytes", &input.reply.len())
237 .finish_non_exhaustive()
238}
239
240#[derive(Debug, Error)]
242pub enum IcObservationRequestError {
243 #[error("IC observation original authority mismatch")]
245 AuthorityMismatch,
246 #[error("IC observation requires a pending original mutation")]
248 NoPendingMutation,
249 #[error("IC observation requires a pending original observation")]
251 NoPendingObservation,
252 #[error("IC observation original mutation mismatch")]
254 MutationMismatch,
255 #[error("IC observation original attempt mismatch")]
257 ObservationMismatch,
258 #[error("IC observation reserved request mismatch")]
260 RequestMismatch,
261 #[error(transparent)]
263 Payload(#[from] IcRequestError),
264 #[error(transparent)]
266 Plan(#[from] OperationPlanError),
267}
268
269#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
271pub enum IcObservationResponseError {
272 #[error("invalid IC observation response attempts")]
274 InvalidAttempts,
275 #[error("IC observation response reply too large")]
277 ReplyTooLarge,
278 #[error("invalid IC observation response target")]
280 InvalidTarget,
281}
282
283mod capture_settlement;
284pub use capture_settlement::{IcCaptureAttribution, IcCaptureSettlement};
285
286mod settlement;
287pub use settlement::{IcLifecycleAttribution, IcLifecycleSettlement};
288
289#[cfg(test)]
290mod tests;