ic_backup/policy/consistency/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 consistency::{
6 ApplicationFenceEvidence, ApplicationFenceState, CaptureState, ConsistencyEvidence,
7 ConsistencyGuaranteeRecord, ConsistencyObservation, ConsistencyRequest,
8 TargetCaptureEvidence,
9 },
10};
11use thiserror::Error;
12
13#[derive(Clone, Debug)]
15pub struct ConsistencyView<'a> {
16 request: ArtifactChecksumRecord,
17 requirement: ArtifactChecksumRecord,
18 observation: &'a ConsistencyObservation,
19}
20impl ConsistencyView<'_> {
21 #[must_use]
23 pub const fn request(&self) -> &ArtifactChecksumRecord {
24 &self.request
25 }
26 #[must_use]
28 pub const fn requirement(&self) -> &ArtifactChecksumRecord {
29 &self.requirement
30 }
31 #[must_use]
33 pub fn targets(&self) -> &[TargetCaptureEvidence] {
34 self.observation.targets()
35 }
36 #[must_use]
38 pub fn fence(&self) -> Option<&ApplicationFenceEvidence> {
39 match self.observation.consistency() {
40 ConsistencyEvidence::PerCanister => None,
41 ConsistencyEvidence::ApplicationCoordinated(fence) => Some(fence),
42 }
43 }
44 #[must_use]
46 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
47 self.observation.evidence()
48 }
49 #[must_use]
51 pub const fn remote_observations(&self) -> u32 {
52 self.observation.remote_observations()
53 }
54}
55pub fn validate<'a>(
65 request: &ConsistencyRequest<'_>,
66 observation: &'a ConsistencyObservation,
67) -> Result<ConsistencyView<'a>, ConsistencyError> {
68 let digest = request.digest();
69 let binding = request.binding();
70 super::observed_context::admit_request_context(
71 &digest,
72 observation.request(),
73 binding,
74 observation.context(),
75 ConsistencyError::RequestMismatch,
76 ConsistencyError::ContextMismatch,
77 )?;
78 if observation.inventory() != request.inventory() {
79 return Err(ConsistencyError::InventoryMismatch);
80 }
81 if !observation
82 .targets()
83 .iter()
84 .map(|target| &target.target)
85 .eq(request.selected_targets())
86 {
87 return Err(ConsistencyError::SelectionMismatch);
88 }
89 super::observed_context::admit_limit(
90 request.max_remote_observations(),
91 observation.remote_observations(),
92 |limit, reported| ConsistencyError::ObservationLimitExceeded { limit, reported },
93 )?;
94 if observation
95 .targets()
96 .iter()
97 .any(|target| target.state != CaptureState::Stopped)
98 {
99 return Err(ConsistencyError::TargetNotStopped);
100 }
101 match (request.requirement().guarantee(), observation.consistency()) {
102 (ConsistencyGuaranteeRecord::PerCanister, ConsistencyEvidence::PerCanister) => {}
103 (
104 ConsistencyGuaranteeRecord::ApplicationCoordinated,
105 ConsistencyEvidence::ApplicationCoordinated(fence),
106 ) => {
107 let expected = request
108 .expected_fence()
109 .ok_or(ConsistencyError::FenceMismatch)?;
110 if fence.identity != expected.identity {
111 return Err(ConsistencyError::FenceMismatch);
112 }
113 if fence.state != ApplicationFenceState::Active {
114 return Err(ConsistencyError::FenceNotActive);
115 }
116 let original_revision_matches =
117 observation.membership_revision() == Some(&expected.membership_revision);
118 let fence_revision_matches = fence.membership_revision == expected.membership_revision;
119 if !original_revision_matches || !fence_revision_matches {
120 return Err(ConsistencyError::MembershipRevisionMismatch);
121 }
122 }
123 _ => return Err(ConsistencyError::GuaranteeMismatch),
124 }
125 Ok(ConsistencyView {
126 request: digest,
127 requirement: request.requirement().digest(),
128 observation,
129 })
130}
131#[derive(Debug, Eq, Error, PartialEq)]
133pub enum ConsistencyError {
134 #[error("consistency request mismatch")]
136 RequestMismatch,
137 #[error("consistency observed {0} mismatch")]
139 ContextMismatch(&'static str),
140 #[error("consistency inventory mismatch")]
142 InventoryMismatch,
143 #[error("consistency selected targets mismatch")]
145 SelectionMismatch,
146 #[error("consistency evidence guarantee mismatch")]
148 GuaranteeMismatch,
149 #[error("consistency target is not stopped")]
151 TargetNotStopped,
152 #[error("consistency fence identity mismatch")]
154 FenceMismatch,
155 #[error("consistency fence is not active")]
157 FenceNotActive,
158 #[error("consistency fence membership revision mismatch")]
160 MembershipRevisionMismatch,
161 #[error("consistency reports {reported} observations above ceiling {limit}")]
163 ObservationLimitExceeded {
164 limit: u32,
166 reported: u32,
168 },
169}
170
171#[cfg(test)]
172mod tests;