Skip to main content

ic_backup/model/ic_snapshot_upload_data_observation/
mod.rs

1//! Exact already reserved destination-data observations of unresolved uploads.
2
3use crate::model::attempt_journal::ObservationReservation;
4use crate::model::{
5    artifacts::ArtifactChecksumRecord,
6    attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
7    ic_observation::{
8        IcObservationRequestError, IcObservationResponseError, IcObservationResponseInput,
9        fmt_response_input, validate_response_input,
10    },
11    ic_snapshot_data::{IcSnapshotDataRequest, MAX_IC_SNAPSHOT_DATA_REPLY_BYTES},
12    ic_snapshot_upload::{
13        IcSnapshotUploadAttemptError, IcSnapshotUploadKind, IcSnapshotUploadRequest,
14        original_authority,
15    },
16    operation_plan::OperationPlanRecord,
17};
18use std::fmt;
19use thiserror::Error;
20
21mod settlement;
22pub use settlement::{IcSnapshotUploadDataAttribution, IcSnapshotUploadDataSettlement};
23
24/// Original data-upload intent and its independently spent exact readback request.
25///
26/// Retain destination metadata and original read bytes before reservation. Their
27/// authenticity, allocation attribution, fresh access, chronology and proof of no
28/// prior observation dispatch remain integration-owned. Reconstruction is no retry.
29#[derive(Debug)]
30pub struct IcSnapshotUploadDataObservationRequest<'request, 'source, 'metadata> {
31    plan: &'request OperationPlanRecord,
32    mutation: &'request IcSnapshotUploadRequest<'source>,
33    payload: &'request IcSnapshotDataRequest<'metadata>,
34    reservation: ObservationReservation,
35    original_chunk_checksum: &'request ArtifactChecksumRecord,
36}
37
38impl<'request, 'source, 'metadata>
39    IcSnapshotUploadDataObservationRequest<'request, 'source, 'metadata>
40{
41    /// Bind one original data upload to a reserved read of the same destination/extent.
42    ///
43    /// Metadata allocation, other IDs, targets, regions, offsets, lengths or chunk
44    /// hashes reject. Destination metadata must declare uploaded source and original
45    /// region sizes. This neither authenticates that metadata nor spends.
46    /// # Errors
47    /// Rejects changed originals, mismatching readback and missing reservations.
48    pub fn new(
49        plan: &'request OperationPlanRecord,
50        operation_sequence: u64,
51        journal: &AttemptJournalRecord,
52        mutation: &'request IcSnapshotUploadRequest<'source>,
53        payload: &'request IcSnapshotDataRequest<'metadata>,
54    ) -> Result<Self, IcSnapshotUploadDataObservationError> {
55        let IcSnapshotUploadKind::Data {
56            snapshot_id,
57            source_kind,
58            chunk_checksum,
59            ..
60        } = mutation.kind()
61        else {
62            return Err(IcSnapshotUploadDataObservationError::DataUploadRequired);
63        };
64        let authority = original_authority(plan, operation_sequence, journal, mutation)?;
65        if payload.target() != mutation.target()
66            || payload.snapshot_id() != snapshot_id
67            || !payload.matches_kind(source_kind)
68        {
69            return Err(IcSnapshotUploadDataObservationError::ReadbackMismatch);
70        }
71        let actual = payload.metadata().metadata();
72        if !matches!(
73            actual.source,
74            Some(ic_platform::management_canister_types::SnapshotSource::MetadataUpload(_))
75        ) || payload.metadata().region_sizes() != mutation.source().region_sizes()
76        {
77            return Err(IcSnapshotUploadDataObservationError::DestinationMetadataMismatch);
78        }
79        let reservation = ObservationReservation::new(authority, journal, payload.digest())
80            .map_err(crate::model::ic_observation::IcObservationRequestError::from)?;
81        let request = Self {
82            plan,
83            mutation,
84            payload,
85            reservation,
86            original_chunk_checksum: chunk_checksum,
87        };
88        Ok(request)
89    }
90
91    /// Read the retained complete original plan and immutable allowances.
92    #[must_use]
93    pub const fn plan(&self) -> &OperationPlanRecord {
94        self.plan
95    }
96    /// Read exact original source-bound upload bytes, without a dispatch permit.
97    #[must_use]
98    pub const fn mutation(&self) -> &'request IcSnapshotUploadRequest<'source> {
99        self.mutation
100    }
101    /// Read exact original destination metadata/read request, without fresh access.
102    #[must_use]
103    pub const fn payload(&self) -> &'request IcSnapshotDataRequest<'metadata> {
104        self.payload
105    }
106    /// Read original operation authority and assigned limits.
107    #[must_use]
108    pub const fn authority(&self) -> &AttemptAuthorityRecord {
109        &self.reservation.authority
110    }
111    /// Read the original pending data-upload attempt.
112    #[must_use]
113    pub const fn mutation_attempt(&self) -> u32 {
114        self.reservation.mutation_attempt
115    }
116    /// Read the already consumed exact data-read observation attempt.
117    #[must_use]
118    pub const fn observation_attempt(&self) -> u32 {
119        self.reservation.observation_attempt
120    }
121    /// Read original encoded chunk identity; it establishes no write attribution.
122    #[must_use]
123    pub const fn original_chunk_checksum(&self) -> &ArtifactChecksumRecord {
124        self.original_chunk_checksum
125    }
126    /// Recheck original current authority, pending IDs and exact read digest.
127    /// # Errors
128    /// Rejects changed/settled reservations and different original read bytes.
129    pub fn validate_journal(
130        &self,
131        journal: &AttemptJournalRecord,
132    ) -> Result<(), IcObservationRequestError> {
133        self.reservation
134            .validate(journal)
135            .map_err(IcObservationRequestError::from)
136    }
137}
138
139/// Exact original data/readback declaration denial, with no outcome transition.
140#[derive(Debug, Error)]
141pub enum IcSnapshotUploadDataObservationError {
142    /// Metadata allocation is outside this boundary.
143    #[error("data upload observation requires original data intent")]
144    DataUploadRequired,
145    /// Destination target, raw ID or exact original extent/hash differs.
146    #[error("data upload observation readback differs")]
147    ReadbackMismatch,
148    /// Destination source is unknown/not uploaded, or declared region sizes differ.
149    #[error("data upload observation destination metadata differs")]
150    DestinationMetadataMismatch,
151    /// Canonical original upload authority/source admission rejected.
152    #[error(transparent)]
153    Upload(#[from] IcSnapshotUploadAttemptError),
154    /// Canonical current original observation reservation rejected.
155    #[error(transparent)]
156    Observation(#[from] IcObservationRequestError),
157}
158
159/// Passive destination-data claims using the existing 2 MiB data-reply ceiling.
160///
161/// The existing status/list response keeps its 1 MiB ceiling. This owner reuses
162/// canonical attempt/principal admission and redaction without widening that API.
163#[derive(Clone)]
164pub struct IcSnapshotUploadDataObservationResponse {
165    input: IcObservationResponseInput,
166}
167
168impl IcSnapshotUploadDataObservationResponse {
169    /// Admit bounded raw bytes and passive chronological/canonical claims.
170    /// # Errors
171    /// Rejects invalid attempts/principals and replies over the existing data bound.
172    pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
173        validate_response_input(&mut input, MAX_IC_SNAPSHOT_DATA_REPLY_BYTES)?;
174        Ok(Self { input })
175    }
176    /// Read immutable existing claim fields and exact original raw data reply.
177    #[must_use]
178    pub const fn input(&self) -> &IcObservationResponseInput {
179        &self.input
180    }
181}
182impl fmt::Debug for IcSnapshotUploadDataObservationResponse {
183    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
184        fmt_response_input(
185            &self.input,
186            formatter,
187            "IcSnapshotUploadDataObservationResponse",
188        )
189    }
190}
191
192#[cfg(test)]
193mod tests;