ic_backup/model/consistency/requirement/
mod.rs1use crate::model::{
4 artifacts::{ArtifactChecksumRecord, ChecksumError},
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: ArtifactChecksumRecord::from_hash(&fields.plan_intent)?
55 .hash()
56 .into(),
57 guarantee: fields.guarantee,
58 })
59 }
60}
61impl ConsistencyRequirementRecord {
62 #[must_use]
64 pub fn new(plan: &OperationPlanRecord, guarantee: ConsistencyGuaranteeRecord) -> Self {
65 Self {
66 version: 1,
67 plan_intent: plan.digest().hash().into(),
68 guarantee,
69 }
70 }
71 #[must_use]
73 pub fn plan_intent(&self) -> &str {
74 &self.plan_intent
75 }
76 #[must_use]
78 pub const fn guarantee(&self) -> ConsistencyGuaranteeRecord {
79 self.guarantee
80 }
81 pub fn validate_plan(
85 &self,
86 plan: &OperationPlanRecord,
87 ) -> Result<(), ConsistencyRequirementError> {
88 if self.plan_intent != plan.digest().hash() {
89 return Err(ConsistencyRequirementError::PlanMismatch);
90 }
91 Ok(())
92 }
93 #[must_use]
98 pub fn digest(&self) -> ArtifactChecksumRecord {
99 let mut bytes = b"ic-backup/consistency-requirement/v1\0".to_vec();
100 bytes.extend_from_slice(self.plan_intent.as_bytes());
101 bytes.push(self.guarantee.tag());
102 ArtifactChecksumRecord::from_bytes(&bytes)
103 }
104}
105#[derive(Debug, Error)]
107pub enum ConsistencyRequirementError {
108 #[error("unsupported consistency requirement version {0}")]
110 UnsupportedVersion(u16),
111 #[error("consistency requirement original plan mismatch")]
113 PlanMismatch,
114 #[error(transparent)]
116 Checksum(#[from] ChecksumError),
117}