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_for_journal(
51 operation_sequence,
52 journal,
53 IcObservationRequestError::AuthorityMismatch,
54 )?;
55 mutation.validate_mutation_binding(authority.binding())?;
56 payload.validate_observation_binding(authority.binding(), &payload.digest())?;
57 let reservation = ObservationReservation::new(authority, journal, payload.digest())
58 .map_err(crate::model::ic_observation::IcObservationRequestError::from)?;
59 let request = Self {
60 plan,
61 mutation,
62 payload,
63 reservation,
64 };
65 Ok(request)
66 }
67 #[must_use]
69 pub const fn plan(&self) -> &OperationPlanRecord {
70 self.plan
71 }
72 #[must_use]
74 pub const fn mutation(&self) -> &'a IcManagementRequestRecord {
75 self.mutation
76 }
77 #[must_use]
79 pub const fn payload(&self) -> &'a IcManagementRequestRecord {
80 self.payload
81 }
82 #[must_use]
84 pub const fn authority(&self) -> &AttemptAuthorityRecord {
85 &self.reservation.authority
86 }
87 #[must_use]
89 pub const fn mutation_attempt(&self) -> u32 {
90 self.reservation.mutation_attempt
91 }
92 #[must_use]
94 pub const fn observation_attempt(&self) -> u32 {
95 self.reservation.observation_attempt
96 }
97 pub fn validate_journal(
101 &self,
102 journal: &AttemptJournalRecord,
103 ) -> Result<(), IcObservationRequestError> {
104 self.reservation
105 .validate(journal)
106 .map_err(IcObservationRequestError::from)
107 }
108}
109
110#[derive(Clone)]
112pub struct IcObservationResponseInput {
113 pub authority: ArtifactChecksumRecord,
115 pub mutation_attempt: u32,
117 pub observation_attempt: u32,
119 pub request: ArtifactChecksumRecord,
121 pub context: PlanContextRecord,
123 pub target: String,
125 pub reply: Vec<u8>,
127 pub evidence: ArtifactChecksumRecord,
129}
130
131#[derive(Clone)]
133pub struct IcObservationResponse {
134 input: IcObservationResponseInput,
135}
136impl IcObservationResponse {
137 pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
141 validate_response_input(&mut input, MAX_IC_OBSERVATION_REPLY_BYTES)?;
142 Ok(Self { input })
143 }
144 #[must_use]
146 pub const fn input(&self) -> &IcObservationResponseInput {
147 &self.input
148 }
149}
150impl fmt::Debug for IcObservationResponse {
151 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
152 fmt_response_input(&self.input, formatter, "IcObservationResponse")
153 }
154}
155
156pub(crate) fn validate_response_input(
157 input: &mut IcObservationResponseInput,
158 max_reply_bytes: usize,
159) -> Result<(), IcObservationResponseError> {
160 if input.mutation_attempt == 0
161 || input.observation_attempt <= input.mutation_attempt
162 || input.observation_attempt > MAX_OPERATION_ATTEMPTS
163 {
164 return Err(IcObservationResponseError::InvalidAttempts);
165 }
166 if input.reply.len() > max_reply_bytes {
167 return Err(IcObservationResponseError::ReplyTooLarge);
168 }
169 input.target = crate::model::principal::canonical_text(&input.target)
170 .ok_or(IcObservationResponseError::InvalidTarget)?;
171 Ok(())
172}
173
174pub(crate) fn fmt_response_input(
175 input: &IcObservationResponseInput,
176 formatter: &mut fmt::Formatter<'_>,
177 name: &str,
178) -> fmt::Result {
179 formatter
180 .debug_struct(name)
181 .field("authority", &input.authority)
182 .field("mutation_attempt", &input.mutation_attempt)
183 .field("observation_attempt", &input.observation_attempt)
184 .field("request", &input.request)
185 .field("target", &input.target)
186 .field("reply_bytes", &input.reply.len())
187 .finish_non_exhaustive()
188}
189
190#[derive(Debug, Error)]
192pub enum IcObservationRequestError {
193 #[error("IC observation original authority mismatch")]
195 AuthorityMismatch,
196 #[error("IC observation requires a pending original mutation")]
198 NoPendingMutation,
199 #[error("IC observation requires a pending original observation")]
201 NoPendingObservation,
202 #[error("IC observation original mutation mismatch")]
204 MutationMismatch,
205 #[error("IC observation original attempt mismatch")]
207 ObservationMismatch,
208 #[error("IC observation reserved request mismatch")]
210 RequestMismatch,
211 #[error(transparent)]
213 Payload(#[from] IcRequestError),
214 #[error(transparent)]
216 Plan(#[from] OperationPlanError),
217}
218
219#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
221pub enum IcObservationResponseError {
222 #[error("invalid IC observation response attempts")]
224 InvalidAttempts,
225 #[error("IC observation response reply too large")]
227 ReplyTooLarge,
228 #[error("invalid IC observation response target")]
230 InvalidTarget,
231}
232
233mod capture_settlement;
234pub use capture_settlement::{IcCaptureAttribution, IcCaptureSettlement};
235
236mod settlement;
237pub use settlement::{IcLifecycleAttribution, IcLifecycleSettlement};
238
239#[cfg(test)]
240mod tests;
241
242impl From<crate::model::attempt_journal::ObservationReservationError>
243 for IcObservationRequestError
244{
245 fn from(error: crate::model::attempt_journal::ObservationReservationError) -> Self {
246 use crate::model::attempt_journal::ObservationReservationError as Reservation;
247 match error {
248 Reservation::AuthorityMismatch => Self::AuthorityMismatch,
249 Reservation::NoPendingMutation => Self::NoPendingMutation,
250 Reservation::NoPendingObservation => Self::NoPendingObservation,
251 Reservation::MutationMismatch => Self::MutationMismatch,
252 Reservation::ObservationMismatch => Self::ObservationMismatch,
253 Reservation::RequestMismatch => Self::RequestMismatch,
254 }
255 }
256}