ic_backup/policy/snapshot_read/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 snapshot_read::{SnapshotReadObservation, SnapshotReadRequest, SnapshotVisibility},
6};
7use thiserror::Error;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum SnapshotReadPath {
12 Controller,
14 Public,
16 AllowedViewer,
18}
19#[derive(Clone, Debug)]
21pub struct SnapshotReadView<'a> {
22 request: ArtifactChecksumRecord,
23 observation: &'a SnapshotReadObservation,
24 path: SnapshotReadPath,
25}
26impl SnapshotReadView<'_> {
27 #[must_use]
29 pub const fn request(&self) -> &ArtifactChecksumRecord {
30 &self.request
31 }
32 #[must_use]
34 pub fn target(&self) -> &str {
35 self.observation.target()
36 }
37 #[must_use]
39 pub const fn visibility(&self) -> &SnapshotVisibility {
40 self.observation.visibility()
41 }
42 #[must_use]
44 pub const fn path(&self) -> SnapshotReadPath {
45 self.path
46 }
47 #[must_use]
49 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
50 self.observation.evidence()
51 }
52 #[must_use]
54 pub const fn remote_observations(&self) -> u32 {
55 self.observation.remote_observations()
56 }
57}
58pub fn validate<'a>(
68 request: &SnapshotReadRequest<'_>,
69 observation: &'a SnapshotReadObservation,
70) -> Result<SnapshotReadView<'a>, SnapshotReadError> {
71 let digest = request.digest();
72 let binding = request.binding();
73 super::observed_context::admit_request_context(
74 &digest,
75 observation.request(),
76 binding,
77 observation.context(),
78 SnapshotReadError::RequestMismatch,
79 SnapshotReadError::ContextMismatch,
80 )?;
81 super::observed_context::admit_target_limit(
82 binding,
83 observation.target(),
84 request.max_remote_observations(),
85 observation.remote_observations(),
86 SnapshotReadError::TargetMismatch,
87 |limit, reported| SnapshotReadError::ObservationLimitExceeded { limit, reported },
88 )?;
89 let controller = observation
90 .controllers()
91 .is_some_and(|set| set.contains_caller(binding));
92 let path = if controller {
93 SnapshotReadPath::Controller
94 } else {
95 match observation.visibility() {
96 SnapshotVisibility::Public => SnapshotReadPath::Public,
97 SnapshotVisibility::AllowedViewers(viewers) if viewers.contains_caller(binding) => {
98 SnapshotReadPath::AllowedViewer
99 }
100 SnapshotVisibility::Controllers | SnapshotVisibility::AllowedViewers(_) => {
101 return Err(if observation.controllers().is_none() {
102 SnapshotReadError::ControllersUnobserved
103 } else {
104 SnapshotReadError::CallerCannotRead
105 });
106 }
107 }
108 };
109 Ok(SnapshotReadView {
110 request: digest,
111 observation,
112 path,
113 })
114}
115#[derive(Debug, Eq, Error, PartialEq)]
117pub enum SnapshotReadError {
118 #[error("snapshot read request mismatch")]
120 RequestMismatch,
121 #[error("snapshot read observed {0} mismatch")]
123 ContextMismatch(&'static str),
124 #[error("snapshot read observed target mismatch")]
126 TargetMismatch,
127 #[error("snapshot read requires unobserved controller evidence")]
129 ControllersUnobserved,
130 #[error("selected caller cannot read observed snapshots")]
132 CallerCannotRead,
133 #[error("snapshot read reports {reported} observations above ceiling {limit}")]
135 ObservationLimitExceeded {
136 limit: u32,
138 reported: u32,
140 },
141}
142
143#[cfg(test)]
144mod tests;