use super::{
IcSnapshotUploadError, IcSnapshotUploadKind, IcSnapshotUploadRequest,
MAX_IC_SNAPSHOT_UPLOAD_REPLY_BYTES, validate_destination,
};
use crate::model::{
artifacts::ArtifactChecksumRecord, ic_lifecycle_reply::EMPTY_CANDID_REPLY,
ic_snapshot_reply::snapshot_id,
};
use candid::{
CandidType,
de::{DecoderConfig, IDLDeserialize},
};
use serde::Deserialize;
use std::fmt;
#[derive(CandidType, Deserialize)]
struct WireMetadataReply {
#[serde(deserialize_with = "snapshot_id")]
snapshot_id: Vec<u8>,
}
#[derive(Debug, Eq, PartialEq)]
pub enum IcSnapshotUploadReplyKind {
Metadata {
snapshot_id: Vec<u8>,
},
DataAcknowledgement,
}
pub struct IcSnapshotUploadReply<'request, 'source> {
request: &'request IcSnapshotUploadRequest<'source>,
kind: IcSnapshotUploadReplyKind,
payload_checksum: ArtifactChecksumRecord,
}
impl<'request, 'source> IcSnapshotUploadReply<'request, 'source> {
pub fn decode(
request: &'request IcSnapshotUploadRequest<'source>,
bytes: &[u8],
) -> Result<Self, IcSnapshotUploadError> {
if bytes.len() > MAX_IC_SNAPSHOT_UPLOAD_REPLY_BYTES {
return Err(IcSnapshotUploadError::ReplyTooLarge);
}
let kind = match request.kind() {
IcSnapshotUploadKind::Metadata => {
let mut config = DecoderConfig::new();
config
.set_decoding_quota(64 * 1024)
.set_skipping_quota(0)
.set_max_type_len(16)
.set_max_header_len(4096)
.set_full_error_message(false);
let mut decoder = IDLDeserialize::new_with_config(bytes, &config)
.map_err(|_| IcSnapshotUploadError::InvalidReply)?;
let reply = decoder
.get_value::<WireMetadataReply>()
.map_err(|_| IcSnapshotUploadError::InvalidReply)?;
if !decoder.is_done() {
return Err(IcSnapshotUploadError::InvalidReply);
}
decoder
.done()
.map_err(|_| IcSnapshotUploadError::InvalidReply)?;
validate_destination(request.source(), &reply.snapshot_id)?;
IcSnapshotUploadReplyKind::Metadata {
snapshot_id: reply.snapshot_id,
}
}
IcSnapshotUploadKind::Data { .. } => {
if bytes != EMPTY_CANDID_REPLY {
return Err(IcSnapshotUploadError::InvalidReply);
}
IcSnapshotUploadReplyKind::DataAcknowledgement
}
};
Ok(Self {
request,
kind,
payload_checksum: ArtifactChecksumRecord::from_bytes(bytes),
})
}
#[must_use]
pub const fn request(&self) -> &'request IcSnapshotUploadRequest<'source> {
self.request
}
#[must_use]
pub const fn kind(&self) -> &IcSnapshotUploadReplyKind {
&self.kind
}
#[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-upload-reply/v1\0".to_vec();
bytes.extend_from_slice(self.request.binding_digest().hash().as_bytes());
bytes.extend_from_slice(self.payload_checksum.hash().as_bytes());
ArtifactChecksumRecord::from_bytes(&bytes)
}
}
impl fmt::Debug for IcSnapshotUploadReply<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IcSnapshotUploadReply")
.field("method", &self.request.method())
.field("payload_checksum", &self.payload_checksum)
.finish_non_exhaustive()
}
}