1use crate::{
13 descriptor::{hex, valid_hash},
14 filesystem as io, ObjectDescriptor, TransferControl, TransferError as E, TransferLimits,
15 TransferResult,
16};
17use fs2::FileExt;
18use serde::{Deserialize, Serialize};
19use sha2::{Digest, Sha256};
20use std::{
21 fs::{self, File},
22 io::{Read, Write},
23 path::{Path, PathBuf},
24 time::{Duration, SystemTime, UNIX_EPOCH},
25};
26#[derive(Debug)]
30pub struct TransferStore {
31 root: PathBuf,
32 limits: TransferLimits,
33 _lock: File,
34 used: u64,
35 files: usize,
36}
37#[derive(Serialize)]
38struct Metadata {
39 format_version: u16,
40 created_ms: u64,
41 descriptor: ObjectDescriptor,
42}
43#[derive(Deserialize)]
44#[serde(deny_unknown_fields)]
45struct MetadataWire {
46 format_version: u16,
47 created_ms: u64,
48 descriptor: crate::descriptor::DescriptorWire,
49}
50#[derive(Debug, Clone)]
52pub struct VerifiedObject {
53 path: PathBuf,
54 descriptor: ObjectDescriptor,
55}
56impl VerifiedObject {
57 pub fn path(&self) -> &Path {
59 &self.path
60 }
61 pub fn descriptor(&self) -> &ObjectDescriptor {
63 &self.descriptor
64 }
65 pub fn open(&self) -> TransferResult<File> {
67 verify_complete(&self.path, &self.descriptor)?;
68 io::open(&self.path)
69 }
70}
71pub struct TransferSession<'a> {
73 store: &'a mut TransferStore,
74 dir: PathBuf,
75 descriptor: ObjectDescriptor,
76 complete: Vec<bool>,
77}
78impl TransferStore {
79 pub fn open(root: impl Into<PathBuf>, limits: TransferLimits) -> TransferResult<Self> {
82 limits.validate()?;
83 let root = root.into();
84 io::validate_root(&root)?;
85 let path = root.join(".lock");
86 let lock = if path.try_exists().map_err(|_| E::Storage)? {
87 io::open(&path)?
88 } else {
89 io::exclusive(&path)?
90 };
91 lock.try_lock_exclusive().map_err(|_| E::Busy)?;
92 let mut value = Self {
93 root,
94 limits,
95 _lock: lock,
96 used: 0,
97 files: 0,
98 };
99 value.recount()?;
100 Ok(value)
101 }
102 pub fn begin(&mut self, descriptor: ObjectDescriptor) -> TransferResult<TransferSession<'_>> {
106 descriptor.validate(self.limits)?;
107 let dir = self.root.join(descriptor.object_hash());
108 if !dir.try_exists().map_err(|_| E::Storage)? {
109 self.reserve(0, 1)?;
110 io::make_directory(&dir)?;
111 self.recount()?;
112 }
113 io::private_directory(&dir)?;
114 let metadata_path = dir.join("descriptor.json");
115 if metadata_path.try_exists().map_err(|_| E::Storage)? {
116 let metadata = read_metadata(&metadata_path, self.limits)?;
117 if metadata.descriptor != descriptor {
118 return Err(E::Integrity);
119 }
120 } else {
121 for entry in fs::read_dir(&dir).map_err(|_| E::Storage)? {
122 let entry = entry.map_err(|_| E::Storage)?;
123 io::regular(&entry.path())?;
124 if entry.file_name() != "descriptor.tmp" {
125 return Err(E::Integrity);
126 }
127 fs::remove_file(entry.path()).map_err(|_| E::Storage)?;
128 }
129 self.recount()?;
130 let metadata = Metadata {
131 format_version: 1,
132 created_ms: now_ms()?,
133 descriptor: descriptor.clone(),
134 };
135 let bytes = serde_json::to_vec(&metadata).map_err(|_| E::Storage)?;
136 self.reserve(bytes.len() as u64, 2)?;
137 io::write(&metadata_path, &bytes)?;
138 self.recount()?;
139 }
140 cleanup_temporaries(&dir, &descriptor)?;
141 self.recount()?;
142 let mut complete = vec![false; descriptor.block_count()];
143 let final_path = dir.join("complete.bin");
144 if final_path.try_exists().map_err(|_| E::Storage)? {
145 verify_complete(&final_path, &descriptor)?;
146 complete.fill(true);
147 } else {
148 for (index, done) in complete.iter_mut().enumerate() {
149 let path = block_path(&dir, index);
150 if path.try_exists().map_err(|_| E::Storage)? {
151 let bytes = io::read(&path, descriptor.block_len(index)? as u64)?;
152 descriptor.verify_block(index, &bytes)?;
153 *done = true;
154 }
155 }
156 }
157 Ok(TransferSession {
158 store: self,
159 dir,
160 descriptor,
161 complete,
162 })
163 }
164 pub fn prune_expired(&mut self, ttl: Duration) -> TransferResult<usize> {
167 if ttl.is_zero() || ttl > Duration::from_secs(365 * 86400) {
168 return Err(E::InvalidContract);
169 }
170 let now = now_ms()?;
171 let mut removed = 0;
172 for entry in fs::read_dir(&self.root).map_err(|_| E::Storage)? {
173 let entry = entry.map_err(|_| E::Storage)?;
174 if entry.file_name() == ".lock" {
175 continue;
176 }
177 let dir = entry.path();
178 io::private_directory(&dir)?;
179 let m = read_metadata(&dir.join("descriptor.json"), self.limits)?;
180 if dir.file_name().and_then(|name| name.to_str()) != Some(m.descriptor.object_hash()) {
181 return Err(E::Integrity);
182 }
183 validate_inventory(&dir, &m.descriptor)?;
184 if !dir
185 .join("complete.bin")
186 .try_exists()
187 .map_err(|_| E::Storage)?
188 && now.saturating_sub(m.created_ms) >= ttl.as_millis() as u64
189 {
190 fs::remove_dir_all(&dir).map_err(|_| E::Storage)?;
191 io::sync_parent(&dir)?;
192 removed += 1;
193 }
194 }
195 self.recount()?;
196 Ok(removed)
197 }
198 pub fn purge_partial(&mut self, descriptor: &ObjectDescriptor) -> TransferResult<()> {
201 descriptor.validate(self.limits)?;
202 let dir = self.root.join(descriptor.object_hash());
203 io::private_directory(&dir)?;
204 if read_metadata(&dir.join("descriptor.json"), self.limits)?.descriptor != *descriptor {
205 return Err(E::Integrity);
206 }
207 if dir
208 .join("complete.bin")
209 .try_exists()
210 .map_err(|_| E::Storage)?
211 {
212 return Err(E::InvalidContract);
213 }
214 validate_inventory(&dir, descriptor)?;
215 fs::remove_dir_all(&dir).map_err(|_| E::Storage)?;
216 io::sync_parent(&dir)?;
217 self.recount()
218 }
219 fn reserve(&self, bytes: u64, files: usize) -> TransferResult<()> {
220 if self
221 .used
222 .checked_add(bytes)
223 .is_none_or(|n| n > self.limits.max_store_bytes)
224 || self
225 .files
226 .checked_add(files)
227 .is_none_or(|n| n > self.limits.max_files)
228 {
229 return Err(E::Limit);
230 }
231 Ok(())
232 }
233 fn recount(&mut self) -> TransferResult<()> {
234 let mut bytes = 0u64;
235 let mut count = 0usize;
236 for entry in fs::read_dir(&self.root).map_err(|_| E::Storage)? {
237 let entry = entry.map_err(|_| E::Storage)?;
238 count += 1;
239 if count > self.limits.max_files {
240 return Err(E::Limit);
241 }
242 if entry.file_name() == ".lock" {
243 bytes = bytes
244 .checked_add(io::regular(&entry.path())?.len())
245 .ok_or(E::Limit)?;
246 continue;
247 }
248 if !entry.file_name().to_str().is_some_and(valid_hash) {
249 return Err(E::UnsafePath);
250 }
251 io::private_directory(&entry.path())?;
252 for file in fs::read_dir(entry.path()).map_err(|_| E::Storage)? {
253 let file = file.map_err(|_| E::Storage)?;
254 count += 1;
255 if count > self.limits.max_files {
256 return Err(E::Limit);
257 }
258 bytes = bytes
259 .checked_add(io::regular(&file.path())?.len())
260 .ok_or(E::Limit)?;
261 if bytes > self.limits.max_store_bytes {
262 return Err(E::Limit);
263 }
264 }
265 }
266 if bytes > self.limits.max_store_bytes {
267 return Err(E::Limit);
268 }
269 self.used = bytes;
270 self.files = count;
271 Ok(())
272 }
273}
274impl TransferSession<'_> {
275 pub fn descriptor(&self) -> &ObjectDescriptor {
277 &self.descriptor
278 }
279 pub fn missing(&self) -> Vec<usize> {
281 self.complete
282 .iter()
283 .enumerate()
284 .filter_map(|(i, done)| (!done).then_some(i))
285 .collect()
286 }
287 pub fn accept(&mut self, index: usize, bytes: &[u8]) -> TransferResult<()> {
290 self.descriptor.verify_block(index, bytes)?;
291 if self.complete[index] {
292 return Ok(());
293 }
294 self.store.reserve(bytes.len() as u64, 2)?;
295 let result = io::write(&block_path(&self.dir, index), bytes);
296 self.store.recount()?;
297 result?;
298 self.complete[index] = true;
299 Ok(())
300 }
301 pub fn finish(self) -> TransferResult<VerifiedObject> {
305 self.finish_inner(None)
306 }
307 pub fn finish_with_control(self, control: &TransferControl) -> TransferResult<VerifiedObject> {
310 self.finish_inner(Some(control))
311 }
312 fn finish_inner(self, control: Option<&TransferControl>) -> TransferResult<VerifiedObject> {
313 checkpoint(control)?;
314 if self.complete.iter().any(|done| !*done) {
315 return Err(E::Integrity);
316 }
317 let path = self.dir.join("complete.bin");
318 if !path.try_exists().map_err(|_| E::Storage)? {
319 self.store.reserve(self.descriptor.total_bytes(), 1)?;
320 let result = io::atomic(&path, |file| {
321 assemble(file, &self.dir, &self.descriptor, control)
322 });
323 self.store.recount()?;
324 result?;
325 }
326 verify_complete_controlled(&path, &self.descriptor, control)?;
327 for index in 0..self.descriptor.block_count() {
328 let block = block_path(&self.dir, index);
329 if block.try_exists().map_err(|_| E::Storage)? {
330 io::regular(&block)?;
331 fs::remove_file(block).map_err(|_| E::Storage)?;
332 }
333 }
334 io::sync_parent(&path)?;
335 self.store.recount()?;
336 Ok(VerifiedObject {
337 path,
338 descriptor: self.descriptor,
339 })
340 }
341}
342fn assemble(
343 file: &mut File,
344 dir: &Path,
345 descriptor: &ObjectDescriptor,
346 control: Option<&TransferControl>,
347) -> TransferResult<()> {
348 let mut full = Sha256::new();
349 for index in 0..descriptor.block_count() {
350 checkpoint(control)?;
351 let bytes = io::read(&block_path(dir, index), descriptor.block_len(index)? as u64)?;
352 descriptor.verify_block(index, &bytes)?;
353 file.write_all(&bytes).map_err(|_| E::Storage)?;
354 full.update(&bytes);
355 }
356 checkpoint(control)?;
357 if hex(&full.finalize()) != descriptor.object_hash() {
358 return Err(E::Integrity);
359 }
360 Ok(())
361}
362fn checkpoint(control: Option<&TransferControl>) -> TransferResult<()> {
363 control.map_or(Ok(()), TransferControl::checkpoint)
364}
365fn verify_complete(path: &Path, descriptor: &ObjectDescriptor) -> TransferResult<()> {
366 verify_complete_controlled(path, descriptor, None)
367}
368fn verify_complete_controlled(
369 path: &Path,
370 descriptor: &ObjectDescriptor,
371 control: Option<&TransferControl>,
372) -> TransferResult<()> {
373 let mut file = io::open(path)?;
374 if file.metadata().map_err(|_| E::Storage)?.len() != descriptor.total_bytes() {
375 return Err(E::Integrity);
376 }
377 let mut hasher = Sha256::new();
378 let mut buffer = [0; 64 * 1024];
379 loop {
380 checkpoint(control)?;
381 let n = file.read(&mut buffer).map_err(|_| E::Storage)?;
382 if n == 0 {
383 break;
384 }
385 hasher.update(&buffer[..n]);
386 }
387 if hex(&hasher.finalize()) != descriptor.object_hash() {
388 return Err(E::Integrity);
389 }
390 Ok(())
391}
392fn block_path(dir: &Path, index: usize) -> PathBuf {
393 dir.join(format!("{index:04}.block"))
394}
395fn read_metadata(path: &Path, limits: TransferLimits) -> TransferResult<Metadata> {
396 let bytes = io::read(path, 1024 * 1024)?;
397 let wire: MetadataWire = serde_json::from_slice(&bytes).map_err(|_| E::Integrity)?;
398 if wire.format_version != 1 {
399 return Err(E::UnsupportedVersion);
400 }
401 let m = Metadata {
402 format_version: wire.format_version,
403 created_ms: wire.created_ms,
404 descriptor: ObjectDescriptor::try_from(wire.descriptor)?,
405 };
406 if m.format_version != 1 {
407 return Err(E::UnsupportedVersion);
408 }
409 m.descriptor.validate(limits)?;
410 Ok(m)
411}
412fn validate_inventory(dir: &Path, descriptor: &ObjectDescriptor) -> TransferResult<()> {
413 for entry in fs::read_dir(dir).map_err(|_| E::Storage)? {
414 let entry = entry.map_err(|_| E::Storage)?;
415 io::regular(&entry.path())?;
416 let name = entry.file_name();
417 let name = name.to_str().ok_or(E::UnsafePath)?;
418 let valid = name == "descriptor.json"
419 || name == "descriptor.tmp"
420 || name == "complete.bin"
421 || name == "complete.tmp"
422 || (0..descriptor.block_count())
423 .any(|i| name == format!("{i:04}.block") || name == format!("{i:04}.tmp"));
424 if !valid {
425 return Err(E::UnsafePath);
426 }
427 }
428 Ok(())
429}
430fn cleanup_temporaries(dir: &Path, descriptor: &ObjectDescriptor) -> TransferResult<()> {
431 validate_inventory(dir, descriptor)?;
432 for entry in fs::read_dir(dir).map_err(|_| E::Storage)? {
433 let entry = entry.map_err(|_| E::Storage)?;
434 if entry.path().extension().is_some_and(|e| e == "tmp") {
435 fs::remove_file(entry.path()).map_err(|_| E::Storage)?;
436 }
437 }
438 io::sync_parent(&dir.join("descriptor.json"))
439}
440fn now_ms() -> TransferResult<u64> {
441 u64::try_from(
442 SystemTime::now()
443 .duration_since(UNIX_EPOCH)
444 .map_err(|_| E::Storage)?
445 .as_millis(),
446 )
447 .map_err(|_| E::Storage)
448}