mod wire;
use super::{
artifacts::ArtifactChecksumRecord,
ic_request::{
IcRequestError, MAX_IC_ARGUMENT_BYTES, MAX_IC_SNAPSHOT_ID_BYTES, management_request_digest,
},
};
use candid::Principal;
use ic_management_canister_types::{
ReadCanisterSnapshotMetadataArgs, ReadCanisterSnapshotMetadataResult,
};
use std::fmt;
use thiserror::Error;
pub const MAX_IC_SNAPSHOT_METADATA_BYTES: usize = 1024 * 1024;
pub const MAX_IC_SNAPSHOT_GLOBALS: usize = 4096;
pub const MAX_IC_SNAPSHOT_CHUNKS: usize = 1024;
#[derive(Clone)]
pub struct IcSnapshotMetadataRequest {
target: String,
target_bytes: Vec<u8>,
snapshot_id: Vec<u8>,
arguments: Vec<u8>,
}
impl IcSnapshotMetadataRequest {
pub fn new(target: &str, snapshot_id: &[u8]) -> Result<Self, IcRequestError> {
if snapshot_id.is_empty() || snapshot_id.len() > MAX_IC_SNAPSHOT_ID_BYTES {
return Err(IcRequestError::InvalidSnapshotId);
}
let target =
super::principal::canonical_text(target).ok_or(IcRequestError::InvalidTarget)?;
let principal = Principal::from_text(&target).map_err(|_| IcRequestError::InvalidTarget)?;
let arguments = candid::encode_one(ReadCanisterSnapshotMetadataArgs {
canister_id: principal,
snapshot_id: snapshot_id.to_vec(),
})
.map_err(|error| IcRequestError::Encoding(error.to_string()))?;
if arguments.len() > MAX_IC_ARGUMENT_BYTES {
return Err(IcRequestError::ArgumentsTooLarge);
}
Ok(Self {
target,
target_bytes: principal.as_slice().to_vec(),
snapshot_id: snapshot_id.to_vec(),
arguments,
})
}
#[must_use]
pub fn target(&self) -> &str {
&self.target
}
#[must_use]
pub fn snapshot_id(&self) -> &[u8] {
&self.snapshot_id
}
#[must_use]
pub fn arguments(&self) -> &[u8] {
&self.arguments
}
#[must_use]
pub const fn receiver(&self) -> &'static str {
"aaaaa-aa"
}
#[must_use]
pub const fn method(&self) -> &'static str {
"read_canister_snapshot_metadata"
}
#[must_use]
pub fn digest(&self) -> ArtifactChecksumRecord {
management_request_digest(&self.target_bytes, self.method(), &self.arguments)
}
}
impl fmt::Debug for IcSnapshotMetadataRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IcSnapshotMetadataRequest")
.field("target", &self.target)
.field("snapshot_id_bytes", &self.snapshot_id.len())
.finish_non_exhaustive()
}
}
pub struct IcSnapshotMetadataReply<'request> {
request: &'request IcSnapshotMetadataRequest,
metadata: ReadCanisterSnapshotMetadataResult,
payload_checksum: ArtifactChecksumRecord,
}
impl<'request> IcSnapshotMetadataReply<'request> {
pub fn decode(
request: &'request IcSnapshotMetadataRequest,
bytes: &[u8],
) -> Result<Self, IcSnapshotMetadataError> {
if bytes.len() > MAX_IC_SNAPSHOT_METADATA_BYTES {
return Err(IcSnapshotMetadataError::ReplyTooLarge);
}
let metadata = wire::decode(bytes)?;
Ok(Self {
request,
metadata,
payload_checksum: ArtifactChecksumRecord::from_bytes(bytes),
})
}
#[must_use]
pub const fn request(&self) -> &'request IcSnapshotMetadataRequest {
self.request
}
#[must_use]
pub const fn metadata(&self) -> &ReadCanisterSnapshotMetadataResult {
&self.metadata
}
#[must_use]
pub const fn payload_checksum(&self) -> &ArtifactChecksumRecord {
&self.payload_checksum
}
#[must_use]
pub fn digest(&self) -> ArtifactChecksumRecord {
let mut bytes = b"ic-backup/ic-snapshot-metadata-reply/v1\0".to_vec();
bytes.extend_from_slice(self.request.digest().hash().as_bytes());
bytes.extend_from_slice(self.payload_checksum.hash().as_bytes());
ArtifactChecksumRecord::from_bytes(&bytes)
}
}
impl fmt::Debug for IcSnapshotMetadataReply<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IcSnapshotMetadataReply")
.field("request", &self.request)
.field("payload_checksum", &self.payload_checksum)
.finish_non_exhaustive()
}
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum IcSnapshotMetadataError {
#[error("snapshot metadata exceeds {MAX_IC_SNAPSHOT_METADATA_BYTES} bytes")]
ReplyTooLarge,
#[error("invalid or unsupported snapshot metadata reply")]
InvalidReply,
#[error("snapshot v128 global exceeds 128 bits")]
InvalidGlobal,
#[error("snapshot chunk hash is not a unique 32-byte identity")]
InvalidChunkHash,
}
#[cfg(test)]
mod tests;