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