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_for_journal(
109 obligation.acquisition_operation(),
110 journal,
111 FenceAcquisitionError::AuthorityMismatch,
112 )?;
113 if payload.target() != authority.binding().target() {
114 return Err(FenceAcquisitionError::TargetMismatch);
115 }
116 if payload.digest().hash() != authority.binding().request() {
117 return Err(FenceAcquisitionError::PayloadMismatch);
118 }
119 Ok(authority)
120}
121
122#[derive(Debug)]
129pub struct FenceAcquisitionRequest<'a> {
130 plan: &'a OperationPlanRecord,
131 obligation: &'a FenceObligationRecord,
132 payload: &'a FenceAcquisitionPayload,
133 authority: AttemptAuthorityRecord,
134 mutation_attempt: u32,
135}
136impl<'a> FenceAcquisitionRequest<'a> {
137 pub fn new(
141 plan: &'a OperationPlanRecord,
142 obligation: &'a FenceObligationRecord,
143 journal: &AttemptJournalRecord,
144 payload: &'a FenceAcquisitionPayload,
145 ) -> Result<Self, FenceAcquisitionError> {
146 let authority = original_authority(plan, obligation, journal, payload)?;
147 let mutation_attempt = journal
148 .pending_mutation()
149 .ok_or(FenceAcquisitionError::NoPendingMutation)?;
150 let request = Self {
151 plan,
152 obligation,
153 payload,
154 authority,
155 mutation_attempt,
156 };
157 request.validate_journal(journal)?;
158 Ok(request)
159 }
160 #[must_use]
162 pub const fn plan(&self) -> &OperationPlanRecord {
163 self.plan
164 }
165 #[must_use]
167 pub const fn obligation(&self) -> &FenceObligationRecord {
168 self.obligation
169 }
170 #[must_use]
172 pub const fn payload(&self) -> &FenceAcquisitionPayload {
173 self.payload
174 }
175 #[must_use]
177 pub const fn authority(&self) -> &AttemptAuthorityRecord {
178 &self.authority
179 }
180 #[must_use]
182 pub const fn mutation_attempt(&self) -> u32 {
183 self.mutation_attempt
184 }
185 pub fn validate_journal(
189 &self,
190 journal: &AttemptJournalRecord,
191 ) -> Result<(), FenceAcquisitionError> {
192 journal
193 .validate_pending_mutation(&self.authority, self.mutation_attempt)
194 .map_err(Into::into)
195 }
196}
197
198#[derive(Clone, Debug)]
200pub struct FenceAcquisitionAcknowledgement {
201 pub authority: ArtifactChecksumRecord,
203 pub mutation_attempt: u32,
205 pub evidence: ArtifactChecksumRecord,
207}
208impl FenceAcquisitionAcknowledgement {
209 pub fn new(
213 authority: ArtifactChecksumRecord,
214 mutation_attempt: u32,
215 evidence: ArtifactChecksumRecord,
216 ) -> Result<Self, FenceAcquisitionError> {
217 if mutation_attempt == 0 || mutation_attempt > MAX_OPERATION_ATTEMPTS {
218 return Err(FenceAcquisitionError::InvalidAttempt);
219 }
220 Ok(Self {
221 authority,
222 mutation_attempt,
223 evidence,
224 })
225 }
226}
227
228#[derive(Debug, Error)]
230pub enum FenceAcquisitionError {
231 #[error("invalid fence acquisition receiver")]
233 InvalidTarget,
234 #[error("fence acquisition requires an application receiver")]
236 ManagementReceiver,
237 #[error("invalid fence acquisition method")]
239 InvalidMethod,
240 #[error("fence acquisition arguments exceed the input bound")]
242 ArgumentsTooLarge,
243 #[error("fence acquisition original journal authority mismatch")]
245 AuthorityMismatch,
246 #[error("fence acquisition original target mismatch")]
248 TargetMismatch,
249 #[error("fence acquisition original payload mismatch")]
251 PayloadMismatch,
252 #[error("fence acquisition requires a pending mutation reservation")]
254 NoPendingMutation,
255 #[error("fence acquisition pending mutation mismatch")]
257 MutationMismatch,
258 #[error("fence acquisition observation recovery is pending")]
260 ObservationPending,
261 #[error("invalid fence acquisition acknowledgement attempt")]
263 InvalidAttempt,
264 #[error(transparent)]
266 Obligation(#[from] FenceObligationError),
267 #[error(transparent)]
269 Plan(#[from] OperationPlanError),
270}
271
272#[cfg(test)]
273mod tests;
274
275impl From<crate::model::attempt_journal::MutationReservationError> for FenceAcquisitionError {
276 fn from(error: crate::model::attempt_journal::MutationReservationError) -> Self {
277 match error {
278 Reservation::NoPendingMutation => Self::NoPendingMutation,
279 Reservation::AuthorityMismatch => Self::AuthorityMismatch,
280 Reservation::MutationMismatch => Self::MutationMismatch,
281 Reservation::ObservationPending => Self::ObservationPending,
282 }
283 }
284}
285
286impl From<crate::model::application_payload::ApplicationPayloadError> for FenceAcquisitionError {
287 fn from(error: crate::model::application_payload::ApplicationPayloadError) -> Self {
288 match error {
289 Payload::ArgumentsTooLarge => Self::ArgumentsTooLarge,
290 Payload::InvalidMethod => Self::InvalidMethod,
291 Payload::InvalidPrincipal => Self::InvalidTarget,
292 Payload::ManagementPrincipal => Self::ManagementReceiver,
293 }
294 }
295}