ic_backup/policy/membership/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 inventory::InventoryRecord,
6 membership::{MembershipObservation, MembershipObservationRequest},
7};
8use thiserror::Error;
9
10#[derive(Clone, Debug)]
12pub struct MembershipView<'a> {
13 request: ArtifactChecksumRecord,
14 inventory: &'a InventoryRecord,
15 selected_targets: &'a [String],
16 revision: Option<&'a ArtifactChecksumRecord>,
17 evidence: &'a ArtifactChecksumRecord,
18 remote_observations: u32,
19}
20
21impl MembershipView<'_> {
22 #[must_use]
24 pub const fn request(&self) -> &ArtifactChecksumRecord {
25 &self.request
26 }
27 #[must_use]
29 pub const fn inventory(&self) -> &InventoryRecord {
30 self.inventory
31 }
32 #[must_use]
34 pub const fn selected_targets(&self) -> &[String] {
35 self.selected_targets
36 }
37 #[must_use]
39 pub const fn revision(&self) -> Option<&ArtifactChecksumRecord> {
40 self.revision
41 }
42 #[must_use]
44 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
45 self.evidence
46 }
47 #[must_use]
49 pub const fn remote_observations(&self) -> u32 {
50 self.remote_observations
51 }
52}
53
54pub fn validate<'a>(
62 request: &'a MembershipObservationRequest<'_>,
63 observation: &'a MembershipObservation,
64) -> Result<MembershipView<'a>, MembershipError> {
65 let digest = request.digest();
66 if observation.request != digest {
67 return Err(MembershipError::RequestMismatch);
68 }
69 let binding = request.binding();
70 if observation.context.network() != binding.network() {
71 return Err(MembershipError::ContextMismatch("network"));
72 }
73 if observation.context.caller() != binding.caller() {
74 return Err(MembershipError::ContextMismatch("caller"));
75 }
76 if observation.context.release() != binding.release() {
77 return Err(MembershipError::ContextMismatch("release"));
78 }
79 if observation.inventory != *request.inventory() {
80 return Err(MembershipError::InventoryMismatch);
81 }
82 if observation.remote_observations > request.max_remote_observations() {
83 return Err(MembershipError::ObservationLimitExceeded {
84 limit: request.max_remote_observations(),
85 reported: observation.remote_observations,
86 });
87 }
88 Ok(MembershipView {
89 request: digest,
90 inventory: &observation.inventory,
91 selected_targets: request.selected_targets(),
92 revision: observation.revision.as_ref(),
93 evidence: &observation.evidence,
94 remote_observations: observation.remote_observations,
95 })
96}
97
98#[derive(Debug, Eq, Error, PartialEq)]
100pub enum MembershipError {
101 #[error("membership request identity mismatch")]
103 RequestMismatch,
104 #[error("membership observed {0} mismatch")]
106 ContextMismatch(&'static str),
107 #[error("membership inventory mismatch")]
109 InventoryMismatch,
110 #[error("membership reports {reported} observations above ceiling {limit}")]
112 ObservationLimitExceeded {
113 limit: u32,
115 reported: u32,
117 },
118}
119
120#[cfg(test)]
121mod tests;