ic_backup/model/snapshot_read/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 attempt_journal::OperationBindingRecord,
6 control_authority::ControllerSet,
7 ic_request::{IcManagementMethodRecord, IcManagementRequestRecord, IcRequestError},
8 operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
9};
10use thiserror::Error;
11
12pub const MAX_SNAPSHOT_VIEWERS: usize = 10;
14pub const MAX_SNAPSHOT_READ_REMOTE_OBSERVATIONS: u32 = 1024;
16
17#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct SnapshotViewerSet {
20 principals: Vec<String>,
21}
22impl SnapshotViewerSet {
23 pub fn new(principals: Vec<String>) -> Result<Self, SnapshotReadObservationError> {
27 let principals = super::principal::canonical_set(
28 principals,
29 MAX_SNAPSHOT_VIEWERS,
30 || SnapshotReadObservationError::TooManyViewers,
31 || SnapshotReadObservationError::InvalidPrincipal,
32 || SnapshotReadObservationError::DuplicateViewer,
33 )?;
34 Ok(Self { principals })
35 }
36 #[must_use]
38 pub fn principals(&self) -> &[String] {
39 &self.principals
40 }
41 #[must_use]
43 pub fn contains_caller(&self, binding: &OperationBindingRecord) -> bool {
44 self.principals
45 .binary_search_by(|principal| principal.as_str().cmp(binding.caller()))
46 .is_ok()
47 }
48}
49
50#[derive(Clone, Debug, Eq, PartialEq)]
55pub enum SnapshotVisibility {
56 Controllers,
58 Public,
60 AllowedViewers(SnapshotViewerSet),
62}
63
64#[derive(Clone, Debug)]
72pub struct SnapshotReadRequest<'a> {
73 binding: OperationBindingRecord,
74 wire: &'a IcManagementRequestRecord,
75 challenge: ArtifactChecksumRecord,
76 max_remote_observations: u32,
77}
78impl<'a> SnapshotReadRequest<'a> {
79 pub fn new(
83 plan: &OperationPlanRecord,
84 sequence: u64,
85 wire: &'a IcManagementRequestRecord,
86 observation: &ArtifactChecksumRecord,
87 challenge: ArtifactChecksumRecord,
88 max_remote_observations: u32,
89 ) -> Result<Self, SnapshotReadRequestError> {
90 if max_remote_observations > MAX_SNAPSHOT_READ_REMOTE_OBSERVATIONS {
91 return Err(SnapshotReadRequestError::ObservationLimitTooLarge);
92 }
93 if wire.method() != IcManagementMethodRecord::ListCanisterSnapshots {
94 return Err(SnapshotReadRequestError::UnsupportedMethod);
95 }
96 let binding = plan.attempt_authority(sequence)?.binding().clone();
97 wire.validate_observation_binding(&binding, observation)?;
98 Ok(Self {
99 binding,
100 wire,
101 challenge,
102 max_remote_observations,
103 })
104 }
105 #[must_use]
107 pub const fn binding(&self) -> &OperationBindingRecord {
108 &self.binding
109 }
110 #[must_use]
112 pub const fn wire(&self) -> &IcManagementRequestRecord {
113 self.wire
114 }
115 #[must_use]
117 pub const fn challenge(&self) -> &ArtifactChecksumRecord {
118 &self.challenge
119 }
120 #[must_use]
122 pub const fn max_remote_observations(&self) -> u32 {
123 self.max_remote_observations
124 }
125 #[must_use]
131 pub fn digest(&self) -> ArtifactChecksumRecord {
132 let mut bytes = b"ic-backup/snapshot-read/v1\0".to_vec();
133 bytes.extend_from_slice(self.binding.intent().as_bytes());
134 bytes.extend_from_slice(&self.binding.operation_sequence().to_be_bytes());
135 bytes.extend_from_slice(self.wire.digest().hash().as_bytes());
136 bytes.extend_from_slice(self.challenge.hash().as_bytes());
137 bytes.extend_from_slice(&self.max_remote_observations.to_be_bytes());
138 ArtifactChecksumRecord::from_bytes(&bytes)
139 }
140}
141
142#[derive(Clone, Debug)]
144pub struct SnapshotReadObservationInput {
145 pub request: ArtifactChecksumRecord,
147 pub context: PlanContextRecord,
149 pub target: String,
151 pub visibility: SnapshotVisibility,
153 pub controllers: Option<ControllerSet>,
159 pub evidence: ArtifactChecksumRecord,
161 pub remote_observations: u32,
163}
164#[derive(Clone, Debug)]
166pub struct SnapshotReadObservation {
167 input: SnapshotReadObservationInput,
168}
169impl SnapshotReadObservation {
170 pub fn new(
174 mut input: SnapshotReadObservationInput,
175 ) -> Result<Self, SnapshotReadObservationError> {
176 input.target = super::principal::canonical_text(&input.target)
177 .ok_or(SnapshotReadObservationError::InvalidPrincipal)?;
178 Ok(Self { input })
179 }
180 #[must_use]
182 pub const fn request(&self) -> &ArtifactChecksumRecord {
183 &self.input.request
184 }
185 #[must_use]
187 pub const fn context(&self) -> &PlanContextRecord {
188 &self.input.context
189 }
190 #[must_use]
192 pub fn target(&self) -> &str {
193 &self.input.target
194 }
195 #[must_use]
197 pub const fn visibility(&self) -> &SnapshotVisibility {
198 &self.input.visibility
199 }
200 #[must_use]
202 pub const fn controllers(&self) -> Option<&ControllerSet> {
203 self.input.controllers.as_ref()
204 }
205 #[must_use]
207 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
208 &self.input.evidence
209 }
210 #[must_use]
212 pub const fn remote_observations(&self) -> u32 {
213 self.input.remote_observations
214 }
215}
216
217#[derive(Debug, Eq, Error, PartialEq)]
219pub enum SnapshotReadObservationError {
220 #[error("invalid snapshot read principal")]
222 InvalidPrincipal,
223 #[error("snapshot viewer set exceeds {MAX_SNAPSHOT_VIEWERS}")]
225 TooManyViewers,
226 #[error("duplicate snapshot viewer")]
228 DuplicateViewer,
229}
230#[derive(Debug, Error)]
232pub enum SnapshotReadRequestError {
233 #[error("snapshot read observation ceiling exceeds {MAX_SNAPSHOT_READ_REMOTE_OBSERVATIONS}")]
235 ObservationLimitTooLarge,
236 #[error("snapshot read contract requires list_canister_snapshots")]
238 UnsupportedMethod,
239 #[error(transparent)]
241 Plan(#[from] OperationPlanError),
242 #[error(transparent)]
244 Payload(#[from] IcRequestError),
245}
246
247#[cfg(test)]
248mod tests;