leodos_protocols/transport/cfdp/filestore.rs
1//! The FileStore trait for abstracting file system operations.
2//!
3//! This trait allows the CFDP implementation to be generic over any storage
4//! backend, from a standard OS file system to an in-memory buffer or a
5//! block device driver in a `#![no_std]` environment.
6
7use core::fmt::Debug;
8use core::future::Future;
9
10use crate::transport::cfdp::CfdpError;
11use crate::transport::cfdp::checksum::CfdpChecksum;
12
13/// A unique identifier for an interned file path.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct FileId(u8);
16
17/// A trait defining the required asynchronous file operations for CFDP.
18///
19/// The methods return `impl Future` to support backends where I/O is non-blocking,
20/// making the trait compatible with any async runtime.
21pub trait FileStore: Debug + Send + Sync {
22 /// Interns a file path and returns a unique `FileId`.
23 fn intern(&self, path: &[u8]) -> Result<FileId, CfdpError>;
24
25 /// Reads a chunk of data from the source file at a given offset.
26 ///
27 /// # Arguments
28 /// * `path`: The path to the source file, as a string slice.
29 /// * `offset`: The byte offset from the beginning of the file to start reading.
30 /// * `length`: The maximum number of bytes to read.
31 /// * `buffer`: The buffer to write the read data into.
32 ///
33 /// # Returns
34 /// A `Future` that resolves to a `Result` containing the number of bytes actually
35 /// read. This may be less than `length` if the end of the file is reached.
36 fn read_chunk(
37 &self,
38 path: FileId,
39 offset: u64,
40 length: u64,
41 buffer: &mut [u8],
42 ) -> impl Future<Output = Result<usize, CfdpError>>;
43
44 /// Writes a chunk of data to the destination file at a given offset.
45 ///
46 /// The `FileStore` implementation is responsible for creating the file if it
47 /// does not exist.
48 ///
49 /// # Arguments
50 /// * `path`: The path to the destination file, as a string slice.
51 /// * `offset`: The byte offset from the beginning of the file to start writing.
52 /// * `data`: The slice of data to write.
53 ///
54 /// # Returns
55 /// A `Future` that resolves to a `Result` indicating whether the write was successful.
56 fn write_chunk(
57 &mut self,
58 path: FileId,
59 offset: u64,
60 data: &[u8],
61 ) -> impl Future<Output = Result<(), CfdpError>>;
62
63 /// Retrieves the total size of a file in bytes.
64 ///
65 /// # Arguments
66 /// * `path`: The path to the file, as a string slice.
67 ///
68 /// # Returns
69 /// A `Future` that resolves to a `Result` containing the file size in bytes.
70 fn file_size(&self, path: FileId) -> impl Future<Output = Result<u64, CfdpError>>;
71
72 /// Calculates the full-file checksum using the given hasher.
73 fn calculate_checksum<H>(
74 &self,
75 file_id: FileId,
76 mut hasher: H,
77 ) -> impl Future<Output = Result<u32, CfdpError>>
78 where
79 H: CfdpChecksum,
80 {
81 async move {
82 let file_size = self.file_size(file_id).await?;
83 let mut offset: u64 = 0;
84 let mut buffer = [0u8; 4096];
85
86 while offset < file_size {
87 let remaining = file_size - offset;
88 let to_read = core::cmp::min(buffer.len() as u64, remaining) as usize;
89
90 let bytes_read = self
91 .read_chunk(file_id, offset, to_read as u64, &mut buffer)
92 .await?;
93
94 if bytes_read == 0 {
95 break;
96 }
97
98 // Update the generic hasher
99 hasher.update(&buffer[..bytes_read]);
100
101 offset += bytes_read as u64;
102 }
103
104 Ok(hasher.finalize())
105 }
106 }
107}