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