ic_backup/model/snapshot_read/
mod.rs1use crate::model::evidence_digest::EvidenceDigest;
4use crate::model::{
5 artifacts::ArtifactChecksumRecord,
6 attempt_journal::OperationBindingRecord,
7 control_authority::ControllerSet,
8 ic_request::{IcManagementMethodRecord, IcManagementRequestRecord, IcRequestError},
9 operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
10};
11use thiserror::Error;
12
13pub const MAX_SNAPSHOT_VIEWERS: usize = 10;
15pub const MAX_SNAPSHOT_READ_REMOTE_OBSERVATIONS: u32 = 1024;
17
18#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct SnapshotViewerSet {
21 principals: Vec<String>,
22}
23impl SnapshotViewerSet {
24 pub fn new(principals: Vec<String>) -> Result<Self, SnapshotReadObservationError> {
28 let principals = super::principal::canonical_set(
29 principals,
30 MAX_SNAPSHOT_VIEWERS,
31 || SnapshotReadObservationError::TooManyViewers,
32 || SnapshotReadObservationError::InvalidPrincipal,
33 || SnapshotReadObservationError::DuplicateViewer,
34 )?;
35 Ok(Self { principals })
36 }
37 #[must_use]
39 pub fn principals(&self) -> &[String] {
40 &self.principals
41 }
42 #[must_use]
44 pub fn contains_caller(&self, binding: &OperationBindingRecord) -> bool {
45 self.principals
46 .binary_search_by(|principal| principal.as_str().cmp(binding.caller()))
47 .is_ok()
48 }
49}
50
51#[derive(Clone, Debug, Eq, PartialEq)]
56pub enum SnapshotVisibility {
57 Controllers,
59 Public,
61 AllowedViewers(SnapshotViewerSet),
63}
64
65#[derive(Clone, Debug)]
73pub struct SnapshotReadRequest<'a> {
74 binding: OperationBindingRecord,
75 wire: &'a IcManagementRequestRecord,
76 challenge: ArtifactChecksumRecord,
77 max_remote_observations: u32,
78}
79impl<'a> SnapshotReadRequest<'a> {
80 pub fn new(
84 plan: &OperationPlanRecord,
85 sequence: u64,
86 wire: &'a IcManagementRequestRecord,
87 observation: &ArtifactChecksumRecord,
88 challenge: ArtifactChecksumRecord,
89 max_remote_observations: u32,
90 ) -> Result<Self, SnapshotReadRequestError> {
91 if max_remote_observations > MAX_SNAPSHOT_READ_REMOTE_OBSERVATIONS {
92 return Err(SnapshotReadRequestError::ObservationLimitTooLarge);
93 }
94 if wire.method() != IcManagementMethodRecord::ListCanisterSnapshots {
95 return Err(SnapshotReadRequestError::UnsupportedMethod);
96 }
97 let binding = plan.attempt_authority(sequence)?.binding().clone();
98 wire.validate_observation_binding(&binding, observation)?;
99 Ok(Self {
100 binding,
101 wire,
102 challenge,
103 max_remote_observations,
104 })
105 }
106 #[must_use]
108 pub const fn binding(&self) -> &OperationBindingRecord {
109 &self.binding
110 }
111 #[must_use]
113 pub const fn wire(&self) -> &IcManagementRequestRecord {
114 self.wire
115 }
116 #[must_use]
118 pub const fn challenge(&self) -> &ArtifactChecksumRecord {
119 &self.challenge
120 }
121 #[must_use]
123 pub const fn max_remote_observations(&self) -> u32 {
124 self.max_remote_observations
125 }
126 #[must_use]
132 pub fn digest(&self) -> ArtifactChecksumRecord {
133 let mut bytes = EvidenceDigest::new(b"ic-backup/snapshot-read/v1\0");
134 bytes.append(self.binding.intent().as_bytes());
135 bytes.append(&self.binding.operation_sequence().to_be_bytes());
136 bytes.append(self.wire.digest().hash().as_bytes());
137 bytes.append(self.challenge.hash().as_bytes());
138 bytes.append(&self.max_remote_observations.to_be_bytes());
139 bytes.finish()
140 }
141}
142
143#[derive(Clone, Debug)]
145pub struct SnapshotReadObservationInput {
146 pub request: ArtifactChecksumRecord,
148 pub context: PlanContextRecord,
150 pub target: String,
152 pub visibility: SnapshotVisibility,
154 pub controllers: Option<ControllerSet>,
160 pub evidence: ArtifactChecksumRecord,
162 pub remote_observations: u32,
164}
165#[derive(Clone, Debug)]
167pub struct SnapshotReadObservation {
168 input: SnapshotReadObservationInput,
169}
170impl SnapshotReadObservation {
171 pub fn new(
175 mut input: SnapshotReadObservationInput,
176 ) -> Result<Self, SnapshotReadObservationError> {
177 input.target = super::principal::canonical_text(&input.target)
178 .ok_or(SnapshotReadObservationError::InvalidPrincipal)?;
179 Ok(Self { input })
180 }
181 #[must_use]
183 pub const fn request(&self) -> &ArtifactChecksumRecord {
184 &self.input.request
185 }
186 #[must_use]
188 pub const fn context(&self) -> &PlanContextRecord {
189 &self.input.context
190 }
191 #[must_use]
193 pub fn target(&self) -> &str {
194 &self.input.target
195 }
196 #[must_use]
198 pub const fn visibility(&self) -> &SnapshotVisibility {
199 &self.input.visibility
200 }
201 #[must_use]
203 pub const fn controllers(&self) -> Option<&ControllerSet> {
204 self.input.controllers.as_ref()
205 }
206 #[must_use]
208 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
209 &self.input.evidence
210 }
211 #[must_use]
213 pub const fn remote_observations(&self) -> u32 {
214 self.input.remote_observations
215 }
216}
217
218#[derive(Debug, Eq, Error, PartialEq)]
220pub enum SnapshotReadObservationError {
221 #[error("invalid snapshot read principal")]
223 InvalidPrincipal,
224 #[error("snapshot viewer set exceeds {MAX_SNAPSHOT_VIEWERS}")]
226 TooManyViewers,
227 #[error("duplicate snapshot viewer")]
229 DuplicateViewer,
230}
231#[derive(Debug, Error)]
233pub enum SnapshotReadRequestError {
234 #[error("snapshot read observation ceiling exceeds {MAX_SNAPSHOT_READ_REMOTE_OBSERVATIONS}")]
236 ObservationLimitTooLarge,
237 #[error("snapshot read contract requires list_canister_snapshots")]
239 UnsupportedMethod,
240 #[error(transparent)]
242 Plan(#[from] OperationPlanError),
243 #[error(transparent)]
245 Payload(#[from] IcRequestError),
246}
247
248#[cfg(test)]
249mod tests;