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 current = journal.view();
58 let request = Self {
59 plan,
60 mutation,
61 payload,
62 authority,
63 mutation_attempt: current
64 .pending_mutation
65 .ok_or(IcObservationRequestError::NoPendingMutation)?,
66 observation_attempt: current
67 .pending_observation
68 .ok_or(IcObservationRequestError::NoPendingObservation)?,
69 };
70 request.validate_journal(journal)?;
71 Ok(request)
72 }
73 #[must_use]
75 pub const fn plan(&self) -> &OperationPlanRecord {
76 self.plan
77 }
78 #[must_use]
80 pub const fn mutation(&self) -> &'a IcManagementRequestRecord {
81 self.mutation
82 }
83 #[must_use]
85 pub const fn payload(&self) -> &'a IcManagementRequestRecord {
86 self.payload
87 }
88 #[must_use]
90 pub const fn authority(&self) -> &AttemptAuthorityRecord {
91 &self.authority
92 }
93 #[must_use]
95 pub const fn mutation_attempt(&self) -> u32 {
96 self.mutation_attempt
97 }
98 #[must_use]
100 pub const fn observation_attempt(&self) -> u32 {
101 self.observation_attempt
102 }
103 pub fn validate_journal(
107 &self,
108 journal: &AttemptJournalRecord,
109 ) -> Result<(), IcObservationRequestError> {
110 ObservationReservation {
111 authority: &self.authority,
112 mutation_attempt: self.mutation_attempt,
113 observation_attempt: self.observation_attempt,
114 request: self.payload.digest(),
115 }
116 .validate(journal)
117 }
118}
119
120pub(crate) struct ObservationReservation<'a> {
121 pub authority: &'a AttemptAuthorityRecord,
122 pub mutation_attempt: u32,
123 pub observation_attempt: u32,
124 pub request: ArtifactChecksumRecord,
125}
126
127impl ObservationReservation<'_> {
128 pub(crate) fn validate(
129 &self,
130 journal: &AttemptJournalRecord,
131 ) -> Result<(), IcObservationRequestError> {
132 if journal.authority() != self.authority {
133 return Err(IcObservationRequestError::AuthorityMismatch);
134 }
135 let current = journal.view();
136 if current.pending_mutation != Some(self.mutation_attempt) {
137 return Err(IcObservationRequestError::MutationMismatch);
138 }
139 if current.pending_observation != Some(self.observation_attempt) {
140 return Err(IcObservationRequestError::ObservationMismatch);
141 }
142 if journal.pending_observation_request() != Some(self.request.hash()) {
143 return Err(IcObservationRequestError::RequestMismatch);
144 }
145 Ok(())
146 }
147}
148
149#[derive(Clone)]
151pub struct IcObservationResponseInput {
152 pub authority: ArtifactChecksumRecord,
154 pub mutation_attempt: u32,
156 pub observation_attempt: u32,
158 pub request: ArtifactChecksumRecord,
160 pub context: PlanContextRecord,
162 pub target: String,
164 pub reply: Vec<u8>,
166 pub evidence: ArtifactChecksumRecord,
168}
169
170#[derive(Clone)]
172pub struct IcObservationResponse {
173 input: IcObservationResponseInput,
174}
175impl IcObservationResponse {
176 pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
180 validate_response_input(&mut input, MAX_IC_OBSERVATION_REPLY_BYTES)?;
181 Ok(Self { input })
182 }
183 #[must_use]
185 pub const fn input(&self) -> &IcObservationResponseInput {
186 &self.input
187 }
188}
189impl fmt::Debug for IcObservationResponse {
190 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
191 fmt_response_input(&self.input, formatter, "IcObservationResponse")
192 }
193}
194
195pub(crate) fn validate_response_input(
196 input: &mut IcObservationResponseInput,
197 max_reply_bytes: usize,
198) -> Result<(), IcObservationResponseError> {
199 if input.mutation_attempt == 0
200 || input.observation_attempt <= input.mutation_attempt
201 || input.observation_attempt > MAX_OPERATION_ATTEMPTS
202 {
203 return Err(IcObservationResponseError::InvalidAttempts);
204 }
205 if input.reply.len() > max_reply_bytes {
206 return Err(IcObservationResponseError::ReplyTooLarge);
207 }
208 input.target = crate::model::principal::canonical_text(&input.target)
209 .ok_or(IcObservationResponseError::InvalidTarget)?;
210 Ok(())
211}
212
213pub(crate) fn fmt_response_input(
214 input: &IcObservationResponseInput,
215 formatter: &mut fmt::Formatter<'_>,
216 name: &str,
217) -> fmt::Result {
218 formatter
219 .debug_struct(name)
220 .field("authority", &input.authority)
221 .field("mutation_attempt", &input.mutation_attempt)
222 .field("observation_attempt", &input.observation_attempt)
223 .field("request", &input.request)
224 .field("target", &input.target)
225 .field("reply_bytes", &input.reply.len())
226 .finish_non_exhaustive()
227}
228
229#[derive(Debug, Error)]
231pub enum IcObservationRequestError {
232 #[error("IC observation original authority mismatch")]
234 AuthorityMismatch,
235 #[error("IC observation requires a pending original mutation")]
237 NoPendingMutation,
238 #[error("IC observation requires a pending original observation")]
240 NoPendingObservation,
241 #[error("IC observation original mutation mismatch")]
243 MutationMismatch,
244 #[error("IC observation original attempt mismatch")]
246 ObservationMismatch,
247 #[error("IC observation reserved request mismatch")]
249 RequestMismatch,
250 #[error(transparent)]
252 Payload(#[from] IcRequestError),
253 #[error(transparent)]
255 Plan(#[from] OperationPlanError),
256}
257
258#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
260pub enum IcObservationResponseError {
261 #[error("invalid IC observation response attempts")]
263 InvalidAttempts,
264 #[error("IC observation response reply too large")]
266 ReplyTooLarge,
267 #[error("invalid IC observation response target")]
269 InvalidTarget,
270}
271
272mod capture_settlement;
273pub use capture_settlement::{IcCaptureAttribution, IcCaptureSettlement};
274
275mod settlement;
276pub use settlement::{IcLifecycleAttribution, IcLifecycleSettlement};
277
278#[cfg(test)]
279mod tests;