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 std::fmt;
10use thiserror::Error;
11
12pub const MAX_FENCE_ACQUISITION_ARGUMENT_BYTES: usize = 1024 * 1024;
14pub const MAX_FENCE_ACQUISITION_METHOD_BYTES: usize = 128;
16
17#[derive(Clone)]
25pub struct FenceAcquisitionPayload {
26 target: String,
27 target_bytes: Vec<u8>,
28 method: String,
29 arguments: Vec<u8>,
30}
31impl FenceAcquisitionPayload {
32 pub fn new(
36 target: &str,
37 method: &str,
38 arguments: &[u8],
39 ) -> Result<Self, FenceAcquisitionError> {
40 if arguments.len() > MAX_FENCE_ACQUISITION_ARGUMENT_BYTES {
41 return Err(FenceAcquisitionError::ArgumentsTooLarge);
42 }
43 if method.is_empty()
44 || method.len() > MAX_FENCE_ACQUISITION_METHOD_BYTES
45 || !method.bytes().all(|byte| byte.is_ascii_graphic())
46 {
47 return Err(FenceAcquisitionError::InvalidMethod);
48 }
49 let (target, principal) = crate::model::principal::canonical_parts(target)
50 .ok_or(FenceAcquisitionError::InvalidTarget)?;
51 if principal.as_slice().is_empty() {
52 return Err(FenceAcquisitionError::ManagementReceiver);
53 }
54 Ok(Self {
55 target,
56 target_bytes: principal.as_slice().to_vec(),
57 method: method.into(),
58 arguments: arguments.into(),
59 })
60 }
61 #[must_use]
63 pub fn target(&self) -> &str {
64 &self.target
65 }
66 #[must_use]
68 pub fn method(&self) -> &str {
69 &self.method
70 }
71 #[must_use]
73 pub fn arguments(&self) -> &[u8] {
74 &self.arguments
75 }
76 #[must_use]
79 pub fn digest(&self) -> ArtifactChecksumRecord {
80 let mut bytes = b"ic-backup/application-fence-acquisition/v1\0".to_vec();
81 bytes.push(self.target_bytes.len().to_le_bytes()[0]);
83 bytes.extend_from_slice(&self.target_bytes);
84 bytes.push(1); bytes.extend_from_slice(&u32::from(self.method.len().to_le_bytes()[0]).to_be_bytes());
87 bytes.extend_from_slice(self.method.as_bytes());
88 bytes.extend_from_slice(&(self.arguments.len() as u64).to_be_bytes());
89 bytes.extend_from_slice(&self.arguments);
90 ArtifactChecksumRecord::from_bytes(&bytes)
91 }
92}
93impl fmt::Debug for FenceAcquisitionPayload {
94 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
95 formatter
96 .debug_struct("FenceAcquisitionPayload")
97 .field("target", &self.target)
98 .field("method", &self.method)
99 .field("argument_bytes", &self.arguments.len())
100 .finish_non_exhaustive()
101 }
102}
103
104pub fn original_authority(
111 plan: &OperationPlanRecord,
112 obligation: &FenceObligationRecord,
113 journal: &AttemptJournalRecord,
114 payload: &FenceAcquisitionPayload,
115) -> Result<AttemptAuthorityRecord, FenceAcquisitionError> {
116 obligation.validate_plan(plan)?;
117 let authority = plan.attempt_authority(obligation.acquisition_operation())?;
118 if journal.authority() != &authority {
119 return Err(FenceAcquisitionError::AuthorityMismatch);
120 }
121 if payload.target() != authority.binding().target() {
122 return Err(FenceAcquisitionError::TargetMismatch);
123 }
124 if payload.digest().hash() != authority.binding().request() {
125 return Err(FenceAcquisitionError::PayloadMismatch);
126 }
127 Ok(authority)
128}
129
130#[derive(Debug)]
137pub struct FenceAcquisitionRequest<'a> {
138 plan: &'a OperationPlanRecord,
139 obligation: &'a FenceObligationRecord,
140 payload: &'a FenceAcquisitionPayload,
141 authority: AttemptAuthorityRecord,
142 mutation_attempt: u32,
143}
144impl<'a> FenceAcquisitionRequest<'a> {
145 pub fn new(
149 plan: &'a OperationPlanRecord,
150 obligation: &'a FenceObligationRecord,
151 journal: &AttemptJournalRecord,
152 payload: &'a FenceAcquisitionPayload,
153 ) -> Result<Self, FenceAcquisitionError> {
154 let authority = original_authority(plan, obligation, journal, payload)?;
155 let mutation_attempt = journal
156 .view()
157 .pending_mutation
158 .ok_or(FenceAcquisitionError::NoPendingMutation)?;
159 let request = Self {
160 plan,
161 obligation,
162 payload,
163 authority,
164 mutation_attempt,
165 };
166 request.validate_journal(journal)?;
167 Ok(request)
168 }
169 #[must_use]
171 pub const fn plan(&self) -> &OperationPlanRecord {
172 self.plan
173 }
174 #[must_use]
176 pub const fn obligation(&self) -> &FenceObligationRecord {
177 self.obligation
178 }
179 #[must_use]
181 pub const fn payload(&self) -> &FenceAcquisitionPayload {
182 self.payload
183 }
184 #[must_use]
186 pub const fn authority(&self) -> &AttemptAuthorityRecord {
187 &self.authority
188 }
189 #[must_use]
191 pub const fn mutation_attempt(&self) -> u32 {
192 self.mutation_attempt
193 }
194 pub fn validate_journal(
198 &self,
199 journal: &AttemptJournalRecord,
200 ) -> Result<(), FenceAcquisitionError> {
201 if journal.authority() != &self.authority {
202 return Err(FenceAcquisitionError::AuthorityMismatch);
203 }
204 let current = journal.view();
205 if current.pending_mutation != Some(self.mutation_attempt) {
206 return Err(FenceAcquisitionError::MutationMismatch);
207 }
208 if current.pending_observation.is_some() {
209 return Err(FenceAcquisitionError::ObservationPending);
210 }
211 Ok(())
212 }
213}
214
215#[derive(Clone, Debug)]
217pub struct FenceAcquisitionAcknowledgement {
218 pub authority: ArtifactChecksumRecord,
220 pub mutation_attempt: u32,
222 pub evidence: ArtifactChecksumRecord,
224}
225impl FenceAcquisitionAcknowledgement {
226 pub fn new(
230 authority: ArtifactChecksumRecord,
231 mutation_attempt: u32,
232 evidence: ArtifactChecksumRecord,
233 ) -> Result<Self, FenceAcquisitionError> {
234 if mutation_attempt == 0 || mutation_attempt > MAX_OPERATION_ATTEMPTS {
235 return Err(FenceAcquisitionError::InvalidAttempt);
236 }
237 Ok(Self {
238 authority,
239 mutation_attempt,
240 evidence,
241 })
242 }
243}
244
245#[derive(Debug, Error)]
247pub enum FenceAcquisitionError {
248 #[error("invalid fence acquisition receiver")]
250 InvalidTarget,
251 #[error("fence acquisition requires an application receiver")]
253 ManagementReceiver,
254 #[error("invalid fence acquisition method")]
256 InvalidMethod,
257 #[error("fence acquisition arguments exceed the input bound")]
259 ArgumentsTooLarge,
260 #[error("fence acquisition original journal authority mismatch")]
262 AuthorityMismatch,
263 #[error("fence acquisition original target mismatch")]
265 TargetMismatch,
266 #[error("fence acquisition original payload mismatch")]
268 PayloadMismatch,
269 #[error("fence acquisition requires a pending mutation reservation")]
271 NoPendingMutation,
272 #[error("fence acquisition pending mutation mismatch")]
274 MutationMismatch,
275 #[error("fence acquisition observation recovery is pending")]
277 ObservationPending,
278 #[error("invalid fence acquisition acknowledgement attempt")]
280 InvalidAttempt,
281 #[error(transparent)]
283 Obligation(#[from] FenceObligationError),
284 #[error(transparent)]
286 Plan(#[from] OperationPlanError),
287}
288
289#[cfg(test)]
290mod tests;