Skip to main content

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}