ic_backup/model/fence_acquisition/
mod.rs1use crate::model::evidence_digest::EvidenceDigest;
4use crate::model::{
5 artifacts::ArtifactChecksumRecord,
6 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord, MAX_OPERATION_ATTEMPTS},
7 fence_obligation::{FenceObligationError, FenceObligationRecord},
8 operation_plan::{OperationPlanError, OperationPlanRecord},
9};
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, principal) = crate::model::principal::canonical_parts(target)
51 .ok_or(FenceAcquisitionError::InvalidTarget)?;
52 if principal.as_slice().is_empty() {
53 return Err(FenceAcquisitionError::ManagementReceiver);
54 }
55 Ok(Self {
56 target,
57 target_bytes: principal.as_slice().to_vec(),
58 method: method.into(),
59 arguments: arguments.into(),
60 })
61 }
62 #[must_use]
64 pub fn target(&self) -> &str {
65 &self.target
66 }
67 #[must_use]
69 pub fn method(&self) -> &str {
70 &self.method
71 }
72 #[must_use]
74 pub fn arguments(&self) -> &[u8] {
75 &self.arguments
76 }
77 #[must_use]
80 pub fn digest(&self) -> ArtifactChecksumRecord {
81 let mut bytes = EvidenceDigest::new(b"ic-backup/application-fence-acquisition/v1\0");
82 bytes.byte(self.target_bytes.len().to_le_bytes()[0]);
84 bytes.append(&self.target_bytes);
85 bytes.byte(1); bytes.append(&u32::from(self.method.len().to_le_bytes()[0]).to_be_bytes());
88 bytes.append(self.method.as_bytes());
89 bytes.append(&(self.arguments.len() as u64).to_be_bytes());
90 bytes.append(&self.arguments);
91 bytes.finish()
92 }
93}
94impl fmt::Debug for FenceAcquisitionPayload {
95 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96 formatter
97 .debug_struct("FenceAcquisitionPayload")
98 .field("target", &self.target)
99 .field("method", &self.method)
100 .field("argument_bytes", &self.arguments.len())
101 .finish_non_exhaustive()
102 }
103}
104
105pub fn original_authority(
112 plan: &OperationPlanRecord,
113 obligation: &FenceObligationRecord,
114 journal: &AttemptJournalRecord,
115 payload: &FenceAcquisitionPayload,
116) -> Result<AttemptAuthorityRecord, FenceAcquisitionError> {
117 obligation.validate_plan(plan)?;
118 let authority = plan.attempt_authority(obligation.acquisition_operation())?;
119 if journal.authority() != &authority {
120 return Err(FenceAcquisitionError::AuthorityMismatch);
121 }
122 if payload.target() != authority.binding().target() {
123 return Err(FenceAcquisitionError::TargetMismatch);
124 }
125 if payload.digest().hash() != authority.binding().request() {
126 return Err(FenceAcquisitionError::PayloadMismatch);
127 }
128 Ok(authority)
129}
130
131#[derive(Debug)]
138pub struct FenceAcquisitionRequest<'a> {
139 plan: &'a OperationPlanRecord,
140 obligation: &'a FenceObligationRecord,
141 payload: &'a FenceAcquisitionPayload,
142 authority: AttemptAuthorityRecord,
143 mutation_attempt: u32,
144}
145impl<'a> FenceAcquisitionRequest<'a> {
146 pub fn new(
150 plan: &'a OperationPlanRecord,
151 obligation: &'a FenceObligationRecord,
152 journal: &AttemptJournalRecord,
153 payload: &'a FenceAcquisitionPayload,
154 ) -> Result<Self, FenceAcquisitionError> {
155 let authority = original_authority(plan, obligation, journal, payload)?;
156 let mutation_attempt = journal
157 .view()
158 .pending_mutation
159 .ok_or(FenceAcquisitionError::NoPendingMutation)?;
160 let request = Self {
161 plan,
162 obligation,
163 payload,
164 authority,
165 mutation_attempt,
166 };
167 request.validate_journal(journal)?;
168 Ok(request)
169 }
170 #[must_use]
172 pub const fn plan(&self) -> &OperationPlanRecord {
173 self.plan
174 }
175 #[must_use]
177 pub const fn obligation(&self) -> &FenceObligationRecord {
178 self.obligation
179 }
180 #[must_use]
182 pub const fn payload(&self) -> &FenceAcquisitionPayload {
183 self.payload
184 }
185 #[must_use]
187 pub const fn authority(&self) -> &AttemptAuthorityRecord {
188 &self.authority
189 }
190 #[must_use]
192 pub const fn mutation_attempt(&self) -> u32 {
193 self.mutation_attempt
194 }
195 pub fn validate_journal(
199 &self,
200 journal: &AttemptJournalRecord,
201 ) -> Result<(), FenceAcquisitionError> {
202 if journal.authority() != &self.authority {
203 return Err(FenceAcquisitionError::AuthorityMismatch);
204 }
205 let current = journal.view();
206 if current.pending_mutation != Some(self.mutation_attempt) {
207 return Err(FenceAcquisitionError::MutationMismatch);
208 }
209 if current.pending_observation.is_some() {
210 return Err(FenceAcquisitionError::ObservationPending);
211 }
212 Ok(())
213 }
214}
215
216#[derive(Clone, Debug)]
218pub struct FenceAcquisitionAcknowledgement {
219 pub authority: ArtifactChecksumRecord,
221 pub mutation_attempt: u32,
223 pub evidence: ArtifactChecksumRecord,
225}
226impl FenceAcquisitionAcknowledgement {
227 pub fn new(
231 authority: ArtifactChecksumRecord,
232 mutation_attempt: u32,
233 evidence: ArtifactChecksumRecord,
234 ) -> Result<Self, FenceAcquisitionError> {
235 if mutation_attempt == 0 || mutation_attempt > MAX_OPERATION_ATTEMPTS {
236 return Err(FenceAcquisitionError::InvalidAttempt);
237 }
238 Ok(Self {
239 authority,
240 mutation_attempt,
241 evidence,
242 })
243 }
244}
245
246#[derive(Debug, Error)]
248pub enum FenceAcquisitionError {
249 #[error("invalid fence acquisition receiver")]
251 InvalidTarget,
252 #[error("fence acquisition requires an application receiver")]
254 ManagementReceiver,
255 #[error("invalid fence acquisition method")]
257 InvalidMethod,
258 #[error("fence acquisition arguments exceed the input bound")]
260 ArgumentsTooLarge,
261 #[error("fence acquisition original journal authority mismatch")]
263 AuthorityMismatch,
264 #[error("fence acquisition original target mismatch")]
266 TargetMismatch,
267 #[error("fence acquisition original payload mismatch")]
269 PayloadMismatch,
270 #[error("fence acquisition requires a pending mutation reservation")]
272 NoPendingMutation,
273 #[error("fence acquisition pending mutation mismatch")]
275 MutationMismatch,
276 #[error("fence acquisition observation recovery is pending")]
278 ObservationPending,
279 #[error("invalid fence acquisition acknowledgement attempt")]
281 InvalidAttempt,
282 #[error(transparent)]
284 Obligation(#[from] FenceObligationError),
285 #[error(transparent)]
287 Plan(#[from] OperationPlanError),
288}
289
290#[cfg(test)]
291mod tests;