ic_backup/model/fence_acquisition/
mod.rs1use crate::model::application_payload::ApplicationPayloadError as Payload;
4use crate::model::attempt_journal::MutationReservationError as Reservation;
5use crate::model::evidence_digest::EvidenceDigest;
6use crate::model::{
7 artifacts::ArtifactChecksumRecord,
8 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord, MAX_OPERATION_ATTEMPTS},
9 fence_obligation::{FenceObligationError, FenceObligationRecord},
10 operation_plan::{OperationPlanError, OperationPlanRecord},
11};
12use std::fmt;
13use thiserror::Error;
14
15pub const MAX_FENCE_ACQUISITION_ARGUMENT_BYTES: usize = 1024 * 1024;
17pub const MAX_FENCE_ACQUISITION_METHOD_BYTES: usize = 128;
19
20#[derive(Clone)]
28pub struct FenceAcquisitionPayload {
29 target: String,
30 target_bytes: Vec<u8>,
31 method: String,
32 arguments: Vec<u8>,
33}
34impl FenceAcquisitionPayload {
35 pub fn new(
39 target: &str,
40 method: &str,
41 arguments: &[u8],
42 ) -> Result<Self, FenceAcquisitionError> {
43 crate::model::application_payload::validate_bytes(
44 arguments,
45 MAX_FENCE_ACQUISITION_ARGUMENT_BYTES,
46 method,
47 MAX_FENCE_ACQUISITION_METHOD_BYTES,
48 )?;
49 let (target, target_bytes) = crate::model::application_payload::principal(target)?;
50 Ok(Self {
51 target,
52 target_bytes,
53 method: method.into(),
54 arguments: arguments.into(),
55 })
56 }
57 #[must_use]
59 pub fn target(&self) -> &str {
60 &self.target
61 }
62 #[must_use]
64 pub fn method(&self) -> &str {
65 &self.method
66 }
67 #[must_use]
69 pub fn arguments(&self) -> &[u8] {
70 &self.arguments
71 }
72 #[must_use]
75 pub fn digest(&self) -> ArtifactChecksumRecord {
76 let mut bytes = EvidenceDigest::new(b"ic-backup/application-fence-acquisition/v1\0");
77 bytes.bytes_u8(&self.target_bytes);
78 bytes.byte(1); bytes.bytes_u32(self.method.as_bytes());
80 bytes.bytes_u64(&self.arguments);
81 bytes.finish()
82 }
83}
84impl fmt::Debug for FenceAcquisitionPayload {
85 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86 formatter
87 .debug_struct("FenceAcquisitionPayload")
88 .field("target", &self.target)
89 .field("method", &self.method)
90 .field("argument_bytes", &self.arguments.len())
91 .finish_non_exhaustive()
92 }
93}
94
95pub fn original_authority(
102 plan: &OperationPlanRecord,
103 obligation: &FenceObligationRecord,
104 journal: &AttemptJournalRecord,
105 payload: &FenceAcquisitionPayload,
106) -> Result<AttemptAuthorityRecord, FenceAcquisitionError> {
107 obligation.validate_plan(plan)?;
108 let authority = plan.attempt_authority(obligation.acquisition_operation())?;
109 if journal.authority() != &authority {
110 return Err(FenceAcquisitionError::AuthorityMismatch);
111 }
112 if payload.target() != authority.binding().target() {
113 return Err(FenceAcquisitionError::TargetMismatch);
114 }
115 if payload.digest().hash() != authority.binding().request() {
116 return Err(FenceAcquisitionError::PayloadMismatch);
117 }
118 Ok(authority)
119}
120
121#[derive(Debug)]
128pub struct FenceAcquisitionRequest<'a> {
129 plan: &'a OperationPlanRecord,
130 obligation: &'a FenceObligationRecord,
131 payload: &'a FenceAcquisitionPayload,
132 authority: AttemptAuthorityRecord,
133 mutation_attempt: u32,
134}
135impl<'a> FenceAcquisitionRequest<'a> {
136 pub fn new(
140 plan: &'a OperationPlanRecord,
141 obligation: &'a FenceObligationRecord,
142 journal: &AttemptJournalRecord,
143 payload: &'a FenceAcquisitionPayload,
144 ) -> Result<Self, FenceAcquisitionError> {
145 let authority = original_authority(plan, obligation, journal, payload)?;
146 let mutation_attempt = journal
147 .pending_mutation()
148 .ok_or(FenceAcquisitionError::NoPendingMutation)?;
149 let request = Self {
150 plan,
151 obligation,
152 payload,
153 authority,
154 mutation_attempt,
155 };
156 request.validate_journal(journal)?;
157 Ok(request)
158 }
159 #[must_use]
161 pub const fn plan(&self) -> &OperationPlanRecord {
162 self.plan
163 }
164 #[must_use]
166 pub const fn obligation(&self) -> &FenceObligationRecord {
167 self.obligation
168 }
169 #[must_use]
171 pub const fn payload(&self) -> &FenceAcquisitionPayload {
172 self.payload
173 }
174 #[must_use]
176 pub const fn authority(&self) -> &AttemptAuthorityRecord {
177 &self.authority
178 }
179 #[must_use]
181 pub const fn mutation_attempt(&self) -> u32 {
182 self.mutation_attempt
183 }
184 pub fn validate_journal(
188 &self,
189 journal: &AttemptJournalRecord,
190 ) -> Result<(), FenceAcquisitionError> {
191 journal
192 .validate_pending_mutation(&self.authority, self.mutation_attempt)
193 .map_err(Into::into)
194 }
195}
196
197#[derive(Clone, Debug)]
199pub struct FenceAcquisitionAcknowledgement {
200 pub authority: ArtifactChecksumRecord,
202 pub mutation_attempt: u32,
204 pub evidence: ArtifactChecksumRecord,
206}
207impl FenceAcquisitionAcknowledgement {
208 pub fn new(
212 authority: ArtifactChecksumRecord,
213 mutation_attempt: u32,
214 evidence: ArtifactChecksumRecord,
215 ) -> Result<Self, FenceAcquisitionError> {
216 if mutation_attempt == 0 || mutation_attempt > MAX_OPERATION_ATTEMPTS {
217 return Err(FenceAcquisitionError::InvalidAttempt);
218 }
219 Ok(Self {
220 authority,
221 mutation_attempt,
222 evidence,
223 })
224 }
225}
226
227#[derive(Debug, Error)]
229pub enum FenceAcquisitionError {
230 #[error("invalid fence acquisition receiver")]
232 InvalidTarget,
233 #[error("fence acquisition requires an application receiver")]
235 ManagementReceiver,
236 #[error("invalid fence acquisition method")]
238 InvalidMethod,
239 #[error("fence acquisition arguments exceed the input bound")]
241 ArgumentsTooLarge,
242 #[error("fence acquisition original journal authority mismatch")]
244 AuthorityMismatch,
245 #[error("fence acquisition original target mismatch")]
247 TargetMismatch,
248 #[error("fence acquisition original payload mismatch")]
250 PayloadMismatch,
251 #[error("fence acquisition requires a pending mutation reservation")]
253 NoPendingMutation,
254 #[error("fence acquisition pending mutation mismatch")]
256 MutationMismatch,
257 #[error("fence acquisition observation recovery is pending")]
259 ObservationPending,
260 #[error("invalid fence acquisition acknowledgement attempt")]
262 InvalidAttempt,
263 #[error(transparent)]
265 Obligation(#[from] FenceObligationError),
266 #[error(transparent)]
268 Plan(#[from] OperationPlanError),
269}
270
271#[cfg(test)]
272mod tests;
273
274impl From<crate::model::attempt_journal::MutationReservationError> for FenceAcquisitionError {
275 fn from(error: crate::model::attempt_journal::MutationReservationError) -> Self {
276 match error {
277 Reservation::NoPendingMutation => Self::NoPendingMutation,
278 Reservation::AuthorityMismatch => Self::AuthorityMismatch,
279 Reservation::MutationMismatch => Self::MutationMismatch,
280 Reservation::ObservationPending => Self::ObservationPending,
281 }
282 }
283}
284
285impl From<crate::model::application_payload::ApplicationPayloadError> for FenceAcquisitionError {
286 fn from(error: crate::model::application_payload::ApplicationPayloadError) -> Self {
287 match error {
288 Payload::ArgumentsTooLarge => Self::ArgumentsTooLarge,
289 Payload::InvalidMethod => Self::InvalidMethod,
290 Payload::InvalidPrincipal => Self::InvalidTarget,
291 Payload::ManagementPrincipal => Self::ManagementReceiver,
292 }
293 }
294}