ic_backup/ops/persistence/download_journal/ic_snapshot_artifact/upload/
mod.rs1mod plan;
4pub use plan::IcSnapshotDataUploadPreparationError;
5
6use super::{
7 DownloadJournalGuard, File, IcSnapshotArtifactError, REGIONS, member,
8 verification::open_regular_child, with_artifact_directory,
9};
10use crate::model::{
11 ic_snapshot_data::{MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES, validate_kind},
12 ic_snapshot_metadata::IcSnapshotMetadataReply,
13 ic_snapshot_upload::{IcSnapshotUploadError, IcSnapshotUploadRequest},
14 operation_plan::OperationPlanRecord,
15};
16use ic_platform::management_canister_types::SnapshotDataKind;
17use std::io::{Read, Seek, SeekFrom};
18use thiserror::Error;
19
20impl DownloadJournalGuard<'_> {
21 pub fn prepare_ic_snapshot_upload_metadata<'source>(
31 &self,
32 plan: &'source OperationPlanRecord,
33 snapshot: &str,
34 metadata: &'source IcSnapshotMetadataReply<'source>,
35 ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
36 let checksum = self.verify_ic_snapshot_artifact(plan, snapshot, metadata)?;
37 Ok(IcSnapshotUploadRequest::metadata(
38 plan, metadata, &checksum,
39 )?)
40 }
41
42 pub fn prepare_ic_snapshot_upload_data<'source>(
54 &self,
55 plan: &OperationPlanRecord,
56 snapshot: &str,
57 metadata_upload: &IcSnapshotUploadRequest<'source>,
58 snapshot_id: &[u8],
59 source_kind: SnapshotDataKind,
60 ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
61 let metadata = metadata_upload.source();
62 metadata_upload.validate_data_destination(snapshot_id)?;
63 validate_kind(metadata, &source_kind).map_err(IcSnapshotUploadError::from)?;
64 self.with_upload_source(plan, snapshot, metadata_upload, |directory| {
65 let chunk = read_chunk(directory, metadata, &source_kind)?;
66 Ok(IcSnapshotUploadRequest::data(
67 metadata_upload,
68 snapshot_id,
69 source_kind,
70 &chunk,
71 )?)
72 })
73 }
74
75 fn with_upload_source<T>(
76 &self,
77 plan: &OperationPlanRecord,
78 snapshot: &str,
79 metadata_upload: &IcSnapshotUploadRequest<'_>,
80 read: impl FnOnce(&File) -> Result<T, IcSnapshotUploadArtifactError>,
81 ) -> Result<T, IcSnapshotUploadArtifactError> {
82 let metadata = metadata_upload.source();
83 self.require_upload_source(plan, snapshot, metadata_upload)?;
84 let entry = self
85 .record
86 .artifact(metadata.request().target(), snapshot)
87 .map_err(super::DownloadJournalError::from)
88 .map_err(IcSnapshotArtifactError::from)?;
89 with_artifact_directory(self.layout.root(), entry.artifact_path(), read, || {
90 self.require_upload_source(plan, snapshot, metadata_upload)
91 })
92 }
93
94 fn require_upload_source(
95 &self,
96 plan: &OperationPlanRecord,
97 snapshot: &str,
98 upload: &IcSnapshotUploadRequest<'_>,
99 ) -> Result<(), IcSnapshotUploadArtifactError> {
100 if plan.digest() != upload.source_plan().digest() {
101 return Err(IcSnapshotUploadArtifactError::SourceMismatch);
102 }
103 let actual = self.verify_ic_snapshot_artifact(plan, snapshot, upload.source())?;
104 if actual != *upload.source_checksum() {
105 return Err(IcSnapshotUploadArtifactError::SourceMismatch);
106 }
107 Ok(())
108 }
109}
110
111fn read_chunk(
112 directory: &File,
113 metadata: &IcSnapshotMetadataReply<'_>,
114 kind: &SnapshotDataKind,
115) -> Result<Vec<u8>, IcSnapshotArtifactError> {
116 let values = metadata.metadata();
117 let (name, length, offset, size) = match member::select(kind)? {
118 member::Member::Region {
119 index,
120 offset,
121 size,
122 } => {
123 let sizes = [
124 values.wasm_module_size,
125 values.wasm_memory_size,
126 values.stable_memory_size,
127 ];
128 (
129 REGIONS[index].to_owned(),
130 Some(sizes[index]),
131 offset,
132 Some(size),
133 )
134 }
135 member::Member::Chunk { name } => (name, None, 0, None),
136 };
137 let maximum = length.unwrap_or(MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES as u64);
138 let (mut file, original) = open_regular_child(directory, &name, length, maximum)?;
139 file.seek(SeekFrom::Start(offset))?;
140 let size = size.unwrap_or(original.len());
141 read_chunk_bytes(file, size)
142}
143
144fn read_chunk_bytes(reader: impl Read, size: u64) -> Result<Vec<u8>, IcSnapshotArtifactError> {
147 let limit = usize::try_from(size)
148 .ok()
149 .filter(|size| *size <= MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES)
150 .ok_or(IcSnapshotArtifactError::FileShape)?;
151 ic_host_artifacts::artifact::read_exact_reader(reader.take(size), limit).map_err(|error| {
152 use ic_host_artifacts::artifact::ArtifactError;
153 match error {
154 ArtifactError::LimitExceeded { .. } | ArtifactError::LengthMismatch { .. } => {
155 IcSnapshotArtifactError::FileShape
156 }
157 error => IcSnapshotArtifactError::Io(error.into()),
158 }
159 })
160}
161
162#[derive(Debug, Error)]
164pub enum IcSnapshotUploadArtifactError {
165 #[error("snapshot upload source checksum differs")]
167 SourceMismatch,
168 #[error(transparent)]
170 Artifact(#[from] IcSnapshotArtifactError),
171 #[error(transparent)]
173 Upload(#[from] IcSnapshotUploadError),
174}