ic_backup/model/ic_mutation/
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_MUTATION_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 IcMutationRequest<'a> {
29 plan: &'a OperationPlanRecord,
30 payload: &'a IcManagementRequestRecord,
31 authority: AttemptAuthorityRecord,
32 mutation_attempt: u32,
33}
34
35impl<'a> IcMutationRequest<'a> {
36 pub fn new(
44 plan: &'a OperationPlanRecord,
45 operation_sequence: u64,
46 journal: &AttemptJournalRecord,
47 payload: &'a IcManagementRequestRecord,
48 ) -> Result<Self, IcMutationRequestError> {
49 let authority = plan.attempt_authority(operation_sequence)?;
50 if journal.authority() != &authority {
51 return Err(IcMutationRequestError::AuthorityMismatch);
52 }
53 payload.validate_mutation_binding(authority.binding())?;
54 let mutation_attempt = journal
55 .view()
56 .pending_mutation
57 .ok_or(IcMutationRequestError::NoPendingMutation)?;
58 let request = Self {
59 plan,
60 payload,
61 authority,
62 mutation_attempt,
63 };
64 request.validate_journal(journal)?;
65 Ok(request)
66 }
67
68 #[must_use]
70 pub const fn plan(&self) -> &OperationPlanRecord {
71 self.plan
72 }
73 #[must_use]
75 pub const fn payload(&self) -> &'a IcManagementRequestRecord {
76 self.payload
77 }
78 #[must_use]
80 pub const fn authority(&self) -> &AttemptAuthorityRecord {
81 &self.authority
82 }
83 #[must_use]
85 pub const fn mutation_attempt(&self) -> u32 {
86 self.mutation_attempt
87 }
88 pub fn validate_journal(
92 &self,
93 journal: &AttemptJournalRecord,
94 ) -> Result<(), IcMutationRequestError> {
95 if journal.authority() != &self.authority {
96 return Err(IcMutationRequestError::AuthorityMismatch);
97 }
98 let current = journal.view();
99 if current.pending_mutation != Some(self.mutation_attempt) {
100 return Err(IcMutationRequestError::MutationMismatch);
101 }
102 if current.pending_observation.is_some() {
103 return Err(IcMutationRequestError::ObservationPending);
104 }
105 Ok(())
106 }
107}
108
109#[derive(Clone)]
111pub struct IcMutationAcknowledgementInput {
112 pub authority: ArtifactChecksumRecord,
114 pub mutation_attempt: u32,
116 pub context: PlanContextRecord,
118 pub target: String,
120 pub reply: Vec<u8>,
122 pub evidence: ArtifactChecksumRecord,
124}
125
126#[derive(Clone)]
132pub struct IcMutationAcknowledgement {
133 input: IcMutationAcknowledgementInput,
134}
135
136impl IcMutationAcknowledgement {
137 pub fn new(
141 mut input: IcMutationAcknowledgementInput,
142 ) -> Result<Self, IcMutationAcknowledgementError> {
143 if input.mutation_attempt == 0 || input.mutation_attempt > MAX_OPERATION_ATTEMPTS {
144 return Err(IcMutationAcknowledgementError::InvalidAttempt);
145 }
146 if input.reply.len() > MAX_IC_MUTATION_REPLY_BYTES {
147 return Err(IcMutationAcknowledgementError::ReplyTooLarge);
148 }
149 input.target = crate::model::principal::canonical_text(&input.target)
150 .ok_or(IcMutationAcknowledgementError::InvalidTarget)?;
151 Ok(Self { input })
152 }
153 #[must_use]
155 pub const fn input(&self) -> &IcMutationAcknowledgementInput {
156 &self.input
157 }
158}
159
160impl fmt::Debug for IcMutationAcknowledgement {
161 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162 formatter
163 .debug_struct("IcMutationAcknowledgement")
164 .field("authority", &self.input.authority)
165 .field("mutation_attempt", &self.input.mutation_attempt)
166 .field("target", &self.input.target)
167 .field("reply_bytes", &self.input.reply.len())
168 .finish_non_exhaustive()
169 }
170}
171
172#[derive(Debug, Error)]
174pub enum IcMutationRequestError {
175 #[error("IC mutation original authority mismatch")]
177 AuthorityMismatch,
178 #[error("IC mutation requires a pending original mutation")]
180 NoPendingMutation,
181 #[error("IC mutation original attempt mismatch")]
183 MutationMismatch,
184 #[error("IC mutation has a pending recovery observation")]
186 ObservationPending,
187 #[error(transparent)]
189 Payload(#[from] IcRequestError),
190 #[error(transparent)]
192 Plan(#[from] OperationPlanError),
193}
194
195#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
197pub enum IcMutationAcknowledgementError {
198 #[error("invalid IC mutation acknowledgement attempt")]
200 InvalidAttempt,
201 #[error("IC mutation acknowledgement reply too large")]
203 ReplyTooLarge,
204 #[error("invalid IC mutation acknowledgement target")]
206 InvalidTarget,
207}
208
209#[cfg(test)]
210mod tests;