ic_backup/model/fence_acquisition/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord, MAX_OPERATION_ATTEMPTS},
6 fence_obligation::{FenceObligationError, FenceObligationRecord},
7 operation_plan::{OperationPlanError, OperationPlanRecord},
8};
9use ic_principal::Principal;
10use std::fmt;
11use thiserror::Error;
12
13pub const MAX_FENCE_ACQUISITION_ARGUMENT_BYTES: usize = 1024 * 1024;
15pub const MAX_FENCE_ACQUISITION_METHOD_BYTES: usize = 128;
17
18#[derive(Clone)]
26pub struct FenceAcquisitionPayload {
27 target: String,
28 target_bytes: Vec<u8>,
29 method: String,
30 arguments: Vec<u8>,
31}
32impl FenceAcquisitionPayload {
33 pub fn new(
37 target: &str,
38 method: &str,
39 arguments: &[u8],
40 ) -> Result<Self, FenceAcquisitionError> {
41 if arguments.len() > MAX_FENCE_ACQUISITION_ARGUMENT_BYTES {
42 return Err(FenceAcquisitionError::ArgumentsTooLarge);
43 }
44 if method.is_empty()
45 || method.len() > MAX_FENCE_ACQUISITION_METHOD_BYTES
46 || !method.bytes().all(|byte| byte.is_ascii_graphic())
47 {
48 return Err(FenceAcquisitionError::InvalidMethod);
49 }
50 let target = crate::model::principal::canonical_text(target)
51 .ok_or(FenceAcquisitionError::InvalidTarget)?;
52 let principal =
53 Principal::from_text(&target).map_err(|_| FenceAcquisitionError::InvalidTarget)?;
54 if principal.as_slice().is_empty() {
55 return Err(FenceAcquisitionError::ManagementReceiver);
56 }
57 Ok(Self {
58 target,
59 target_bytes: principal.as_slice().to_vec(),
60 method: method.into(),
61 arguments: arguments.into(),
62 })
63 }
64 #[must_use]
66 pub fn target(&self) -> &str {
67 &self.target
68 }
69 #[must_use]
71 pub fn method(&self) -> &str {
72 &self.method
73 }
74 #[must_use]
76 pub fn arguments(&self) -> &[u8] {
77 &self.arguments
78 }
79 #[must_use]
82 pub fn digest(&self) -> ArtifactChecksumRecord {
83 let mut bytes = b"ic-backup/application-fence-acquisition/v1\0".to_vec();
84 bytes.push(self.target_bytes.len().to_le_bytes()[0]);
86 bytes.extend_from_slice(&self.target_bytes);
87 bytes.push(1); bytes.extend_from_slice(&u32::from(self.method.len().to_le_bytes()[0]).to_be_bytes());
90 bytes.extend_from_slice(self.method.as_bytes());
91 bytes.extend_from_slice(&(self.arguments.len() as u64).to_be_bytes());
92 bytes.extend_from_slice(&self.arguments);
93 ArtifactChecksumRecord::from_bytes(&bytes)
94 }
95}
96impl fmt::Debug for FenceAcquisitionPayload {
97 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
98 formatter
99 .debug_struct("FenceAcquisitionPayload")
100 .field("target", &self.target)
101 .field("method", &self.method)
102 .field("argument_bytes", &self.arguments.len())
103 .finish_non_exhaustive()
104 }
105}
106
107pub fn original_authority(
114 plan: &OperationPlanRecord,
115 obligation: &FenceObligationRecord,
116 journal: &AttemptJournalRecord,
117 payload: &FenceAcquisitionPayload,
118) -> Result<AttemptAuthorityRecord, FenceAcquisitionError> {
119 obligation.validate_plan(plan)?;
120 let authority = plan.attempt_authority(obligation.acquisition_operation())?;
121 if journal.authority() != &authority {
122 return Err(FenceAcquisitionError::AuthorityMismatch);
123 }
124 if payload.target() != authority.binding().target() {
125 return Err(FenceAcquisitionError::TargetMismatch);
126 }
127 if payload.digest().hash() != authority.binding().request() {
128 return Err(FenceAcquisitionError::PayloadMismatch);
129 }
130 Ok(authority)
131}
132
133#[derive(Debug)]
140pub struct FenceAcquisitionRequest<'a> {
141 plan: &'a OperationPlanRecord,
142 obligation: &'a FenceObligationRecord,
143 payload: &'a FenceAcquisitionPayload,
144 authority: AttemptAuthorityRecord,
145 mutation_attempt: u32,
146}
147impl<'a> FenceAcquisitionRequest<'a> {
148 pub fn new(
152 plan: &'a OperationPlanRecord,
153 obligation: &'a FenceObligationRecord,
154 journal: &AttemptJournalRecord,
155 payload: &'a FenceAcquisitionPayload,
156 ) -> Result<Self, FenceAcquisitionError> {
157 let authority = original_authority(plan, obligation, journal, payload)?;
158 let mutation_attempt = journal
159 .view()
160 .pending_mutation
161 .ok_or(FenceAcquisitionError::NoPendingMutation)?;
162 let request = Self {
163 plan,
164 obligation,
165 payload,
166 authority,
167 mutation_attempt,
168 };
169 request.validate_journal(journal)?;
170 Ok(request)
171 }
172 #[must_use]
174 pub const fn plan(&self) -> &OperationPlanRecord {
175 self.plan
176 }
177 #[must_use]
179 pub const fn obligation(&self) -> &FenceObligationRecord {
180 self.obligation
181 }
182 #[must_use]
184 pub const fn payload(&self) -> &FenceAcquisitionPayload {
185 self.payload
186 }
187 #[must_use]
189 pub const fn authority(&self) -> &AttemptAuthorityRecord {
190 &self.authority
191 }
192 #[must_use]
194 pub const fn mutation_attempt(&self) -> u32 {
195 self.mutation_attempt
196 }
197 pub fn validate_journal(
201 &self,
202 journal: &AttemptJournalRecord,
203 ) -> Result<(), FenceAcquisitionError> {
204 if journal.authority() != &self.authority {
205 return Err(FenceAcquisitionError::AuthorityMismatch);
206 }
207 let current = journal.view();
208 if current.pending_mutation != Some(self.mutation_attempt) {
209 return Err(FenceAcquisitionError::MutationMismatch);
210 }
211 if current.pending_observation.is_some() {
212 return Err(FenceAcquisitionError::ObservationPending);
213 }
214 Ok(())
215 }
216}
217
218#[derive(Clone, Debug)]
220pub struct FenceAcquisitionAcknowledgement {
221 pub authority: ArtifactChecksumRecord,
223 pub mutation_attempt: u32,
225 pub evidence: ArtifactChecksumRecord,
227}
228impl FenceAcquisitionAcknowledgement {
229 pub fn new(
233 authority: ArtifactChecksumRecord,
234 mutation_attempt: u32,
235 evidence: ArtifactChecksumRecord,
236 ) -> Result<Self, FenceAcquisitionError> {
237 if mutation_attempt == 0 || mutation_attempt > MAX_OPERATION_ATTEMPTS {
238 return Err(FenceAcquisitionError::InvalidAttempt);
239 }
240 Ok(Self {
241 authority,
242 mutation_attempt,
243 evidence,
244 })
245 }
246}
247
248#[derive(Debug, Error)]
250pub enum FenceAcquisitionError {
251 #[error("invalid fence acquisition receiver")]
253 InvalidTarget,
254 #[error("fence acquisition requires an application receiver")]
256 ManagementReceiver,
257 #[error("invalid fence acquisition method")]
259 InvalidMethod,
260 #[error("fence acquisition arguments exceed the input bound")]
262 ArgumentsTooLarge,
263 #[error("fence acquisition original journal authority mismatch")]
265 AuthorityMismatch,
266 #[error("fence acquisition original target mismatch")]
268 TargetMismatch,
269 #[error("fence acquisition original payload mismatch")]
271 PayloadMismatch,
272 #[error("fence acquisition requires a pending mutation reservation")]
274 NoPendingMutation,
275 #[error("fence acquisition pending mutation mismatch")]
277 MutationMismatch,
278 #[error("fence acquisition observation recovery is pending")]
280 ObservationPending,
281 #[error("invalid fence acquisition acknowledgement attempt")]
283 InvalidAttempt,
284 #[error(transparent)]
286 Obligation(#[from] FenceObligationError),
287 #[error(transparent)]
289 Plan(#[from] OperationPlanError),
290}
291
292#[cfg(test)]
293mod tests;