Skip to main content

ic_backup/model/ic_snapshot_transfer_read/
mod.rs

1//! Exact originally reserved metadata/data reads; no dispatch or transfer attestation.
2
3use crate::model::ic_update_response::IcUpdateResponseInput;
4use crate::model::{
5    artifacts::ArtifactChecksumRecord,
6    attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
7    ic_snapshot_data::{IcSnapshotDataRequest, MAX_IC_SNAPSHOT_DATA_REPLY_BYTES},
8    ic_snapshot_metadata::IcSnapshotMetadataRequest,
9    operation_plan::{OperationPlanError, OperationPlanRecord},
10};
11use std::fmt;
12use thiserror::Error;
13
14/// The existing two bounded replicated-update read payloads; no new encoder.
15#[derive(Clone, Copy, Debug)]
16pub enum IcSnapshotTransferReadPayload<'request, 'metadata> {
17    /// Read the exact original raw snapshot ID's metadata.
18    Metadata(&'request IcSnapshotMetadataRequest),
19    /// Read one metadata-bound range or known chunk hash.
20    Data(&'request IcSnapshotDataRequest<'metadata>),
21}
22
23impl IcSnapshotTransferReadPayload<'_, '_> {
24    pub(crate) fn validate_binding(
25        &self,
26        authority: &AttemptAuthorityRecord,
27    ) -> Result<(), IcSnapshotTransferReadError> {
28        if self.target() != authority.binding().target()
29            || self.digest().hash() != authority.binding().request()
30        {
31            return Err(IcSnapshotTransferReadError::PayloadMismatch);
32        }
33        Ok(())
34    }
35    /// Read the canonical effective routing target.
36    #[must_use]
37    pub fn target(&self) -> &str {
38        match self {
39            Self::Metadata(payload) => payload.target(),
40            Self::Data(payload) => payload.target(),
41        }
42    }
43    /// Read the management receiver; both methods use host replicated updates.
44    #[must_use]
45    pub const fn receiver(&self) -> &'static str {
46        match self {
47            Self::Metadata(payload) => payload.receiver(),
48            Self::Data(payload) => payload.receiver(),
49        }
50    }
51    /// Read the exact original method name.
52    #[must_use]
53    pub const fn method(&self) -> &'static str {
54        match self {
55            Self::Metadata(payload) => payload.method(),
56            Self::Data(payload) => payload.method(),
57        }
58    }
59    /// Read existing canonical bounded Candid bytes.
60    #[must_use]
61    pub fn arguments(&self) -> &[u8] {
62        match self {
63            Self::Metadata(payload) => payload.arguments(),
64            Self::Data(payload) => payload.arguments(),
65        }
66    }
67    /// Reuse the original nonrecursive wire digest; metadata evidence stays separate.
68    #[must_use]
69    pub fn digest(&self) -> ArtifactChecksumRecord {
70        match self {
71            Self::Metadata(payload) => payload.digest(),
72            Self::Data(payload) => payload.digest(),
73        }
74    }
75}
76
77/// Full original plan and already consumed update reservation for one transfer read.
78///
79/// A semantic read still uses replicated update ingress and the existing mutation
80/// reservation lane. It grants no fresh permission or proof of never-dispatched
81/// custody; reconstruction never permits repeating a lost read.
82#[derive(Debug)]
83pub struct IcSnapshotTransferReadRequest<'request, 'metadata> {
84    plan: &'request OperationPlanRecord,
85    payload: IcSnapshotTransferReadPayload<'request, 'metadata>,
86    authority: AttemptAuthorityRecord,
87    mutation_attempt: u32,
88}
89
90impl<'request, 'metadata> IcSnapshotTransferReadRequest<'request, 'metadata> {
91    /// Bind exact original target/wire bytes and immutable original allowance.
92    /// # Errors
93    /// Rejects another plan/operation/payload, absent or changed update reservation,
94    /// or already pending recovery. Creates no journal and spends nothing.
95    pub fn new(
96        plan: &'request OperationPlanRecord,
97        operation_sequence: u64,
98        journal: &AttemptJournalRecord,
99        payload: IcSnapshotTransferReadPayload<'request, 'metadata>,
100    ) -> Result<Self, IcSnapshotTransferReadError> {
101        let authority = plan.attempt_authority(operation_sequence)?;
102        if journal.authority() != &authority {
103            return Err(IcSnapshotTransferReadError::AuthorityMismatch);
104        }
105        payload.validate_binding(&authority)?;
106        let mutation_attempt = journal
107            .pending_mutation()
108            .ok_or(IcSnapshotTransferReadError::NoPendingMutation)?;
109        let request = Self {
110            plan,
111            payload,
112            authority,
113            mutation_attempt,
114        };
115        request.validate_journal(journal)?;
116        Ok(request)
117    }
118    /// Read full original context, inventory, graph and allowances.
119    #[must_use]
120    pub const fn plan(&self) -> &'request OperationPlanRecord {
121        self.plan
122    }
123    /// Read the exact metadata/data payload without reconstructing its bytes.
124    #[must_use]
125    pub const fn payload(&self) -> IcSnapshotTransferReadPayload<'request, 'metadata> {
126        self.payload
127    }
128    /// Read the full original operation authority.
129    #[must_use]
130    pub const fn authority(&self) -> &AttemptAuthorityRecord {
131        &self.authority
132    }
133    /// Read the already consumed replicated-update attempt.
134    #[must_use]
135    pub const fn mutation_attempt(&self) -> u32 {
136        self.mutation_attempt
137    }
138    /// Recheck original reservation before passive response association.
139    /// # Errors
140    /// Rejects changed authority, settled/replaced update or pending recovery.
141    pub fn validate_journal(
142        &self,
143        journal: &AttemptJournalRecord,
144    ) -> Result<(), IcSnapshotTransferReadError> {
145        journal.validate_pending_mutation(
146            &self.authority,
147            self.mutation_attempt,
148            IcSnapshotTransferReadError::AuthorityMismatch,
149            IcSnapshotTransferReadError::MutationMismatch,
150            IcSnapshotTransferReadError::ObservationPending,
151        )
152    }
153}
154
155/// Bounded immutable raw reply, with no receipt, persisted authority or default.
156#[derive(Clone)]
157pub struct IcSnapshotTransferReadResponse {
158    input: IcUpdateResponseInput,
159}
160
161impl IcSnapshotTransferReadResponse {
162    /// Admit the existing finite raw-data ceiling and original attempt range.
163    /// # Errors
164    /// Rejects invalid attempt, excessive bytes and invalid target principals.
165    pub fn new(mut input: IcUpdateResponseInput) -> Result<Self, IcSnapshotTransferReadError> {
166        input.admit(
167            MAX_IC_SNAPSHOT_DATA_REPLY_BYTES,
168            IcSnapshotTransferReadError::InvalidAttempt,
169            IcSnapshotTransferReadError::ReplyTooLarge,
170            IcSnapshotTransferReadError::InvalidTarget,
171        )?;
172        Ok(Self { input })
173    }
174    /// Read exact retained claims and raw bytes; no mutable access is exposed.
175    #[must_use]
176    pub const fn input(&self) -> &IcUpdateResponseInput {
177        &self.input
178    }
179}
180
181impl fmt::Debug for IcSnapshotTransferReadResponse {
182    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
183        self.input
184            .fmt_redacted(formatter, "IcSnapshotTransferReadResponse")
185    }
186}
187
188/// Structural read rejection; every denial retains original accounting and evidence.
189#[derive(Debug, Error)]
190pub enum IcSnapshotTransferReadError {
191    /// Current journal differs from full original authority.
192    #[error("snapshot transfer read authority differs")]
193    AuthorityMismatch,
194    /// Original target or exact wire digest differs.
195    #[error("snapshot transfer read payload differs")]
196    PayloadMismatch,
197    /// No original replicated-update reservation is pending.
198    #[error("snapshot transfer read requires a pending original update")]
199    NoPendingMutation,
200    /// Original update was changed or settled.
201    #[error("snapshot transfer read original attempt differs")]
202    MutationMismatch,
203    /// Existing recovery observation remains pending.
204    #[error("snapshot transfer read recovery observation is pending")]
205    ObservationPending,
206    /// Attempt is outside the existing 1..=1,024 bound.
207    #[error("invalid snapshot transfer read attempt")]
208    InvalidAttempt,
209    /// Raw reply exceeds the existing 2 MiB data wire ceiling.
210    #[error("snapshot transfer read reply too large")]
211    ReplyTooLarge,
212    /// Claimed actual target is not a principal.
213    #[error("invalid snapshot transfer read target")]
214    InvalidTarget,
215    /// Original plan cannot derive this operation authority.
216    #[error(transparent)]
217    Plan(#[from] OperationPlanError),
218}