Skip to main content

ic_backup/ops/persistence/download_journal/ic_snapshot_artifact/upload/
mod.rs

1//! Fresh original source-byte preparation; payloads confer no upload permission.
2
3mod plan;
4mod session;
5pub use plan::IcSnapshotDataUploadPreparationError;
6
7use super::{
8    DownloadJournalGuard, File, IcSnapshotArtifactError, REGIONS, member,
9    verification::open_regular_child, with_artifact_directory,
10};
11use crate::model::{
12    ic_snapshot_data::{MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES, validate_kind},
13    ic_snapshot_metadata::IcSnapshotMetadataReply,
14    ic_snapshot_upload::{
15        IcSnapshotDataUploadPlan, IcSnapshotUploadError, IcSnapshotUploadRequest,
16    },
17    operation_plan::OperationPlanRecord,
18};
19use ic_platform::management_canister_types::SnapshotDataKind;
20use std::io::{Read, Seek, SeekFrom};
21use thiserror::Error;
22
23impl DownloadJournalGuard<'_> {
24    pub(crate) fn upload_session<'a, 'workflow, 'request, 'source>(
25        &'a self,
26        snapshot: &'a str,
27        upload: &'a IcSnapshotDataUploadPlan<'workflow, 'request, 'source>,
28    ) -> Result<
29        session::UploadSession<'a, 'workflow, 'request, 'source>,
30        IcSnapshotUploadArtifactError,
31    > {
32        session::UploadSession::new(self, snapshot, upload)
33    }
34    /// Freshly verify an original published IC source and encode exact upload metadata.
35    ///
36    /// Requires the full retained source plan, unchanged journal and complete Durable
37    /// selection. Target remains the source canister and replacement stays absent.
38    /// The payload must precede its separately retained original upload plan/reservation.
39    /// Stable future bytes, authentic source, actual context/permissions and same-release
40    /// restore obligations stay integration-owned. Nothing is written or dispatched.
41    /// # Errors
42    /// Rejects original/byte/custody drift or unrepresentable original metadata.
43    pub fn prepare_ic_snapshot_upload_metadata<'source>(
44        &self,
45        plan: &'source OperationPlanRecord,
46        snapshot: &str,
47        metadata: &'source IcSnapshotMetadataReply<'source>,
48    ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
49        let checksum = self.verify_ic_snapshot_artifact(plan, snapshot, metadata)?;
50        Ok(IcSnapshotUploadRequest::metadata(
51            plan, metadata, &checksum,
52        )?)
53    }
54
55    /// Read one bounded exact source extent/chunk and encode a distinct destination ID.
56    ///
57    /// Fresh full IC-tree verification before/after descriptor-relative reading binds
58    /// actual bytes to the original metadata/checksum and guarded source plan/journal.
59    /// This is an explicit local byte operation, not resume or a transfer-completion view.
60    /// Only one <=1 MiB chunk is buffered; no aggregate region or allowance is allocated.
61    /// The caller qualifies destination allocation and retains the new exact data intent
62    /// before its own reservation. No source/restore reference, journal or fence changes.
63    /// # Errors
64    /// Rejects wrong source declaration, ranges/hash/IDs, changed/unsafe files or custody,
65    /// short reads, encoding failures and mismatching retained original evidence.
66    pub fn prepare_ic_snapshot_upload_data<'source>(
67        &self,
68        plan: &OperationPlanRecord,
69        snapshot: &str,
70        metadata_upload: &IcSnapshotUploadRequest<'source>,
71        snapshot_id: &[u8],
72        source_kind: SnapshotDataKind,
73    ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
74        let metadata = metadata_upload.source();
75        metadata_upload.validate_data_destination(snapshot_id)?;
76        validate_kind(metadata, &source_kind).map_err(IcSnapshotUploadError::from)?;
77        self.with_upload_source(plan, snapshot, metadata_upload, |directory| {
78            let chunk = read_chunk(directory, metadata, &source_kind)?;
79            Ok(IcSnapshotUploadRequest::data(
80                metadata_upload,
81                snapshot_id,
82                source_kind,
83                &chunk,
84            )?)
85        })
86    }
87
88    fn with_upload_source<T>(
89        &self,
90        plan: &OperationPlanRecord,
91        snapshot: &str,
92        metadata_upload: &IcSnapshotUploadRequest<'_>,
93        read: impl FnOnce(&File) -> Result<T, IcSnapshotUploadArtifactError>,
94    ) -> Result<T, IcSnapshotUploadArtifactError> {
95        let metadata = metadata_upload.source();
96        self.require_upload_source(plan, snapshot, metadata_upload)?;
97        let entry = self
98            .record
99            .artifact(metadata.request().target(), snapshot)
100            .map_err(super::DownloadJournalError::from)
101            .map_err(IcSnapshotArtifactError::from)?;
102        with_artifact_directory(self.layout.root(), entry.artifact_path(), read, || {
103            self.require_upload_source(plan, snapshot, metadata_upload)
104        })
105    }
106
107    fn require_upload_source(
108        &self,
109        plan: &OperationPlanRecord,
110        snapshot: &str,
111        upload: &IcSnapshotUploadRequest<'_>,
112    ) -> Result<(), IcSnapshotUploadArtifactError> {
113        if plan.digest() != upload.source_plan().digest() {
114            return Err(IcSnapshotUploadArtifactError::SourceMismatch);
115        }
116        let actual = self.verify_ic_snapshot_artifact(plan, snapshot, upload.source())?;
117        if actual != *upload.source_checksum() {
118            return Err(IcSnapshotUploadArtifactError::SourceMismatch);
119        }
120        Ok(())
121    }
122}
123
124fn read_chunk(
125    directory: &File,
126    metadata: &IcSnapshotMetadataReply<'_>,
127    kind: &SnapshotDataKind,
128) -> Result<Vec<u8>, IcSnapshotArtifactError> {
129    let values = metadata.metadata();
130    let (name, length, offset, size) = match member::select(kind)? {
131        member::Member::Region {
132            index,
133            offset,
134            size,
135        } => {
136            let sizes = [
137                values.wasm_module_size,
138                values.wasm_memory_size,
139                values.stable_memory_size,
140            ];
141            (
142                REGIONS[index].to_owned(),
143                Some(sizes[index]),
144                offset,
145                Some(size),
146            )
147        }
148        member::Member::Chunk { name } => (name, None, 0, None),
149    };
150    let maximum = length.unwrap_or(MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES as u64);
151    let (mut file, original) = open_regular_child(directory, &name, length, maximum)?;
152    file.seek(SeekFrom::Start(offset))?;
153    let size = size.unwrap_or(original.len());
154    read_chunk_bytes(file, size)
155}
156
157// Both individual preparation and batch binding buffer one exact admitted chunk.
158// Full-tree verification and closing custody checks remain with their callers.
159fn read_chunk_bytes(reader: impl Read, size: u64) -> Result<Vec<u8>, IcSnapshotArtifactError> {
160    #[cfg(test)]
161    let reader = super::read_count::Reader(reader);
162    let limit = usize::try_from(size)
163        .ok()
164        .filter(|size| *size <= MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES)
165        .ok_or(IcSnapshotArtifactError::FileShape)?;
166    ic_host_artifacts::artifact::read_exact_reader(reader.take(size), limit).map_err(|error| {
167        use ic_host_artifacts::artifact::ArtifactError;
168        match error {
169            ArtifactError::LimitExceeded { .. } | ArtifactError::LengthMismatch { .. } => {
170                IcSnapshotArtifactError::FileShape
171            }
172            error => IcSnapshotArtifactError::Io(error.into()),
173        }
174    })
175}
176
177/// Typed local preparation failure; all original bytes, spending and references survive.
178#[derive(Debug, Error)]
179pub enum IcSnapshotUploadArtifactError {
180    /// Original declared source checksum differs from the freshly verified retained tree.
181    #[error("snapshot upload source checksum differs")]
182    SourceMismatch,
183    /// Existing guarded IC artifact admission or descriptor IO failed.
184    #[error(transparent)]
185    Artifact(#[from] IcSnapshotArtifactError),
186    /// Canonical pure bounded upload construction failed.
187    #[error(transparent)]
188    Upload(#[from] IcSnapshotUploadError),
189}