ic_backup/model/fence_obligation/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 consistency::{
6 ApplicationFenceBinding, ConsistencyGuaranteeRecord, ConsistencyRequirementError,
7 ConsistencyRequirementRecord,
8 },
9 operation_plan::{OperationPlanError, OperationPlanRecord},
10 restore_safety::{
11 RestoreFenceBindingRecord, RestoreSafetyLaneRecord, RestoreSafetyRequirementError,
12 RestoreSafetyRequirementRecord,
13 },
14};
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17
18pub const MAX_FENCE_OBLIGATION_BYTES: u64 = 1024;
20
21#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
23#[serde(tag = "purpose", rename_all = "snake_case", deny_unknown_fields)]
24pub enum FenceObligationScopeRecord {
25 Capture {
27 requirement: ArtifactChecksumRecord,
29 identity: ArtifactChecksumRecord,
31 membership_revision: ArtifactChecksumRecord,
33 },
34 Restore {
36 requirement: ArtifactChecksumRecord,
38 fence: RestoreFenceBindingRecord,
40 },
41}
42
43#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
51#[serde(try_from = "ObligationFields")]
52pub struct FenceObligationRecord {
53 version: u16,
54 plan_intent: ArtifactChecksumRecord,
55 acquisition_operation: u64,
56 scope: FenceObligationScopeRecord,
57}
58#[derive(Deserialize)]
59#[serde(deny_unknown_fields)]
60struct ObligationFields {
61 version: u16,
62 plan_intent: ArtifactChecksumRecord,
63 acquisition_operation: u64,
64 scope: FenceObligationScopeRecord,
65}
66impl TryFrom<ObligationFields> for FenceObligationRecord {
67 type Error = FenceObligationError;
68 fn try_from(fields: ObligationFields) -> Result<Self, Self::Error> {
69 if fields.version != 1 {
70 return Err(FenceObligationError::UnsupportedVersion(fields.version));
71 }
72 Ok(Self {
73 version: 1,
74 plan_intent: fields.plan_intent,
75 acquisition_operation: fields.acquisition_operation,
76 scope: fields.scope,
77 })
78 }
79}
80impl FenceObligationRecord {
81 pub fn for_capture(
85 plan: &OperationPlanRecord,
86 requirement: &ConsistencyRequirementRecord,
87 acquisition_operation: u64,
88 fence: &ApplicationFenceBinding,
89 ) -> Result<Self, FenceObligationError> {
90 requirement.validate_plan(plan)?;
91 if requirement.guarantee() != ConsistencyGuaranteeRecord::ApplicationCoordinated {
92 return Err(FenceObligationError::FenceNotRequired);
93 }
94 Self::new(
95 plan,
96 acquisition_operation,
97 FenceObligationScopeRecord::Capture {
98 requirement: requirement.digest(),
99 identity: fence.identity.clone(),
100 membership_revision: fence.membership_revision.clone(),
101 },
102 )
103 }
104 pub fn for_restore(
108 plan: &OperationPlanRecord,
109 source: &OperationPlanRecord,
110 requirement: &RestoreSafetyRequirementRecord,
111 acquisition_operation: u64,
112 ) -> Result<Self, FenceObligationError> {
113 requirement.validate_plans(plan, source)?;
114 if requirement.safety() != RestoreSafetyLaneRecord::ApplicationFenced {
115 return Err(FenceObligationError::FenceNotRequired);
116 }
117 let fence = requirement
118 .expected_fence()
119 .ok_or(FenceObligationError::FenceNotRequired)?;
120 Self::new(
121 plan,
122 acquisition_operation,
123 FenceObligationScopeRecord::Restore {
124 requirement: requirement.digest(),
125 fence: fence.clone(),
126 },
127 )
128 }
129 fn new(
130 plan: &OperationPlanRecord,
131 acquisition_operation: u64,
132 scope: FenceObligationScopeRecord,
133 ) -> Result<Self, FenceObligationError> {
134 let record = Self {
135 version: 1,
136 plan_intent: plan.digest(),
137 acquisition_operation,
138 scope,
139 };
140 record.validate_plan(plan)?;
141 Ok(record)
142 }
143 #[must_use]
145 pub const fn plan_intent(&self) -> &ArtifactChecksumRecord {
146 &self.plan_intent
147 }
148 #[must_use]
150 pub const fn acquisition_operation(&self) -> u64 {
151 self.acquisition_operation
152 }
153 #[must_use]
155 pub const fn scope(&self) -> &FenceObligationScopeRecord {
156 &self.scope
157 }
158 pub fn validate_plan(&self, plan: &OperationPlanRecord) -> Result<(), FenceObligationError> {
162 if self.plan_intent != plan.digest() {
163 return Err(FenceObligationError::PlanMismatch);
164 }
165 if plan
166 .attempt_authority(self.acquisition_operation)?
167 .budget()
168 .mutations()
169 == 0
170 {
171 return Err(FenceObligationError::NoAcquisitionAllowance);
172 }
173 Ok(())
174 }
175 pub fn validate_capture(
179 &self,
180 plan: &OperationPlanRecord,
181 requirement: &ConsistencyRequirementRecord,
182 fence: &ApplicationFenceBinding,
183 ) -> Result<(), FenceObligationError> {
184 let original = Self::for_capture(plan, requirement, self.acquisition_operation, fence)?;
185 self.validate_original(&original)
186 }
187 pub fn validate_restore(
191 &self,
192 plan: &OperationPlanRecord,
193 source: &OperationPlanRecord,
194 requirement: &RestoreSafetyRequirementRecord,
195 ) -> Result<(), FenceObligationError> {
196 let original = Self::for_restore(plan, source, requirement, self.acquisition_operation)?;
197 self.validate_original(&original)
198 }
199 fn validate_original(&self, original: &Self) -> Result<(), FenceObligationError> {
200 if self != original {
201 return Err(FenceObligationError::BindingMismatch);
202 }
203 Ok(())
204 }
205 #[must_use]
211 pub fn digest(&self) -> ArtifactChecksumRecord {
212 let mut bytes = b"ic-backup/fence-obligation/v1\0".to_vec();
213 bytes.extend_from_slice(self.plan_intent.hash().as_bytes());
214 bytes.extend_from_slice(&self.acquisition_operation.to_be_bytes());
215 let (tag, hashes) = match &self.scope {
216 FenceObligationScopeRecord::Capture {
217 requirement,
218 identity,
219 membership_revision,
220 } => (0, vec![requirement, identity, membership_revision]),
221 FenceObligationScopeRecord::Restore { requirement, fence } => (
222 1,
223 vec![
224 requirement,
225 &fence.identity,
226 &fence.membership_revision,
227 &fence.external_obligations_revision,
228 ],
229 ),
230 };
231 bytes.push(tag);
232 for hash in hashes {
233 bytes.extend_from_slice(hash.hash().as_bytes());
234 }
235 ArtifactChecksumRecord::from_bytes(&bytes)
236 }
237}
238#[derive(Debug, Error)]
240pub enum FenceObligationError {
241 #[error("unsupported fence obligation version {0}")]
243 UnsupportedVersion(u16),
244 #[error("fence obligation original plan mismatch")]
246 PlanMismatch,
247 #[error("fence obligation original binding mismatch")]
249 BindingMismatch,
250 #[error("original requirement does not require an application fence")]
252 FenceNotRequired,
253 #[error("original fence acquisition operation has no mutation allowance")]
255 NoAcquisitionAllowance,
256 #[error(transparent)]
258 Plan(#[from] OperationPlanError),
259 #[error(transparent)]
261 Consistency(#[from] ConsistencyRequirementError),
262 #[error(transparent)]
264 Restore(#[from] RestoreSafetyRequirementError),
265}
266
267#[cfg(test)]
268mod tests;