ic_backup/model/ic_observation/
mod.rs1use crate::model::attempt_journal::ObservationReservation;
4use crate::model::{
5 artifacts::ArtifactChecksumRecord,
6 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord, MAX_OPERATION_ATTEMPTS},
7 ic_lifecycle_reply::MAX_IC_LIFECYCLE_REPLY_BYTES,
8 ic_request::{IcManagementRequestRecord, IcRequestError},
9 ic_snapshot_reply::MAX_IC_SNAPSHOT_REPLY_BYTES,
10 operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
11};
12use std::fmt;
13use thiserror::Error;
14
15pub const MAX_IC_OBSERVATION_REPLY_BYTES: usize =
17 if MAX_IC_SNAPSHOT_REPLY_BYTES < MAX_IC_LIFECYCLE_REPLY_BYTES {
18 MAX_IC_SNAPSHOT_REPLY_BYTES
19 } else {
20 MAX_IC_LIFECYCLE_REPLY_BYTES
21 };
22
23#[derive(Debug)]
29pub struct IcObservationRequest<'a> {
30 plan: &'a OperationPlanRecord,
31 mutation: &'a IcManagementRequestRecord,
32 payload: &'a IcManagementRequestRecord,
33 reservation: ObservationReservation,
34}
35
36impl<'a> IcObservationRequest<'a> {
37 pub fn new(
44 plan: &'a OperationPlanRecord,
45 operation_sequence: u64,
46 journal: &AttemptJournalRecord,
47 mutation: &'a IcManagementRequestRecord,
48 payload: &'a IcManagementRequestRecord,
49 ) -> Result<Self, IcObservationRequestError> {
50 let authority = plan.attempt_authority(operation_sequence)?;
51 if journal.authority() != &authority {
52 return Err(IcObservationRequestError::AuthorityMismatch);
53 }
54 mutation.validate_mutation_binding(authority.binding())?;
55 payload.validate_observation_binding(authority.binding(), &payload.digest())?;
56 let reservation = ObservationReservation::new(authority, journal, payload.digest())
57 .map_err(crate::model::ic_observation::IcObservationRequestError::from)?;
58 let request = Self {
59 plan,
60 mutation,
61 payload,
62 reservation,
63 };
64 Ok(request)
65 }
66 #[must_use]
68 pub const fn plan(&self) -> &OperationPlanRecord {
69 self.plan
70 }
71 #[must_use]
73 pub const fn mutation(&self) -> &'a IcManagementRequestRecord {
74 self.mutation
75 }
76 #[must_use]
78 pub const fn payload(&self) -> &'a IcManagementRequestRecord {
79 self.payload
80 }
81 #[must_use]
83 pub const fn authority(&self) -> &AttemptAuthorityRecord {
84 &self.reservation.authority
85 }
86 #[must_use]
88 pub const fn mutation_attempt(&self) -> u32 {
89 self.reservation.mutation_attempt
90 }
91 #[must_use]
93 pub const fn observation_attempt(&self) -> u32 {
94 self.reservation.observation_attempt
95 }
96 pub fn validate_journal(
100 &self,
101 journal: &AttemptJournalRecord,
102 ) -> Result<(), IcObservationRequestError> {
103 self.reservation
104 .validate(journal)
105 .map_err(IcObservationRequestError::from)
106 }
107}
108
109#[derive(Clone)]
111pub struct IcObservationResponseInput {
112 pub authority: ArtifactChecksumRecord,
114 pub mutation_attempt: u32,
116 pub observation_attempt: u32,
118 pub request: ArtifactChecksumRecord,
120 pub context: PlanContextRecord,
122 pub target: String,
124 pub reply: Vec<u8>,
126 pub evidence: ArtifactChecksumRecord,
128}
129
130#[derive(Clone)]
132pub struct IcObservationResponse {
133 input: IcObservationResponseInput,
134}
135impl IcObservationResponse {
136 pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
140 validate_response_input(&mut input, MAX_IC_OBSERVATION_REPLY_BYTES)?;
141 Ok(Self { input })
142 }
143 #[must_use]
145 pub const fn input(&self) -> &IcObservationResponseInput {
146 &self.input
147 }
148}
149impl fmt::Debug for IcObservationResponse {
150 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151 fmt_response_input(&self.input, formatter, "IcObservationResponse")
152 }
153}
154
155pub(crate) fn validate_response_input(
156 input: &mut IcObservationResponseInput,
157 max_reply_bytes: usize,
158) -> Result<(), IcObservationResponseError> {
159 if input.mutation_attempt == 0
160 || input.observation_attempt <= input.mutation_attempt
161 || input.observation_attempt > MAX_OPERATION_ATTEMPTS
162 {
163 return Err(IcObservationResponseError::InvalidAttempts);
164 }
165 if input.reply.len() > max_reply_bytes {
166 return Err(IcObservationResponseError::ReplyTooLarge);
167 }
168 input.target = crate::model::principal::canonical_text(&input.target)
169 .ok_or(IcObservationResponseError::InvalidTarget)?;
170 Ok(())
171}
172
173pub(crate) fn fmt_response_input(
174 input: &IcObservationResponseInput,
175 formatter: &mut fmt::Formatter<'_>,
176 name: &str,
177) -> fmt::Result {
178 formatter
179 .debug_struct(name)
180 .field("authority", &input.authority)
181 .field("mutation_attempt", &input.mutation_attempt)
182 .field("observation_attempt", &input.observation_attempt)
183 .field("request", &input.request)
184 .field("target", &input.target)
185 .field("reply_bytes", &input.reply.len())
186 .finish_non_exhaustive()
187}
188
189#[derive(Debug, Error)]
191pub enum IcObservationRequestError {
192 #[error("IC observation original authority mismatch")]
194 AuthorityMismatch,
195 #[error("IC observation requires a pending original mutation")]
197 NoPendingMutation,
198 #[error("IC observation requires a pending original observation")]
200 NoPendingObservation,
201 #[error("IC observation original mutation mismatch")]
203 MutationMismatch,
204 #[error("IC observation original attempt mismatch")]
206 ObservationMismatch,
207 #[error("IC observation reserved request mismatch")]
209 RequestMismatch,
210 #[error(transparent)]
212 Payload(#[from] IcRequestError),
213 #[error(transparent)]
215 Plan(#[from] OperationPlanError),
216}
217
218#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
220pub enum IcObservationResponseError {
221 #[error("invalid IC observation response attempts")]
223 InvalidAttempts,
224 #[error("IC observation response reply too large")]
226 ReplyTooLarge,
227 #[error("invalid IC observation response target")]
229 InvalidTarget,
230}
231
232mod capture_settlement;
233pub use capture_settlement::{IcCaptureAttribution, IcCaptureSettlement};
234
235mod settlement;
236pub use settlement::{IcLifecycleAttribution, IcLifecycleSettlement};
237
238#[cfg(test)]
239mod tests;
240
241impl From<crate::model::attempt_journal::ObservationReservationError>
242 for IcObservationRequestError
243{
244 fn from(error: crate::model::attempt_journal::ObservationReservationError) -> Self {
245 use crate::model::attempt_journal::ObservationReservationError as Reservation;
246 match error {
247 Reservation::AuthorityMismatch => Self::AuthorityMismatch,
248 Reservation::NoPendingMutation => Self::NoPendingMutation,
249 Reservation::NoPendingObservation => Self::NoPendingObservation,
250 Reservation::MutationMismatch => Self::MutationMismatch,
251 Reservation::ObservationMismatch => Self::ObservationMismatch,
252 Reservation::RequestMismatch => Self::RequestMismatch,
253 }
254 }
255}