Skip to main content

ic_backup/model/ic_snapshot_upload/reply/
mod.rs

1//! Bounded exact upload results; passive association never settles an attempt.
2
3use super::{
4    IcSnapshotUploadError, IcSnapshotUploadKind, IcSnapshotUploadRequest,
5    MAX_IC_SNAPSHOT_UPLOAD_REPLY_BYTES, validate_destination,
6};
7use crate::model::{
8    artifacts::ArtifactChecksumRecord, ic_lifecycle_reply::EMPTY_CANDID_REPLY,
9    ic_snapshot_reply::snapshot_id,
10};
11use candid::{
12    CandidType,
13    de::{DecoderConfig, IDLDeserialize},
14};
15use serde::Deserialize;
16use std::fmt;
17
18#[derive(CandidType, Deserialize)]
19struct WireMetadataReply {
20    #[serde(deserialize_with = "snapshot_id")]
21    snapshot_id: Vec<u8>,
22}
23
24/// Passive local wire result; neither branch authenticates an effect or new destination.
25#[derive(Debug, Eq, PartialEq)]
26pub enum IcSnapshotUploadReplyKind {
27    /// Exact returned raw ID, distinct from retained source and generic backend tokens.
28    Metadata {
29        /// Bounded original response bytes, with no automatic authority derivation.
30        snapshot_id: Vec<u8>,
31    },
32    /// Exact canonical empty Candid tuple; not data-transfer completeness.
33    DataAcknowledgement,
34}
35
36/// Read-only decoded association with the exact source-bound upload declaration.
37pub struct IcSnapshotUploadReply<'request, 'source> {
38    request: &'request IcSnapshotUploadRequest<'source>,
39    kind: IcSnapshotUploadReplyKind,
40    payload_checksum: ArtifactChecksumRecord,
41}
42
43impl<'request, 'source> IcSnapshotUploadReply<'request, 'source> {
44    /// Admit an exact metadata ID or canonical empty data reply under finite quotas.
45    ///
46    /// Reuses the bounded raw-ID visitor and lifecycle empty tuple owner. Exact wire
47    /// association proves no target/context authentication, exclusive allocation,
48    /// complete upload, new spending, retry, mutation settlement or release.
49    /// # Errors
50    /// Rejects excessive/invalid/extended/trailing replies and invalid/reused IDs.
51    pub fn decode(
52        request: &'request IcSnapshotUploadRequest<'source>,
53        bytes: &[u8],
54    ) -> Result<Self, IcSnapshotUploadError> {
55        if bytes.len() > MAX_IC_SNAPSHOT_UPLOAD_REPLY_BYTES {
56            return Err(IcSnapshotUploadError::ReplyTooLarge);
57        }
58        let kind = match request.kind() {
59            IcSnapshotUploadKind::Metadata => {
60                let mut config = DecoderConfig::new();
61                config
62                    .set_decoding_quota(64 * 1024)
63                    .set_skipping_quota(0)
64                    .set_max_type_len(16)
65                    .set_max_header_len(4096)
66                    .set_full_error_message(false);
67                let mut decoder = IDLDeserialize::new_with_config(bytes, &config)
68                    .map_err(|_| IcSnapshotUploadError::InvalidReply)?;
69                let reply = decoder
70                    .get_value::<WireMetadataReply>()
71                    .map_err(|_| IcSnapshotUploadError::InvalidReply)?;
72                if !decoder.is_done() {
73                    return Err(IcSnapshotUploadError::InvalidReply);
74                }
75                decoder
76                    .done()
77                    .map_err(|_| IcSnapshotUploadError::InvalidReply)?;
78                validate_destination(request.source(), &reply.snapshot_id)?;
79                IcSnapshotUploadReplyKind::Metadata {
80                    snapshot_id: reply.snapshot_id,
81                }
82            }
83            IcSnapshotUploadKind::Data { .. } => {
84                if bytes != EMPTY_CANDID_REPLY {
85                    return Err(IcSnapshotUploadError::InvalidReply);
86                }
87                IcSnapshotUploadReplyKind::DataAcknowledgement
88            }
89        };
90        Ok(Self {
91            request,
92            kind,
93            payload_checksum: ArtifactChecksumRecord::from_bytes(bytes),
94        })
95    }
96    /// Read the original exact source/wire declaration.
97    #[must_use]
98    pub const fn request(&self) -> &'request IcSnapshotUploadRequest<'source> {
99        self.request
100    }
101    /// Read passive method-specific data without outcome or dispatch admission.
102    #[must_use]
103    pub const fn kind(&self) -> &IcSnapshotUploadReplyKind {
104        &self.kind
105    }
106    /// Read exact raw-reply checksum, independent of decoded canonical values.
107    #[must_use]
108    pub const fn payload_checksum(&self) -> &ArtifactChecksumRecord {
109        &self.payload_checksum
110    }
111    /// Bind exact original source/wire association and raw bytes as local evidence.
112    #[must_use]
113    pub fn digest(&self) -> ArtifactChecksumRecord {
114        let mut bytes = b"ic-backup/ic-snapshot-upload-reply/v1\0".to_vec();
115        bytes.extend_from_slice(self.request.binding_digest().hash().as_bytes());
116        bytes.extend_from_slice(self.payload_checksum.hash().as_bytes());
117        ArtifactChecksumRecord::from_bytes(&bytes)
118    }
119}
120
121impl fmt::Debug for IcSnapshotUploadReply<'_, '_> {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        f.debug_struct("IcSnapshotUploadReply")
124            .field("method", &self.request.method())
125            .field("payload_checksum", &self.payload_checksum)
126            .finish_non_exhaustive()
127    }
128}