Skip to main content

ic_backup/policy/ic_snapshot_transfer_read/
mod.rs

1//! Pure current-reservation association using the original metadata/data decoder owners.
2
3use super::response_identity::{ResponseIdentity, ResponseIdentityMismatch};
4use crate::model::{
5    attempt_journal::AttemptJournalRecord,
6    ic_snapshot_data::{IcSnapshotDataError, IcSnapshotDataReply},
7    ic_snapshot_metadata::{IcSnapshotMetadataError, IcSnapshotMetadataReply},
8    ic_snapshot_transfer_read::{
9        IcSnapshotTransferReadError, IcSnapshotTransferReadPayload, IcSnapshotTransferReadRequest,
10        IcSnapshotTransferReadResponse,
11    },
12};
13use thiserror::Error;
14
15/// Existing bounded method-specific reply evidence; not authenticated transfer proof.
16#[derive(Debug)]
17pub enum IcSnapshotTransferReadReply<'request, 'metadata> {
18    /// Exact original metadata under its original raw-ID request.
19    Metadata(IcSnapshotMetadataReply<'request>),
20    /// Exact requested range length or known hash-checked chunk bytes.
21    Data(IcSnapshotDataReply<'request, 'metadata>),
22}
23
24/// Passive response together with its original method-specific decoded projection.
25#[derive(Debug)]
26pub struct IcSnapshotTransferReadView<'request, 'metadata> {
27    response: &'request IcSnapshotTransferReadResponse,
28    reply: IcSnapshotTransferReadReply<'request, 'metadata>,
29}
30
31impl<'request, 'metadata> IcSnapshotTransferReadView<'request, 'metadata> {
32    /// Read exact retained claims, raw bytes and opaque provider evidence.
33    #[must_use]
34    pub const fn response(&self) -> &'request IcSnapshotTransferReadResponse {
35        self.response
36    }
37    /// Read the original bounded decoder's projection without a new codec.
38    #[must_use]
39    pub const fn reply(&self) -> &IcSnapshotTransferReadReply<'request, 'metadata> {
40        &self.reply
41    }
42}
43
44/// Match original authority/reservation and actual claimed context/target, then decode.
45///
46/// This performs no IO or journal transition and grants no fresh read permission,
47/// authenticity, retry, aggregate coverage, durable bytes or terminal/release proof.
48/// Failures/lost replies stay pending. Integrations qualify actual authentication,
49/// freshness, snapshot custody and never-dispatched original-call custody.
50/// # Errors
51/// Rejects stale reservations, mismatched claims and existing bounded decoder errors.
52pub fn validate_response<'request, 'metadata>(
53    request: &IcSnapshotTransferReadRequest<'request, 'metadata>,
54    journal: &AttemptJournalRecord,
55    response: &'request IcSnapshotTransferReadResponse,
56) -> Result<IcSnapshotTransferReadView<'request, 'metadata>, IcSnapshotTransferReadAssociationError>
57{
58    request.validate_journal(journal)?;
59    let input = response.input();
60    ResponseIdentity {
61        authority: &input.authority,
62        mutation_attempt: input.mutation_attempt,
63        context: &input.context,
64        target: &input.target,
65    }
66    .compare(&ResponseIdentity {
67        authority: &request.authority().digest(),
68        mutation_attempt: request.mutation_attempt(),
69        context: request.plan().context(),
70        target: request.payload().target(),
71    })
72    .map_err(|mismatch| match mismatch {
73        ResponseIdentityMismatch::Authority => {
74            IcSnapshotTransferReadAssociationError::AuthorityMismatch
75        }
76        ResponseIdentityMismatch::Attempt => {
77            IcSnapshotTransferReadAssociationError::AttemptMismatch
78        }
79        ResponseIdentityMismatch::Context => {
80            IcSnapshotTransferReadAssociationError::ContextMismatch
81        }
82        ResponseIdentityMismatch::Target => IcSnapshotTransferReadAssociationError::TargetMismatch,
83    })?;
84    let reply = match request.payload() {
85        IcSnapshotTransferReadPayload::Metadata(payload) => IcSnapshotTransferReadReply::Metadata(
86            IcSnapshotMetadataReply::decode(payload, &input.reply)?,
87        ),
88        IcSnapshotTransferReadPayload::Data(payload) => {
89            IcSnapshotTransferReadReply::Data(IcSnapshotDataReply::decode(payload, &input.reply)?)
90        }
91    };
92    Ok(IcSnapshotTransferReadView { response, reply })
93}
94
95/// Passive association denial; no outcome or allowance refund follows.
96#[derive(Debug, Error)]
97pub enum IcSnapshotTransferReadAssociationError {
98    /// Current original journal no longer matches the read request.
99    #[error(transparent)]
100    Reservation(#[from] IcSnapshotTransferReadError),
101    /// Claimed full original authority differs.
102    #[error("snapshot transfer read response authority differs")]
103    AuthorityMismatch,
104    /// Claimed original attempt differs.
105    #[error("snapshot transfer read response attempt differs")]
106    AttemptMismatch,
107    /// Actual claimed network/caller/release differs.
108    #[error("snapshot transfer read response context differs")]
109    ContextMismatch,
110    /// Actual claimed routing target differs.
111    #[error("snapshot transfer read response target differs")]
112    TargetMismatch,
113    /// Original metadata decoder rejects shape or its tighter 1 MiB bound.
114    #[error(transparent)]
115    Metadata(#[from] IcSnapshotMetadataError),
116    /// Original data decoder rejects shape, bound, range length or chunk hash.
117    #[error(transparent)]
118    Data(#[from] IcSnapshotDataError),
119}
120
121#[cfg(test)]
122mod tests;