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 let Some(field) = super::observed_context::first_mismatch(binding, &observation.context) {
71 return Err(MembershipError::ContextMismatch(field));
72 }
73 if observation.inventory != *request.inventory() {
74 return Err(MembershipError::InventoryMismatch);
75 }
76 if observation.remote_observations > request.max_remote_observations() {
77 return Err(MembershipError::ObservationLimitExceeded {
78 limit: request.max_remote_observations(),
79 reported: observation.remote_observations,
80 });
81 }
82 Ok(MembershipView {
83 request: digest,
84 inventory: &observation.inventory,
85 selected_targets: request.selected_targets(),
86 revision: observation.revision.as_ref(),
87 evidence: &observation.evidence,
88 remote_observations: observation.remote_observations,
89 })
90}
91
92#[derive(Debug, Eq, Error, PartialEq)]
94pub enum MembershipError {
95 #[error("membership request identity mismatch")]
97 RequestMismatch,
98 #[error("membership observed {0} mismatch")]
100 ContextMismatch(&'static str),
101 #[error("membership inventory mismatch")]
103 InventoryMismatch,
104 #[error("membership reports {reported} observations above ceiling {limit}")]
106 ObservationLimitExceeded {
107 limit: u32,
109 reported: u32,
111 },
112}
113
114#[cfg(test)]
115mod tests;