ic_backup/model/ic_snapshot_metadata/
mod.rs1mod wire;
4
5use super::{
6 artifacts::ArtifactChecksumRecord,
7 ic_request::{
8 IcRequestError, MAX_IC_ARGUMENT_BYTES, MAX_IC_SNAPSHOT_ID_BYTES, management_request_digest,
9 },
10};
11use candid::Principal;
12use ic_management_canister_types::{
13 ReadCanisterSnapshotMetadataArgs, ReadCanisterSnapshotMetadataResult,
14};
15use std::fmt;
16use thiserror::Error;
17
18pub const MAX_IC_SNAPSHOT_METADATA_BYTES: usize = 1024 * 1024;
20pub const MAX_IC_SNAPSHOT_GLOBALS: usize = 4096;
22pub const MAX_IC_SNAPSHOT_CHUNKS: usize = 1024;
24
25#[derive(Clone)]
31pub struct IcSnapshotMetadataRequest {
32 target: String,
33 target_bytes: Vec<u8>,
34 snapshot_id: Vec<u8>,
35 arguments: Vec<u8>,
36}
37
38impl IcSnapshotMetadataRequest {
39 pub fn new(target: &str, snapshot_id: &[u8]) -> Result<Self, IcRequestError> {
45 if snapshot_id.is_empty() || snapshot_id.len() > MAX_IC_SNAPSHOT_ID_BYTES {
46 return Err(IcRequestError::InvalidSnapshotId);
47 }
48 let target =
49 super::principal::canonical_text(target).ok_or(IcRequestError::InvalidTarget)?;
50 let principal = Principal::from_text(&target).map_err(|_| IcRequestError::InvalidTarget)?;
51 let arguments = candid::encode_one(ReadCanisterSnapshotMetadataArgs {
52 canister_id: principal,
53 snapshot_id: snapshot_id.to_vec(),
54 })
55 .map_err(|error| IcRequestError::Encoding(error.to_string()))?;
56 if arguments.len() > MAX_IC_ARGUMENT_BYTES {
57 return Err(IcRequestError::ArgumentsTooLarge);
58 }
59 Ok(Self {
60 target,
61 target_bytes: principal.as_slice().to_vec(),
62 snapshot_id: snapshot_id.to_vec(),
63 arguments,
64 })
65 }
66
67 #[must_use]
69 pub fn target(&self) -> &str {
70 &self.target
71 }
72
73 #[must_use]
75 pub fn snapshot_id(&self) -> &[u8] {
76 &self.snapshot_id
77 }
78
79 #[must_use]
81 pub fn arguments(&self) -> &[u8] {
82 &self.arguments
83 }
84
85 #[must_use]
87 pub const fn receiver(&self) -> &'static str {
88 "aaaaa-aa"
89 }
90
91 #[must_use]
93 pub const fn method(&self) -> &'static str {
94 "read_canister_snapshot_metadata"
95 }
96
97 #[must_use]
99 pub fn digest(&self) -> ArtifactChecksumRecord {
100 management_request_digest(&self.target_bytes, self.method(), &self.arguments)
101 }
102}
103
104impl fmt::Debug for IcSnapshotMetadataRequest {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 f.debug_struct("IcSnapshotMetadataRequest")
107 .field("target", &self.target)
108 .field("snapshot_id_bytes", &self.snapshot_id.len())
109 .finish_non_exhaustive()
110 }
111}
112
113pub struct IcSnapshotMetadataReply<'request> {
120 request: &'request IcSnapshotMetadataRequest,
121 metadata: ReadCanisterSnapshotMetadataResult,
122 payload_checksum: ArtifactChecksumRecord,
123}
124
125impl<'request> IcSnapshotMetadataReply<'request> {
126 pub fn decode(
133 request: &'request IcSnapshotMetadataRequest,
134 bytes: &[u8],
135 ) -> Result<Self, IcSnapshotMetadataError> {
136 if bytes.len() > MAX_IC_SNAPSHOT_METADATA_BYTES {
137 return Err(IcSnapshotMetadataError::ReplyTooLarge);
138 }
139 let metadata = wire::decode(bytes)?;
140 Ok(Self {
141 request,
142 metadata,
143 payload_checksum: ArtifactChecksumRecord::from_bytes(bytes),
144 })
145 }
146
147 #[must_use]
149 pub const fn request(&self) -> &'request IcSnapshotMetadataRequest {
150 self.request
151 }
152
153 #[must_use]
155 pub const fn metadata(&self) -> &ReadCanisterSnapshotMetadataResult {
156 &self.metadata
157 }
158
159 #[must_use]
161 pub const fn payload_checksum(&self) -> &ArtifactChecksumRecord {
162 &self.payload_checksum
163 }
164
165 #[must_use]
167 pub fn digest(&self) -> ArtifactChecksumRecord {
168 let mut bytes = b"ic-backup/ic-snapshot-metadata-reply/v1\0".to_vec();
169 bytes.extend_from_slice(self.request.digest().hash().as_bytes());
170 bytes.extend_from_slice(self.payload_checksum.hash().as_bytes());
171 ArtifactChecksumRecord::from_bytes(&bytes)
172 }
173}
174
175impl fmt::Debug for IcSnapshotMetadataReply<'_> {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 f.debug_struct("IcSnapshotMetadataReply")
178 .field("request", &self.request)
179 .field("payload_checksum", &self.payload_checksum)
180 .finish_non_exhaustive()
181 }
182}
183
184#[derive(Debug, Error, Eq, PartialEq)]
186pub enum IcSnapshotMetadataError {
187 #[error("snapshot metadata exceeds {MAX_IC_SNAPSHOT_METADATA_BYTES} bytes")]
189 ReplyTooLarge,
190 #[error("invalid or unsupported snapshot metadata reply")]
192 InvalidReply,
193 #[error("snapshot v128 global exceeds 128 bits")]
195 InvalidGlobal,
196 #[error("snapshot chunk hash is not a unique 32-byte identity")]
198 InvalidChunkHash,
199}
200
201#[cfg(test)]
202mod tests;