ic_backup/model/ic_snapshot_metadata/
mod.rs1mod wire;
4
5use super::{
6 artifacts::ArtifactChecksumRecord,
7 ic_request::{IcRequestError, admits_snapshot_id, encode_arguments, management_request_digest},
8};
9use ic_platform::Principal;
10use ic_platform::management_canister_types::{
11 ReadCanisterSnapshotMetadataArgs, ReadCanisterSnapshotMetadataResult,
12};
13use std::fmt;
14use thiserror::Error;
15
16pub const MAX_IC_SNAPSHOT_METADATA_BYTES: usize = 1024 * 1024;
18pub const MAX_IC_SNAPSHOT_GLOBALS: usize = 4096;
20pub const MAX_IC_SNAPSHOT_CHUNKS: usize = 1024;
22
23#[derive(Clone)]
29pub struct IcSnapshotMetadataRequest {
30 target: String,
31 principal: Principal,
32 snapshot_id: Vec<u8>,
33 arguments: Vec<u8>,
34}
35
36impl IcSnapshotMetadataRequest {
37 pub fn new(target: &str, snapshot_id: &[u8]) -> Result<Self, IcRequestError> {
43 if !admits_snapshot_id(snapshot_id) {
44 return Err(IcRequestError::InvalidSnapshotId);
45 }
46 let (target, principal) =
47 super::principal::canonical_parts(target).ok_or(IcRequestError::InvalidTarget)?;
48 let arguments = encode_arguments(ReadCanisterSnapshotMetadataArgs {
49 canister_id: principal.into(),
50 snapshot_id: snapshot_id.to_vec(),
51 })?;
52 Ok(Self {
53 target,
54 principal,
55 snapshot_id: snapshot_id.to_vec(),
56 arguments,
57 })
58 }
59
60 pub(crate) const fn principal(&self) -> Principal {
61 self.principal
62 }
63
64 #[must_use]
66 pub fn target(&self) -> &str {
67 &self.target
68 }
69
70 #[must_use]
72 pub fn snapshot_id(&self) -> &[u8] {
73 &self.snapshot_id
74 }
75
76 #[must_use]
78 pub fn arguments(&self) -> &[u8] {
79 &self.arguments
80 }
81
82 #[must_use]
84 pub const fn receiver(&self) -> &'static str {
85 "aaaaa-aa"
86 }
87
88 #[must_use]
90 pub const fn method(&self) -> &'static str {
91 "read_canister_snapshot_metadata"
92 }
93
94 #[must_use]
96 pub fn digest(&self) -> ArtifactChecksumRecord {
97 management_request_digest(self.principal.as_slice(), self.method(), &self.arguments)
98 }
99}
100
101impl fmt::Debug for IcSnapshotMetadataRequest {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 f.debug_struct("IcSnapshotMetadataRequest")
104 .field("target", &self.target)
105 .field("snapshot_id_bytes", &self.snapshot_id.len())
106 .finish_non_exhaustive()
107 }
108}
109
110pub struct IcSnapshotMetadataReply<'request> {
117 request: &'request IcSnapshotMetadataRequest,
118 metadata: ReadCanisterSnapshotMetadataResult,
119 payload_checksum: ArtifactChecksumRecord,
120}
121
122impl<'request> IcSnapshotMetadataReply<'request> {
123 pub(crate) fn region_sizes(&self) -> [u64; 3] {
124 [
125 self.metadata.wasm_module_size,
126 self.metadata.wasm_memory_size,
127 self.metadata.stable_memory_size,
128 ]
129 }
130
131 pub(crate) fn chunk_index(&self, hash: &[u8]) -> Option<usize> {
132 self.metadata
133 .wasm_chunk_store
134 .iter()
135 .position(|chunk| chunk.hash == hash)
136 }
137
138 pub fn decode(
145 request: &'request IcSnapshotMetadataRequest,
146 bytes: &[u8],
147 ) -> Result<Self, IcSnapshotMetadataError> {
148 if bytes.len() > MAX_IC_SNAPSHOT_METADATA_BYTES {
149 return Err(IcSnapshotMetadataError::ReplyTooLarge);
150 }
151 let metadata = wire::decode(bytes)?;
152 Ok(Self {
153 request,
154 metadata,
155 payload_checksum: ArtifactChecksumRecord::from_bytes(bytes),
156 })
157 }
158
159 #[must_use]
161 pub const fn request(&self) -> &'request IcSnapshotMetadataRequest {
162 self.request
163 }
164
165 #[must_use]
167 pub const fn metadata(&self) -> &ReadCanisterSnapshotMetadataResult {
168 &self.metadata
169 }
170
171 #[must_use]
173 pub const fn payload_checksum(&self) -> &ArtifactChecksumRecord {
174 &self.payload_checksum
175 }
176
177 #[must_use]
179 pub fn digest(&self) -> ArtifactChecksumRecord {
180 super::evidence_digest::reply(
181 b"ic-backup/ic-snapshot-metadata-reply/v1\0",
182 [&self.request.digest(), &self.payload_checksum],
183 )
184 }
185}
186
187impl fmt::Debug for IcSnapshotMetadataReply<'_> {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 f.debug_struct("IcSnapshotMetadataReply")
190 .field("request", &self.request)
191 .field("payload_checksum", &self.payload_checksum)
192 .finish_non_exhaustive()
193 }
194}
195
196#[derive(Debug, Error, Eq, PartialEq)]
198pub enum IcSnapshotMetadataError {
199 #[error("snapshot metadata exceeds {MAX_IC_SNAPSHOT_METADATA_BYTES} bytes")]
201 ReplyTooLarge,
202 #[error("invalid or unsupported snapshot metadata reply")]
204 InvalidReply,
205 #[error("snapshot v128 global exceeds 128 bits")]
207 InvalidGlobal,
208 #[error("snapshot chunk hash is not a unique 32-byte identity")]
210 InvalidChunkHash,
211}
212
213#[cfg(test)]
214mod tests;