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