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,
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    fn with_upload_source<T>(
56        &self,
57        plan: &OperationPlanRecord,
58        snapshot: &str,
59        metadata_upload: &IcSnapshotUploadRequest<'_>,
60        read: impl FnOnce(&File) -> Result<T, IcSnapshotUploadArtifactError>,
61    ) -> Result<T, IcSnapshotUploadArtifactError> {
62        let metadata = metadata_upload.source();
63        self.require_upload_source(plan, snapshot, metadata_upload)?;
64        let entry = self
65            .record
66            .artifact(metadata.request().target(), snapshot)
67            .map_err(super::DownloadJournalError::from)
68            .map_err(IcSnapshotArtifactError::from)?;
69        with_artifact_directory(self.layout.root(), entry.artifact_path(), read, || {
70            self.require_upload_source(plan, snapshot, metadata_upload)
71        })
72    }
73
74    fn require_upload_source(
75        &self,
76        plan: &OperationPlanRecord,
77        snapshot: &str,
78        upload: &IcSnapshotUploadRequest<'_>,
79    ) -> Result<(), IcSnapshotUploadArtifactError> {
80        if plan.digest() != upload.source_plan().digest() {
81            return Err(IcSnapshotUploadArtifactError::SourceMismatch);
82        }
83        let actual = self.verify_ic_snapshot_artifact(plan, snapshot, upload.source())?;
84        if actual != *upload.source_checksum() {
85            return Err(IcSnapshotUploadArtifactError::SourceMismatch);
86        }
87        Ok(())
88    }
89}
90
91fn read_chunk(
92    directory: &File,
93    metadata: &IcSnapshotMetadataReply<'_>,
94    kind: &SnapshotDataKind,
95) -> Result<Vec<u8>, IcSnapshotArtifactError> {
96    match member::select(kind)? {
97        member::Member::Region {
98            region,
99            offset,
100            size,
101        } => {
102            let length = metadata.region_sizes()[region.index()];
103            let (mut file, _) =
104                open_regular_child(directory, REGIONS[region.index()], Some(length), length)?;
105            file.seek(SeekFrom::Start(offset))?;
106            read_chunk_bytes(file, size)
107        }
108        member::Member::Chunk { name } => {
109            let (mut file, original) = open_regular_child(
110                directory,
111                &name,
112                None,
113                MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES as u64,
114            )?;
115            file.seek(SeekFrom::Start(0))?;
116            read_chunk_bytes(file, original.len())
117        }
118    }
119}
120
121// Both individual preparation and batch binding buffer one exact admitted chunk.
122// Full-tree verification and closing custody checks remain with their callers.
123fn read_chunk_bytes(reader: impl Read, size: u64) -> Result<Vec<u8>, IcSnapshotArtifactError> {
124    #[cfg(test)]
125    let reader = super::tests::read_count::Reader(reader);
126    let limit = usize::try_from(size)
127        .ok()
128        .filter(|size| *size <= MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES)
129        .ok_or(IcSnapshotArtifactError::FileShape)?;
130    ic_host_artifacts::artifact::read_exact_reader(reader.take(size), limit).map_err(|error| {
131        use ic_host_artifacts::artifact::ArtifactError;
132        match error {
133            ArtifactError::LimitExceeded { .. } | ArtifactError::LengthMismatch { .. } => {
134                IcSnapshotArtifactError::FileShape
135            }
136            error => IcSnapshotArtifactError::Io(error.into()),
137        }
138    })
139}
140
141/// Typed local preparation failure; all original bytes, spending and references survive.
142#[derive(Debug, Error)]
143pub enum IcSnapshotUploadArtifactError {
144    /// Original declared source checksum differs from the freshly verified retained tree.
145    #[error("snapshot upload source checksum differs")]
146    SourceMismatch,
147    /// Existing guarded IC artifact admission or descriptor IO failed.
148    #[error(transparent)]
149    Artifact(#[from] IcSnapshotArtifactError),
150    /// Canonical pure bounded upload construction failed.
151    #[error(transparent)]
152    Upload(#[from] IcSnapshotUploadError),
153}
154
155#[cfg(test)]
156mod tests;