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 Ok((
128 journal
129 .pending_mutation()
130 .ok_or(IcObservationRequestError::NoPendingMutation)?,
131 journal
132 .pending_observation()
133 .ok_or(IcObservationRequestError::NoPendingObservation)?,
134 ))
135}
136
137impl ObservationReservation<'_> {
138 pub(crate) fn validate(
139 &self,
140 journal: &AttemptJournalRecord,
141 ) -> Result<(), IcObservationRequestError> {
142 if journal.authority() != self.authority {
143 return Err(IcObservationRequestError::AuthorityMismatch);
144 }
145 if journal.pending_mutation() != Some(self.mutation_attempt) {
146 return Err(IcObservationRequestError::MutationMismatch);
147 }
148 if journal.pending_observation() != Some(self.observation_attempt) {
149 return Err(IcObservationRequestError::ObservationMismatch);
150 }
151 if journal.pending_observation_request() != Some(self.request.hash()) {
152 return Err(IcObservationRequestError::RequestMismatch);
153 }
154 Ok(())
155 }
156}
157
158#[derive(Clone)]
160pub struct IcObservationResponseInput {
161 pub authority: ArtifactChecksumRecord,
163 pub mutation_attempt: u32,
165 pub observation_attempt: u32,
167 pub request: ArtifactChecksumRecord,
169 pub context: PlanContextRecord,
171 pub target: String,
173 pub reply: Vec<u8>,
175 pub evidence: ArtifactChecksumRecord,
177}
178
179#[derive(Clone)]
181pub struct IcObservationResponse {
182 input: IcObservationResponseInput,
183}
184impl IcObservationResponse {
185 pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
189 validate_response_input(&mut input, MAX_IC_OBSERVATION_REPLY_BYTES)?;
190 Ok(Self { input })
191 }
192 #[must_use]
194 pub const fn input(&self) -> &IcObservationResponseInput {
195 &self.input
196 }
197}
198impl fmt::Debug for IcObservationResponse {
199 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
200 fmt_response_input(&self.input, formatter, "IcObservationResponse")
201 }
202}
203
204pub(crate) fn validate_response_input(
205 input: &mut IcObservationResponseInput,
206 max_reply_bytes: usize,
207) -> Result<(), IcObservationResponseError> {
208 if input.mutation_attempt == 0
209 || input.observation_attempt <= input.mutation_attempt
210 || input.observation_attempt > MAX_OPERATION_ATTEMPTS
211 {
212 return Err(IcObservationResponseError::InvalidAttempts);
213 }
214 if input.reply.len() > max_reply_bytes {
215 return Err(IcObservationResponseError::ReplyTooLarge);
216 }
217 input.target = crate::model::principal::canonical_text(&input.target)
218 .ok_or(IcObservationResponseError::InvalidTarget)?;
219 Ok(())
220}
221
222pub(crate) fn fmt_response_input(
223 input: &IcObservationResponseInput,
224 formatter: &mut fmt::Formatter<'_>,
225 name: &str,
226) -> fmt::Result {
227 formatter
228 .debug_struct(name)
229 .field("authority", &input.authority)
230 .field("mutation_attempt", &input.mutation_attempt)
231 .field("observation_attempt", &input.observation_attempt)
232 .field("request", &input.request)
233 .field("target", &input.target)
234 .field("reply_bytes", &input.reply.len())
235 .finish_non_exhaustive()
236}
237
238#[derive(Debug, Error)]
240pub enum IcObservationRequestError {
241 #[error("IC observation original authority mismatch")]
243 AuthorityMismatch,
244 #[error("IC observation requires a pending original mutation")]
246 NoPendingMutation,
247 #[error("IC observation requires a pending original observation")]
249 NoPendingObservation,
250 #[error("IC observation original mutation mismatch")]
252 MutationMismatch,
253 #[error("IC observation original attempt mismatch")]
255 ObservationMismatch,
256 #[error("IC observation reserved request mismatch")]
258 RequestMismatch,
259 #[error(transparent)]
261 Payload(#[from] IcRequestError),
262 #[error(transparent)]
264 Plan(#[from] OperationPlanError),
265}
266
267#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
269pub enum IcObservationResponseError {
270 #[error("invalid IC observation response attempts")]
272 InvalidAttempts,
273 #[error("IC observation response reply too large")]
275 ReplyTooLarge,
276 #[error("invalid IC observation response target")]
278 InvalidTarget,
279}
280
281mod capture_settlement;
282pub use capture_settlement::{IcCaptureAttribution, IcCaptureSettlement};
283
284mod settlement;
285pub use settlement::{IcLifecycleAttribution, IcLifecycleSettlement};
286
287#[cfg(test)]
288mod tests;