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 reservation: ObservationReservation,
33}
34
35impl<'a> IcObservationRequest<'a> {
36 pub fn new(
43 plan: &'a OperationPlanRecord,
44 operation_sequence: u64,
45 journal: &AttemptJournalRecord,
46 mutation: &'a IcManagementRequestRecord,
47 payload: &'a IcManagementRequestRecord,
48 ) -> Result<Self, IcObservationRequestError> {
49 let authority = plan.attempt_authority(operation_sequence)?;
50 if journal.authority() != &authority {
51 return Err(IcObservationRequestError::AuthorityMismatch);
52 }
53 mutation.validate_mutation_binding(authority.binding())?;
54 payload.validate_observation_binding(authority.binding(), &payload.digest())?;
55 let reservation = ObservationReservation::new(authority, journal, payload.digest())?;
56 let request = Self {
57 plan,
58 mutation,
59 payload,
60 reservation,
61 };
62 Ok(request)
63 }
64 #[must_use]
66 pub const fn plan(&self) -> &OperationPlanRecord {
67 self.plan
68 }
69 #[must_use]
71 pub const fn mutation(&self) -> &'a IcManagementRequestRecord {
72 self.mutation
73 }
74 #[must_use]
76 pub const fn payload(&self) -> &'a IcManagementRequestRecord {
77 self.payload
78 }
79 #[must_use]
81 pub const fn authority(&self) -> &AttemptAuthorityRecord {
82 &self.reservation.authority
83 }
84 #[must_use]
86 pub const fn mutation_attempt(&self) -> u32 {
87 self.reservation.mutation_attempt
88 }
89 #[must_use]
91 pub const fn observation_attempt(&self) -> u32 {
92 self.reservation.observation_attempt
93 }
94 pub fn validate_journal(
98 &self,
99 journal: &AttemptJournalRecord,
100 ) -> Result<(), IcObservationRequestError> {
101 self.reservation.validate(journal)
102 }
103}
104
105#[derive(Debug)]
106pub(crate) struct ObservationReservation {
107 pub authority: AttemptAuthorityRecord,
108 pub mutation_attempt: u32,
109 pub observation_attempt: u32,
110 pub request: ArtifactChecksumRecord,
111}
112
113fn pending_attempts(
115 journal: &AttemptJournalRecord,
116) -> Result<(u32, u32), IcObservationRequestError> {
117 Ok((
118 journal
119 .pending_mutation()
120 .ok_or(IcObservationRequestError::NoPendingMutation)?,
121 journal
122 .pending_observation()
123 .ok_or(IcObservationRequestError::NoPendingObservation)?,
124 ))
125}
126
127impl ObservationReservation {
128 pub(crate) fn new(
129 authority: AttemptAuthorityRecord,
130 journal: &AttemptJournalRecord,
131 request: ArtifactChecksumRecord,
132 ) -> Result<Self, IcObservationRequestError> {
133 let (mutation_attempt, observation_attempt) = pending_attempts(journal)?;
134 let reservation = Self {
135 authority,
136 mutation_attempt,
137 observation_attempt,
138 request,
139 };
140 reservation.validate(journal)?;
141 Ok(reservation)
142 }
143
144 pub(crate) fn validate(
145 &self,
146 journal: &AttemptJournalRecord,
147 ) -> Result<(), IcObservationRequestError> {
148 if journal.authority() != &self.authority {
149 return Err(IcObservationRequestError::AuthorityMismatch);
150 }
151 if journal.pending_mutation() != Some(self.mutation_attempt) {
152 return Err(IcObservationRequestError::MutationMismatch);
153 }
154 if journal.pending_observation() != Some(self.observation_attempt) {
155 return Err(IcObservationRequestError::ObservationMismatch);
156 }
157 if journal.pending_observation_request() != Some(self.request.hash()) {
158 return Err(IcObservationRequestError::RequestMismatch);
159 }
160 Ok(())
161 }
162}
163
164#[derive(Clone)]
166pub struct IcObservationResponseInput {
167 pub authority: ArtifactChecksumRecord,
169 pub mutation_attempt: u32,
171 pub observation_attempt: u32,
173 pub request: ArtifactChecksumRecord,
175 pub context: PlanContextRecord,
177 pub target: String,
179 pub reply: Vec<u8>,
181 pub evidence: ArtifactChecksumRecord,
183}
184
185#[derive(Clone)]
187pub struct IcObservationResponse {
188 input: IcObservationResponseInput,
189}
190impl IcObservationResponse {
191 pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
195 validate_response_input(&mut input, MAX_IC_OBSERVATION_REPLY_BYTES)?;
196 Ok(Self { input })
197 }
198 #[must_use]
200 pub const fn input(&self) -> &IcObservationResponseInput {
201 &self.input
202 }
203}
204impl fmt::Debug for IcObservationResponse {
205 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
206 fmt_response_input(&self.input, formatter, "IcObservationResponse")
207 }
208}
209
210pub(crate) fn validate_response_input(
211 input: &mut IcObservationResponseInput,
212 max_reply_bytes: usize,
213) -> Result<(), IcObservationResponseError> {
214 if input.mutation_attempt == 0
215 || input.observation_attempt <= input.mutation_attempt
216 || input.observation_attempt > MAX_OPERATION_ATTEMPTS
217 {
218 return Err(IcObservationResponseError::InvalidAttempts);
219 }
220 if input.reply.len() > max_reply_bytes {
221 return Err(IcObservationResponseError::ReplyTooLarge);
222 }
223 input.target = crate::model::principal::canonical_text(&input.target)
224 .ok_or(IcObservationResponseError::InvalidTarget)?;
225 Ok(())
226}
227
228pub(crate) fn fmt_response_input(
229 input: &IcObservationResponseInput,
230 formatter: &mut fmt::Formatter<'_>,
231 name: &str,
232) -> fmt::Result {
233 formatter
234 .debug_struct(name)
235 .field("authority", &input.authority)
236 .field("mutation_attempt", &input.mutation_attempt)
237 .field("observation_attempt", &input.observation_attempt)
238 .field("request", &input.request)
239 .field("target", &input.target)
240 .field("reply_bytes", &input.reply.len())
241 .finish_non_exhaustive()
242}
243
244#[derive(Debug, Error)]
246pub enum IcObservationRequestError {
247 #[error("IC observation original authority mismatch")]
249 AuthorityMismatch,
250 #[error("IC observation requires a pending original mutation")]
252 NoPendingMutation,
253 #[error("IC observation requires a pending original observation")]
255 NoPendingObservation,
256 #[error("IC observation original mutation mismatch")]
258 MutationMismatch,
259 #[error("IC observation original attempt mismatch")]
261 ObservationMismatch,
262 #[error("IC observation reserved request mismatch")]
264 RequestMismatch,
265 #[error(transparent)]
267 Payload(#[from] IcRequestError),
268 #[error(transparent)]
270 Plan(#[from] OperationPlanError),
271}
272
273#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
275pub enum IcObservationResponseError {
276 #[error("invalid IC observation response attempts")]
278 InvalidAttempts,
279 #[error("IC observation response reply too large")]
281 ReplyTooLarge,
282 #[error("invalid IC observation response target")]
284 InvalidTarget,
285}
286
287mod capture_settlement;
288pub use capture_settlement::{IcCaptureAttribution, IcCaptureSettlement};
289
290mod settlement;
291pub use settlement::{IcLifecycleAttribution, IcLifecycleSettlement};
292
293#[cfg(test)]
294mod tests;