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
107#[derive(Debug)]
114pub struct FenceAcquisitionRequest<'a> {
115 plan: &'a OperationPlanRecord,
116 obligation: &'a FenceObligationRecord,
117 payload: &'a FenceAcquisitionPayload,
118 authority: AttemptAuthorityRecord,
119 mutation_attempt: u32,
120}
121impl<'a> FenceAcquisitionRequest<'a> {
122 pub fn new(
126 plan: &'a OperationPlanRecord,
127 obligation: &'a FenceObligationRecord,
128 journal: &AttemptJournalRecord,
129 payload: &'a FenceAcquisitionPayload,
130 ) -> Result<Self, FenceAcquisitionError> {
131 obligation.validate_plan(plan)?;
132 let authority = plan.attempt_authority(obligation.acquisition_operation())?;
133 if journal.authority() != &authority {
134 return Err(FenceAcquisitionError::AuthorityMismatch);
135 }
136 if payload.target() != authority.binding().target() {
137 return Err(FenceAcquisitionError::TargetMismatch);
138 }
139 if payload.digest().hash() != authority.binding().request() {
140 return Err(FenceAcquisitionError::PayloadMismatch);
141 }
142 let mutation_attempt = journal
143 .view()
144 .pending_mutation
145 .ok_or(FenceAcquisitionError::NoPendingMutation)?;
146 let request = Self {
147 plan,
148 obligation,
149 payload,
150 authority,
151 mutation_attempt,
152 };
153 request.validate_journal(journal)?;
154 Ok(request)
155 }
156 #[must_use]
158 pub const fn plan(&self) -> &OperationPlanRecord {
159 self.plan
160 }
161 #[must_use]
163 pub const fn obligation(&self) -> &FenceObligationRecord {
164 self.obligation
165 }
166 #[must_use]
168 pub const fn payload(&self) -> &FenceAcquisitionPayload {
169 self.payload
170 }
171 #[must_use]
173 pub const fn authority(&self) -> &AttemptAuthorityRecord {
174 &self.authority
175 }
176 #[must_use]
178 pub const fn mutation_attempt(&self) -> u32 {
179 self.mutation_attempt
180 }
181 pub fn validate_journal(
185 &self,
186 journal: &AttemptJournalRecord,
187 ) -> Result<(), FenceAcquisitionError> {
188 if journal.authority() != &self.authority {
189 return Err(FenceAcquisitionError::AuthorityMismatch);
190 }
191 let current = journal.view();
192 if current.pending_mutation != Some(self.mutation_attempt) {
193 return Err(FenceAcquisitionError::MutationMismatch);
194 }
195 if current.pending_observation.is_some() {
196 return Err(FenceAcquisitionError::ObservationPending);
197 }
198 Ok(())
199 }
200}
201
202#[derive(Clone, Debug)]
204pub struct FenceAcquisitionAcknowledgement {
205 pub authority: ArtifactChecksumRecord,
207 pub mutation_attempt: u32,
209 pub evidence: ArtifactChecksumRecord,
211}
212impl FenceAcquisitionAcknowledgement {
213 pub fn new(
217 authority: ArtifactChecksumRecord,
218 mutation_attempt: u32,
219 evidence: ArtifactChecksumRecord,
220 ) -> Result<Self, FenceAcquisitionError> {
221 if mutation_attempt == 0 || mutation_attempt > MAX_OPERATION_ATTEMPTS {
222 return Err(FenceAcquisitionError::InvalidAttempt);
223 }
224 Ok(Self {
225 authority,
226 mutation_attempt,
227 evidence,
228 })
229 }
230}
231
232#[derive(Debug, Error)]
234pub enum FenceAcquisitionError {
235 #[error("invalid fence acquisition receiver")]
237 InvalidTarget,
238 #[error("fence acquisition requires an application receiver")]
240 ManagementReceiver,
241 #[error("invalid fence acquisition method")]
243 InvalidMethod,
244 #[error("fence acquisition arguments exceed the input bound")]
246 ArgumentsTooLarge,
247 #[error("fence acquisition original journal authority mismatch")]
249 AuthorityMismatch,
250 #[error("fence acquisition original target mismatch")]
252 TargetMismatch,
253 #[error("fence acquisition original payload mismatch")]
255 PayloadMismatch,
256 #[error("fence acquisition requires a pending mutation reservation")]
258 NoPendingMutation,
259 #[error("fence acquisition pending mutation mismatch")]
261 MutationMismatch,
262 #[error("fence acquisition observation recovery is pending")]
264 ObservationPending,
265 #[error("invalid fence acquisition acknowledgement attempt")]
267 InvalidAttempt,
268 #[error(transparent)]
270 Obligation(#[from] FenceObligationError),
271 #[error(transparent)]
273 Plan(#[from] OperationPlanError),
274}
275
276#[cfg(test)]
277mod tests;