ic_backup/model/consistency/requirement/
mod.rs1use crate::model::{
4 artifacts::{ArtifactChecksumRecord, ChecksumError, canonical_hash},
5 operation_plan::OperationPlanRecord,
6};
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10pub const MAX_CONSISTENCY_REQUIREMENT_BYTES: u64 = 1024;
12#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
14#[serde(rename_all = "snake_case")]
15pub enum ConsistencyGuaranteeRecord {
16 PerCanister,
18 ApplicationCoordinated,
20}
21impl ConsistencyGuaranteeRecord {
22 pub(super) const fn tag(self) -> u8 {
23 match self {
24 Self::PerCanister => 0,
25 Self::ApplicationCoordinated => 1,
26 }
27 }
28}
29#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31#[serde(try_from = "RequirementFields")]
32pub struct ConsistencyRequirementRecord {
33 version: u16,
34 plan_intent: String,
35 guarantee: ConsistencyGuaranteeRecord,
36}
37#[derive(Deserialize)]
38#[serde(deny_unknown_fields)]
39struct RequirementFields {
40 version: u16,
41 plan_intent: String,
42 guarantee: ConsistencyGuaranteeRecord,
43}
44impl TryFrom<RequirementFields> for ConsistencyRequirementRecord {
45 type Error = ConsistencyRequirementError;
46 fn try_from(fields: RequirementFields) -> Result<Self, Self::Error> {
47 if fields.version != 1 {
48 return Err(ConsistencyRequirementError::UnsupportedVersion(
49 fields.version,
50 ));
51 }
52 Ok(Self {
53 version: 1,
54 plan_intent: canonical_hash(&fields.plan_intent)?,
55 guarantee: fields.guarantee,
56 })
57 }
58}
59impl ConsistencyRequirementRecord {
60 #[must_use]
62 pub fn new(plan: &OperationPlanRecord, guarantee: ConsistencyGuaranteeRecord) -> Self {
63 Self {
64 version: 1,
65 plan_intent: plan.digest().hash().into(),
66 guarantee,
67 }
68 }
69 #[must_use]
71 pub fn plan_intent(&self) -> &str {
72 &self.plan_intent
73 }
74 #[must_use]
76 pub const fn guarantee(&self) -> ConsistencyGuaranteeRecord {
77 self.guarantee
78 }
79 pub fn validate_plan(
83 &self,
84 plan: &OperationPlanRecord,
85 ) -> Result<(), ConsistencyRequirementError> {
86 if self.plan_intent != plan.digest().hash() {
87 return Err(ConsistencyRequirementError::PlanMismatch);
88 }
89 Ok(())
90 }
91 #[must_use]
96 pub fn digest(&self) -> ArtifactChecksumRecord {
97 let mut bytes = b"ic-backup/consistency-requirement/v1\0".to_vec();
98 bytes.extend_from_slice(self.plan_intent.as_bytes());
99 bytes.push(self.guarantee.tag());
100 ArtifactChecksumRecord::from_bytes(&bytes)
101 }
102}
103#[derive(Debug, Error)]
105pub enum ConsistencyRequirementError {
106 #[error("unsupported consistency requirement version {0}")]
108 UnsupportedVersion(u16),
109 #[error("consistency requirement original plan mismatch")]
111 PlanMismatch,
112 #[error(transparent)]
114 Checksum(#[from] ChecksumError),
115}