ic_backup/model/restore_safety/
mod.rs1mod requirement;
4pub use requirement::{
5 MAX_RESTORE_SAFETY_REQUIREMENT_BYTES, RestoreFenceBindingRecord, RestoreSafetyLaneRecord,
6 RestoreSafetyRequirementError, RestoreSafetyRequirementRecord, RestoreSafetyRequirementRequest,
7};
8
9use crate::model::{
10 artifacts::ArtifactChecksumRecord,
11 attempt_journal::OperationBindingRecord,
12 evidence_digest::EvidenceDigest,
13 ic_request::{IcManagementMethodRecord, IcManagementRequestRecord, IcRequestError},
14 inventory::{InventoryRecord, MAX_INVENTORY_TARGETS},
15 operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
16};
17use ic_platform::management_canister_types::CanisterStatusType;
18use thiserror::Error;
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub enum ApplicationFenceState {
23 Active,
25 Inactive,
27}
28
29pub const MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS: u32 = 1024;
31#[derive(Clone, Debug)]
33pub struct RestoreSafetyRequestInput {
34 pub operation_sequence: u64,
36 pub challenge: ArtifactChecksumRecord,
38 pub max_remote_observations: u32,
40}
41#[derive(Clone, Debug)]
43pub struct RestoreSafetyRequest<'a> {
44 plan: &'a OperationPlanRecord,
45 requirement: &'a RestoreSafetyRequirementRecord,
46 binding: OperationBindingRecord,
47 wire: &'a IcManagementRequestRecord,
48 input: RestoreSafetyRequestInput,
49}
50impl<'a> RestoreSafetyRequest<'a> {
51 pub fn new(
59 plan: &'a OperationPlanRecord,
60 source: &OperationPlanRecord,
61 requirement: &'a RestoreSafetyRequirementRecord,
62 wire: &'a IcManagementRequestRecord,
63 input: RestoreSafetyRequestInput,
64 ) -> Result<Self, RestoreSafetyRequestError> {
65 requirement.validate_plans(plan, source)?;
66 if input.max_remote_observations > MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS {
67 return Err(RestoreSafetyRequestError::ObservationLimitTooLarge);
68 }
69 if !matches!(
70 wire.method(),
71 IcManagementMethodRecord::LoadCanisterSnapshot
72 | IcManagementMethodRecord::StartCanister
73 ) {
74 return Err(RestoreSafetyRequestError::UnsupportedMethod);
75 }
76 let binding = plan.operation_binding(input.operation_sequence)?;
77 wire.validate_mutation_binding(&binding)?;
78 Ok(Self {
79 plan,
80 requirement,
81 binding,
82 wire,
83 input,
84 })
85 }
86 #[must_use]
88 pub const fn binding(&self) -> &OperationBindingRecord {
89 &self.binding
90 }
91 #[must_use]
93 pub const fn inventory(&self) -> &InventoryRecord {
94 self.plan.inventory()
95 }
96 #[must_use]
98 pub fn selected_targets(&self) -> &[String] {
99 self.plan.selected_targets()
100 }
101 #[must_use]
103 pub const fn requirement(&self) -> &RestoreSafetyRequirementRecord {
104 self.requirement
105 }
106 #[must_use]
108 pub const fn wire(&self) -> &IcManagementRequestRecord {
109 self.wire
110 }
111 #[must_use]
113 pub const fn challenge(&self) -> &ArtifactChecksumRecord {
114 &self.input.challenge
115 }
116 #[must_use]
118 pub const fn max_remote_observations(&self) -> u32 {
119 self.input.max_remote_observations
120 }
121 #[must_use]
125 pub fn digest(&self) -> ArtifactChecksumRecord {
126 let mut framing = EvidenceDigest::new(b"ic-backup/restore-safety-request/v1\0");
127 framing.checksum(&self.requirement.digest());
128 framing.u64_be(self.binding.operation_sequence());
129 framing.checksum(&self.wire.digest());
130 framing.checksum(self.challenge());
131 framing.u32_be(self.max_remote_observations());
132 framing.finish()
133 }
134}
135#[derive(Clone, Debug)]
137pub struct TargetRestoreEvidence {
138 pub target: String,
140 pub state: CanisterStatusType,
142 pub lifecycle_evidence: ArtifactChecksumRecord,
144 pub restored_acceptance: Option<ArtifactChecksumRecord>,
147}
148impl super::observed_targets::ObservedTargetRow for TargetRestoreEvidence {
149 fn target(&self) -> &str {
150 &self.target
151 }
152
153 fn target_mut(&mut self) -> &mut String {
154 &mut self.target
155 }
156}
157#[derive(Clone, Debug)]
159pub struct RestoreFenceEvidence {
160 pub state: ApplicationFenceState,
162 pub binding: RestoreFenceBindingRecord,
164 pub whole_selection: ArtifactChecksumRecord,
166 pub rewind_independent_custody: ArtifactChecksumRecord,
168 pub external_obligations_and_replay: ArtifactChecksumRecord,
171 pub controlled_execution: Option<ArtifactChecksumRecord>,
174}
175#[derive(Clone, Debug)]
177pub enum RestoreSafetyEvidence {
178 NoIrreversibleEffects(ArtifactChecksumRecord),
180 ApplicationFenced(Box<RestoreFenceEvidence>),
182 Unresolved(ArtifactChecksumRecord),
184}
185#[derive(Clone, Debug)]
187pub struct RestoreSafetyObservationInput {
188 pub request: ArtifactChecksumRecord,
190 pub context: PlanContextRecord,
192 pub inventory: InventoryRecord,
194 pub source_plan_intent: ArtifactChecksumRecord,
196 pub source_artifacts: ArtifactChecksumRecord,
199 pub targets: Vec<TargetRestoreEvidence>,
201 pub safety: RestoreSafetyEvidence,
203 pub evidence: ArtifactChecksumRecord,
205 pub remote_observations: u32,
207}
208#[derive(Clone, Debug)]
210pub struct RestoreSafetyObservation {
211 input: RestoreSafetyObservationInput,
212}
213impl RestoreSafetyObservation {
214 pub fn new(
218 mut input: RestoreSafetyObservationInput,
219 ) -> Result<Self, RestoreSafetyObservationError> {
220 input.targets = super::observed_targets::canonicalize(
221 input.targets,
222 &input.inventory,
223 || RestoreSafetyObservationError::InvalidTargetCount,
224 || RestoreSafetyObservationError::InvalidPrincipal,
225 || RestoreSafetyObservationError::TargetAbsentFromInventory,
226 || RestoreSafetyObservationError::DuplicateTarget,
227 )?;
228 Ok(Self { input })
229 }
230 #[must_use]
232 pub const fn request(&self) -> &ArtifactChecksumRecord {
233 &self.input.request
234 }
235 #[must_use]
237 pub const fn context(&self) -> &PlanContextRecord {
238 &self.input.context
239 }
240 #[must_use]
242 pub const fn inventory(&self) -> &InventoryRecord {
243 &self.input.inventory
244 }
245 #[must_use]
247 pub const fn source_plan_intent(&self) -> &ArtifactChecksumRecord {
248 &self.input.source_plan_intent
249 }
250 #[must_use]
252 pub const fn source_artifacts(&self) -> &ArtifactChecksumRecord {
253 &self.input.source_artifacts
254 }
255 #[must_use]
257 pub fn targets(&self) -> &[TargetRestoreEvidence] {
258 &self.input.targets
259 }
260 #[must_use]
262 pub const fn safety(&self) -> &RestoreSafetyEvidence {
263 &self.input.safety
264 }
265 #[must_use]
267 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
268 &self.input.evidence
269 }
270 #[must_use]
272 pub const fn remote_observations(&self) -> u32 {
273 self.input.remote_observations
274 }
275}
276#[derive(Debug, Eq, Error, PartialEq)]
278pub enum RestoreSafetyObservationError {
279 #[error("restore safety targets must contain 1..={MAX_INVENTORY_TARGETS} entries")]
281 InvalidTargetCount,
282 #[error("invalid restore safety target principal")]
284 InvalidPrincipal,
285 #[error("duplicate restore safety target")]
287 DuplicateTarget,
288 #[error("restore safety target absent from inventory")]
290 TargetAbsentFromInventory,
291}
292#[derive(Debug, Error)]
294pub enum RestoreSafetyRequestError {
295 #[error(transparent)]
297 Requirement(#[from] RestoreSafetyRequirementError),
298 #[error(transparent)]
300 Plan(#[from] OperationPlanError),
301 #[error(transparent)]
303 Payload(#[from] IcRequestError),
304 #[error("restore safety requires exact load or start request")]
306 UnsupportedMethod,
307 #[error("restore safety observation ceiling exceeds {MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS}")]
309 ObservationLimitTooLarge,
310}
311
312#[cfg(test)]
313mod tests;