ic_backup/policy/control_authority/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 control_authority::{ControlObservation, ControlObservationRequest, ControllerSet},
6};
7use thiserror::Error;
8
9#[derive(Clone, Debug)]
11pub struct ControlAuthorityView<'a> {
12 request: ArtifactChecksumRecord,
13 observation: &'a ControlObservation,
14}
15impl ControlAuthorityView<'_> {
16 #[must_use]
18 pub const fn request(&self) -> &ArtifactChecksumRecord {
19 &self.request
20 }
21 #[must_use]
23 pub fn target(&self) -> &str {
24 self.observation.target()
25 }
26 #[must_use]
28 pub const fn controllers(&self) -> &ControllerSet {
29 self.observation.controllers()
30 }
31 #[must_use]
33 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
34 self.observation.evidence()
35 }
36 #[must_use]
38 pub const fn remote_observations(&self) -> u32 {
39 self.observation.remote_observations()
40 }
41}
42pub fn validate<'a>(
52 request: &ControlObservationRequest<'_>,
53 observation: &'a ControlObservation,
54) -> Result<ControlAuthorityView<'a>, ControlAuthorityError> {
55 let digest = request.digest();
56 if *observation.request() != digest {
57 return Err(ControlAuthorityError::RequestMismatch);
58 }
59 let binding = request.binding();
60 for (field, expected, actual) in [
61 (
62 "network",
63 binding.network(),
64 observation.context().network(),
65 ),
66 ("caller", binding.caller(), observation.context().caller()),
67 (
68 "release",
69 binding.release(),
70 observation.context().release(),
71 ),
72 ] {
73 if actual != expected {
74 return Err(ControlAuthorityError::ContextMismatch(field));
75 }
76 }
77 if observation.target() != binding.target() {
78 return Err(ControlAuthorityError::TargetMismatch);
79 }
80 if observation.remote_observations() > request.max_remote_observations() {
81 return Err(ControlAuthorityError::ObservationLimitExceeded {
82 limit: request.max_remote_observations(),
83 reported: observation.remote_observations(),
84 });
85 }
86 if !observation.controllers().contains_caller(binding) {
87 return Err(ControlAuthorityError::CallerNotController);
88 }
89 Ok(ControlAuthorityView {
90 request: digest,
91 observation,
92 })
93}
94#[derive(Debug, Eq, Error, PartialEq)]
96pub enum ControlAuthorityError {
97 #[error("control observation request mismatch")]
99 RequestMismatch,
100 #[error("control observed {0} mismatch")]
102 ContextMismatch(&'static str),
103 #[error("control observed target mismatch")]
105 TargetMismatch,
106 #[error("selected caller is not an observed controller")]
108 CallerNotController,
109 #[error("control reports {reported} observations above ceiling {limit}")]
111 ObservationLimitExceeded {
112 limit: u32,
114 reported: u32,
116 },
117}
118
119#[cfg(test)]
120mod tests;