mod wire;
use super::{
artifacts::ArtifactChecksumRecord,
ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
};
use thiserror::Error;
pub const MAX_IC_SNAPSHOT_REPLY_BYTES: usize = 1024 * 1024;
pub const MAX_IC_SNAPSHOT_REPLY_ENTRIES: usize = 1024;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcSnapshotInfo {
id: Vec<u8>,
taken_at_timestamp: u64,
total_size: u64,
}
impl IcSnapshotInfo {
#[must_use]
pub fn id(&self) -> &[u8] {
&self.id
}
#[must_use]
pub const fn taken_at_timestamp(&self) -> u64 {
self.taken_at_timestamp
}
#[must_use]
pub const fn total_size(&self) -> u64 {
self.total_size
}
}
#[derive(Debug)]
pub struct IcSnapshotReply<'request> {
request: &'request IcManagementRequestRecord,
snapshots: Vec<IcSnapshotInfo>,
payload_checksum: ArtifactChecksumRecord,
}
impl<'request> IcSnapshotReply<'request> {
pub fn decode(
request: &'request IcManagementRequestRecord,
bytes: &[u8],
) -> Result<Self, IcSnapshotReplyError> {
let method = request.method();
if !matches!(
method,
IcManagementMethodRecord::TakeCanisterSnapshot
| IcManagementMethodRecord::ListCanisterSnapshots
) {
return Err(IcSnapshotReplyError::UnsupportedMethod { method });
}
if bytes.len() > MAX_IC_SNAPSHOT_REPLY_BYTES {
return Err(IcSnapshotReplyError::ReplyTooLarge);
}
let mut snapshots = wire::decode(method, bytes)?;
snapshots.sort_unstable_by(|left, right| left.id.cmp(&right.id));
if snapshots.windows(2).any(|pair| pair[0].id == pair[1].id) {
return Err(IcSnapshotReplyError::DuplicateSnapshotId);
}
Ok(Self {
request,
snapshots,
payload_checksum: ArtifactChecksumRecord::from_bytes(bytes),
})
}
#[must_use]
pub const fn request(&self) -> &'request IcManagementRequestRecord {
self.request
}
#[must_use]
pub fn snapshots(&self) -> &[IcSnapshotInfo] {
&self.snapshots
}
#[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-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)
}
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum IcSnapshotReplyError {
#[error("snapshot reply codec does not support {method:?}")]
UnsupportedMethod {
method: IcManagementMethodRecord,
},
#[error("snapshot reply exceeds {MAX_IC_SNAPSHOT_REPLY_BYTES} bytes")]
ReplyTooLarge,
#[error("invalid or unbounded snapshot Candid reply")]
InvalidReply,
#[error("snapshot inventory contains duplicate raw identifiers")]
DuplicateSnapshotId,
}
#[cfg(test)]
mod tests;