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