cfdp/
filestore.rs

1use spacepackets::ByteConversionError;
2use spacepackets::cfdp::ChecksumType;
3#[cfg(feature = "std")]
4pub use std_mod::*;
5
6#[derive(Debug, thiserror::Error)]
7#[cfg_attr(all(feature = "defmt", not(feature = "std")), derive(defmt::Format))]
8#[non_exhaustive]
9pub enum FilestoreError {
10    #[error("file does not exist")]
11    FileDoesNotExist,
12    #[error("file already exists")]
13    FileAlreadyExists,
14    #[error("directory does not exist")]
15    DirDoesNotExist,
16    #[error("permission error")]
17    Permission,
18    #[error("is not a file")]
19    IsNotFile,
20    #[error("is not a directory")]
21    IsNotDirectory,
22    #[error("byte conversion: {0}")]
23    ByteConversion(#[from] ByteConversionError),
24    #[error("IO error: {0})")]
25    #[cfg(feature = "std")]
26    Io(#[from] std::io::Error),
27    #[error("checksum type not implemented: {0:?}")]
28    ChecksumTypeNotImplemented(ChecksumType),
29    #[error("utf8 error")]
30    Utf8Error,
31    #[error("other error")]
32    Other,
33}
34
35pub trait VirtualFilestore {
36    fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>;
37
38    fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError>;
39
40    /// Truncating a file means deleting all its data so the resulting file is empty.
41    /// This can be more efficient than removing and re-creating a file.
42    fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError>;
43
44    fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError>;
45    fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError>;
46
47    fn read_data(
48        &self,
49        file_path: &str,
50        offset: u64,
51        read_len: u64,
52        buf: &mut [u8],
53    ) -> Result<(), FilestoreError>;
54
55    fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError>;
56
57    fn filename_from_full_path(path: &str) -> Option<&str>
58    where
59        Self: Sized;
60
61    fn is_file(&self, path: &str) -> Result<bool, FilestoreError>;
62
63    fn is_dir(&self, path: &str) -> Result<bool, FilestoreError> {
64        Ok(!self.is_file(path)?)
65    }
66
67    fn exists(&self, path: &str) -> Result<bool, FilestoreError>;
68
69    /// Extract the file name part of a full path.
70    ///
71    /// This method should behave similarly to the [std::path::Path::file_name] method.
72    fn file_name<'a>(&self, full_path: &'a str) -> Result<Option<&'a str>, FilestoreError>;
73
74    fn file_size(&self, path: &str) -> Result<u64, FilestoreError>;
75
76    /// This special function is the CFDP specific abstraction to calculate the checksum of a file.
77    /// This allows to keep OS specific details like reading the whole file in the most efficient
78    /// manner inside the file system abstraction.
79    ///
80    /// The passed verification buffer argument will be used by the specific implementation as
81    /// a buffer to read the file into. It is recommended to use common buffer sizes like
82    /// 4096 or 8192 bytes.
83    fn calculate_checksum(
84        &self,
85        file_path: &str,
86        checksum_type: ChecksumType,
87        size_to_verify: u64,
88        verification_buf: &mut [u8],
89    ) -> Result<u32, FilestoreError>;
90
91    /// This special function is the CFDP specific abstraction to verify the checksum of a file.
92    /// This allows to keep OS specific details like reading the whole file in the most efficient
93    /// manner inside the file system abstraction.
94    ///
95    /// The passed verification buffer argument will be used by the specific implementation as
96    /// a buffer to read the file into. It is recommended to use common buffer sizes like
97    /// 4096 or 8192 bytes.
98    fn checksum_verify(
99        &self,
100        expected_checksum: u32,
101        file_path: &str,
102        checksum_type: ChecksumType,
103        size_to_verify: u64,
104        verification_buf: &mut [u8],
105    ) -> Result<bool, FilestoreError> {
106        Ok(
107            self.calculate_checksum(file_path, checksum_type, size_to_verify, verification_buf)?
108                == expected_checksum,
109        )
110    }
111}
112
113#[cfg(feature = "std")]
114pub mod std_mod {
115
116    use crc::Crc;
117
118    use crate::{CRC_32, CRC_32C};
119
120    use super::*;
121    use std::{
122        fs::{self, File, OpenOptions},
123        io::{BufReader, Read, Seek, SeekFrom, Write},
124        path::Path,
125    };
126
127    #[derive(Default)]
128    pub struct NativeFilestore {}
129
130    impl VirtualFilestore for NativeFilestore {
131        fn create_file(&self, file_path: &str) -> Result<(), FilestoreError> {
132            if self.exists(file_path)? {
133                return Err(FilestoreError::FileAlreadyExists);
134            }
135            File::create(file_path)?;
136            Ok(())
137        }
138
139        fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError> {
140            if !self.exists(file_path)? {
141                return Err(FilestoreError::FileDoesNotExist);
142            }
143            if !self.is_file(file_path)? {
144                return Err(FilestoreError::IsNotFile);
145            }
146            fs::remove_file(file_path)?;
147            Ok(())
148        }
149
150        fn file_name<'a>(&self, full_path: &'a str) -> Result<Option<&'a str>, FilestoreError> {
151            let path = Path::new(full_path);
152            path.file_name()
153                .map(|s| s.to_str())
154                .ok_or(FilestoreError::Utf8Error)
155        }
156
157        fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> {
158            if !self.exists(file_path)? {
159                return Err(FilestoreError::FileDoesNotExist);
160            }
161            if !self.is_file(file_path)? {
162                return Err(FilestoreError::IsNotFile);
163            }
164            OpenOptions::new()
165                .write(true)
166                .truncate(true)
167                .open(file_path)?;
168            Ok(())
169        }
170
171        fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> {
172            fs::create_dir(dir_path)?;
173            Ok(())
174        }
175
176        fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError> {
177            if !self.exists(dir_path)? {
178                return Err(FilestoreError::DirDoesNotExist);
179            }
180            if !self.is_dir(dir_path)? {
181                return Err(FilestoreError::IsNotDirectory);
182            }
183            if !all {
184                fs::remove_dir(dir_path)?;
185                return Ok(());
186            }
187            fs::remove_dir_all(dir_path)?;
188            Ok(())
189        }
190
191        fn read_data(
192            &self,
193            file_name: &str,
194            offset: u64,
195            read_len: u64,
196            buf: &mut [u8],
197        ) -> Result<(), FilestoreError> {
198            if buf.len() < read_len as usize {
199                return Err(ByteConversionError::ToSliceTooSmall {
200                    found: buf.len(),
201                    expected: read_len as usize,
202                }
203                .into());
204            }
205            if !self.exists(file_name)? {
206                return Err(FilestoreError::FileDoesNotExist);
207            }
208            if !self.is_file(file_name)? {
209                return Err(FilestoreError::IsNotFile);
210            }
211            let mut file = File::open(file_name)?;
212            file.seek(SeekFrom::Start(offset))?;
213            file.read_exact(&mut buf[0..read_len as usize])?;
214            Ok(())
215        }
216
217        fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError> {
218            if !self.exists(file)? {
219                return Err(FilestoreError::FileDoesNotExist);
220            }
221            if !self.is_file(file)? {
222                return Err(FilestoreError::IsNotFile);
223            }
224            let mut file = OpenOptions::new().write(true).open(file)?;
225            file.seek(SeekFrom::Start(offset))?;
226            file.write_all(buf)?;
227            Ok(())
228        }
229
230        fn is_file(&self, str_path: &str) -> Result<bool, FilestoreError> {
231            let path = Path::new(str_path);
232            if !self.exists(str_path)? {
233                return Err(FilestoreError::FileDoesNotExist);
234            }
235            Ok(path.is_file())
236        }
237
238        fn exists(&self, path: &str) -> Result<bool, FilestoreError> {
239            let path = Path::new(path);
240            Ok(self.exists_internal(path))
241        }
242
243        fn file_size(&self, str_path: &str) -> Result<u64, FilestoreError> {
244            let path = Path::new(str_path);
245            if !self.exists_internal(path) {
246                return Err(FilestoreError::FileDoesNotExist);
247            }
248            if !path.is_file() {
249                return Err(FilestoreError::IsNotFile);
250            }
251            Ok(path.metadata()?.len())
252        }
253
254        fn calculate_checksum(
255            &self,
256            file_path: &str,
257            checksum_type: ChecksumType,
258            size_to_verify: u64,
259            verification_buf: &mut [u8],
260        ) -> Result<u32, FilestoreError> {
261            let mut calc_with_crc_lib = |crc: Crc<u32>| -> Result<u32, FilestoreError> {
262                let mut digest = crc.digest();
263                let mut buf_reader = BufReader::new(File::open(file_path)?);
264                let mut remaining_bytes = size_to_verify;
265                while remaining_bytes > 0 {
266                    // Read the smaller of the remaining bytes or the buffer size
267                    let bytes_to_read = remaining_bytes.min(verification_buf.len() as u64) as usize;
268                    let bytes_read = buf_reader.read(&mut verification_buf[0..bytes_to_read])?;
269
270                    if bytes_read == 0 {
271                        break; // Reached end of file
272                    }
273                    digest.update(&verification_buf[0..bytes_read]);
274                    remaining_bytes -= bytes_read as u64;
275                }
276                Ok(digest.finalize())
277            };
278            match checksum_type {
279                ChecksumType::Modular => self.calc_modular_checksum(file_path),
280                ChecksumType::Crc32 => calc_with_crc_lib(CRC_32),
281                ChecksumType::Crc32C => calc_with_crc_lib(CRC_32C),
282                ChecksumType::NullChecksum => Ok(0),
283                _ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)),
284            }
285        }
286
287        fn filename_from_full_path(path: &str) -> Option<&str>
288        where
289            Self: Sized,
290        {
291            // Convert the path string to a Path
292            let path = Path::new(path);
293
294            // Extract the file name using the file_name() method
295            path.file_name().and_then(|name| name.to_str())
296        }
297    }
298
299    impl NativeFilestore {
300        pub fn calc_modular_checksum(&self, file_path: &str) -> Result<u32, FilestoreError> {
301            let mut checksum: u32 = 0;
302            let file = File::open(file_path)?;
303            let mut buf_reader = BufReader::new(file);
304            let mut buffer = [0; 4];
305
306            loop {
307                let bytes_read = buf_reader.read(&mut buffer)?;
308                if bytes_read == 0 {
309                    break;
310                }
311                // Perform padding directly in the buffer
312                (bytes_read..4).for_each(|i| {
313                    buffer[i] = 0;
314                });
315
316                checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
317            }
318            Ok(checksum)
319        }
320
321        fn exists_internal(&self, path: &Path) -> bool {
322            if !path.exists() {
323                return false;
324            }
325            true
326        }
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use std::{fs, path::Path, println, string::ToString};
333
334    use super::*;
335    use alloc::format;
336    use tempfile::tempdir;
337
338    const EXAMPLE_DATA_CFDP: [u8; 15] = [
339        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
340    ];
341
342    const NATIVE_FS: NativeFilestore = NativeFilestore {};
343
344    #[test]
345    fn test_basic_native_filestore_create() {
346        let tmpdir = tempdir().expect("creating tmpdir failed");
347        let file_path = tmpdir.path().join("test.txt");
348        let result =
349            NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
350        assert!(result.is_ok());
351        let path = Path::new(&file_path);
352        assert!(path.exists());
353        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
354        assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
355    }
356
357    #[test]
358    fn test_basic_native_fs_file_exists() {
359        let tmpdir = tempdir().expect("creating tmpdir failed");
360        let file_path = tmpdir.path().join("test.txt");
361        assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
362        NATIVE_FS
363            .create_file(file_path.to_str().expect("getting str for file failed"))
364            .unwrap();
365        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
366        assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
367    }
368
369    #[test]
370    fn test_basic_native_fs_dir_exists() {
371        let tmpdir = tempdir().expect("creating tmpdir failed");
372        let dir_path = tmpdir.path().join("testdir");
373        assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
374        NATIVE_FS
375            .create_dir(dir_path.to_str().expect("getting str for file failed"))
376            .unwrap();
377        assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
378        assert!(
379            NATIVE_FS
380                .is_dir(dir_path.as_path().to_str().unwrap())
381                .unwrap()
382        );
383    }
384
385    #[test]
386    fn test_basic_native_fs_remove_file() {
387        let tmpdir = tempdir().expect("creating tmpdir failed");
388        let file_path = tmpdir.path().join("test.txt");
389        NATIVE_FS
390            .create_file(file_path.to_str().expect("getting str for file failed"))
391            .expect("creating file failed");
392        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
393        NATIVE_FS
394            .remove_file(file_path.to_str().unwrap())
395            .expect("removing file failed");
396        assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
397    }
398
399    #[test]
400    fn test_basic_native_fs_write() {
401        let tmpdir = tempdir().expect("creating tmpdir failed");
402        let file_path = tmpdir.path().join("test.txt");
403        assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
404        NATIVE_FS
405            .create_file(file_path.to_str().expect("getting str for file failed"))
406            .unwrap();
407        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
408        assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
409        println!("{}", file_path.to_str().unwrap());
410        let write_data = "hello world\n";
411        NATIVE_FS
412            .write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
413            .expect("writing to file failed");
414        let read_back = fs::read_to_string(file_path).expect("reading back data failed");
415        assert_eq!(read_back, write_data);
416    }
417
418    #[test]
419    fn test_basic_native_fs_read() {
420        let tmpdir = tempdir().expect("creating tmpdir failed");
421        let file_path = tmpdir.path().join("test.txt");
422        assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
423        NATIVE_FS
424            .create_file(file_path.to_str().expect("getting str for file failed"))
425            .unwrap();
426        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
427        assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
428        println!("{}", file_path.to_str().unwrap());
429        let write_data = "hello world\n";
430        NATIVE_FS
431            .write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
432            .expect("writing to file failed");
433        let read_back = fs::read_to_string(file_path).expect("reading back data failed");
434        assert_eq!(read_back, write_data);
435    }
436
437    #[test]
438    fn test_truncate_file() {
439        let tmpdir = tempdir().expect("creating tmpdir failed");
440        let file_path = tmpdir.path().join("test.txt");
441        NATIVE_FS
442            .create_file(file_path.to_str().expect("getting str for file failed"))
443            .expect("creating file failed");
444        fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
445        assert_eq!(fs::read(file_path.clone()).unwrap(), [1, 2, 3, 4]);
446        NATIVE_FS
447            .truncate_file(file_path.to_str().unwrap())
448            .unwrap();
449        assert_eq!(fs::read(file_path.clone()).unwrap(), []);
450    }
451
452    #[test]
453    fn test_remove_dir() {
454        let tmpdir = tempdir().expect("creating tmpdir failed");
455        let dir_path = tmpdir.path().join("testdir");
456        assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
457        NATIVE_FS
458            .create_dir(dir_path.to_str().expect("getting str for file failed"))
459            .unwrap();
460        assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
461        NATIVE_FS
462            .remove_dir(dir_path.to_str().unwrap(), false)
463            .unwrap();
464        assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
465    }
466
467    #[test]
468    fn test_read_file() {
469        let tmpdir = tempdir().expect("creating tmpdir failed");
470        let file_path = tmpdir.path().join("test.txt");
471        NATIVE_FS
472            .create_file(file_path.to_str().expect("getting str for file failed"))
473            .expect("creating file failed");
474        fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
475        let read_buf: &mut [u8] = &mut [0; 4];
476        NATIVE_FS
477            .read_data(file_path.to_str().unwrap(), 0, 4, read_buf)
478            .unwrap();
479        assert_eq!([1, 2, 3, 4], read_buf);
480        NATIVE_FS
481            .write_data(file_path.to_str().unwrap(), 4, &[5, 6, 7, 8])
482            .expect("writing to file failed");
483        NATIVE_FS
484            .read_data(file_path.to_str().unwrap(), 2, 4, read_buf)
485            .unwrap();
486        assert_eq!([3, 4, 5, 6], read_buf);
487    }
488
489    #[test]
490    fn test_remove_which_does_not_exist() {
491        let tmpdir = tempdir().expect("creating tmpdir failed");
492        let file_path = tmpdir.path().join("test.txt");
493        let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
494        assert!(result.is_err());
495        let error = result.unwrap_err();
496        if let FilestoreError::FileDoesNotExist = error {
497            assert_eq!(error.to_string(), "file does not exist");
498        } else {
499            panic!("unexpected error");
500        }
501    }
502
503    #[test]
504    fn test_file_already_exists() {
505        let tmpdir = tempdir().expect("creating tmpdir failed");
506        let file_path = tmpdir.path().join("test.txt");
507        let result =
508            NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
509        assert!(result.is_ok());
510        let result =
511            NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
512        assert!(result.is_err());
513        let error = result.unwrap_err();
514        if let FilestoreError::FileAlreadyExists = error {
515            assert_eq!(error.to_string(), "file already exists");
516        } else {
517            panic!("unexpected error");
518        }
519    }
520
521    #[test]
522    fn test_remove_file_with_dir_api() {
523        let tmpdir = tempdir().expect("creating tmpdir failed");
524        let file_path = tmpdir.path().join("test.txt");
525        NATIVE_FS
526            .create_file(file_path.to_str().expect("getting str for file failed"))
527            .unwrap();
528        let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
529        assert!(result.is_err());
530        let error = result.unwrap_err();
531        if let FilestoreError::IsNotDirectory = error {
532            assert_eq!(error.to_string(), "is not a directory");
533        } else {
534            panic!("unexpected error");
535        }
536    }
537
538    #[test]
539    fn test_remove_dir_remove_all() {
540        let tmpdir = tempdir().expect("creating tmpdir failed");
541        let dir_path = tmpdir.path().join("test");
542        NATIVE_FS
543            .create_dir(dir_path.to_str().expect("getting str for file failed"))
544            .unwrap();
545        let file_path = dir_path.as_path().join("test.txt");
546        NATIVE_FS
547            .create_file(file_path.to_str().expect("getting str for file failed"))
548            .unwrap();
549        let result = NATIVE_FS.remove_dir(dir_path.to_str().unwrap(), true);
550        assert!(result.is_ok());
551        assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
552    }
553
554    #[test]
555    fn test_remove_dir_with_file_api() {
556        let tmpdir = tempdir().expect("creating tmpdir failed");
557        let file_path = tmpdir.path().join("test");
558        NATIVE_FS
559            .create_dir(file_path.to_str().expect("getting str for file failed"))
560            .unwrap();
561        let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
562        assert!(result.is_err());
563        let error = result.unwrap_err();
564        if let FilestoreError::IsNotFile = error {
565            assert_eq!(error.to_string(), "is not a file");
566        } else {
567            panic!("unexpected error");
568        }
569    }
570
571    #[test]
572    fn test_remove_dir_which_does_not_exist() {
573        let tmpdir = tempdir().expect("creating tmpdir failed");
574        let file_path = tmpdir.path().join("test");
575        let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
576        assert!(result.is_err());
577        let error = result.unwrap_err();
578        if let FilestoreError::DirDoesNotExist = error {
579            assert_eq!(error.to_string(), "directory does not exist");
580        } else {
581            panic!("unexpected error");
582        }
583    }
584
585    #[test]
586    fn test_remove_file_which_does_not_exist() {
587        let tmpdir = tempdir().expect("creating tmpdir failed");
588        let file_path = tmpdir.path().join("test.txt");
589        let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
590        assert!(result.is_err());
591        let error = result.unwrap_err();
592        if let FilestoreError::FileDoesNotExist = error {
593            assert_eq!(error.to_string(), "file does not exist");
594        } else {
595            panic!("unexpected error");
596        }
597    }
598
599    #[test]
600    fn test_truncate_file_which_does_not_exist() {
601        let tmpdir = tempdir().expect("creating tmpdir failed");
602        let file_path = tmpdir.path().join("test.txt");
603        let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
604        assert!(result.is_err());
605        let error = result.unwrap_err();
606        if let FilestoreError::FileDoesNotExist = error {
607            assert_eq!(error.to_string(), "file does not exist");
608        } else {
609            panic!("unexpected error");
610        }
611    }
612
613    #[test]
614    fn test_truncate_file_on_directory() {
615        let tmpdir = tempdir().expect("creating tmpdir failed");
616        let file_path = tmpdir.path().join("test");
617        NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
618        let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
619        assert!(result.is_err());
620        let error = result.unwrap_err();
621        if let FilestoreError::IsNotFile = error {
622            assert_eq!(error.to_string(), "is not a file");
623        } else {
624            panic!("unexpected error");
625        }
626    }
627
628    #[test]
629    fn test_byte_conversion_error_when_reading() {
630        let tmpdir = tempdir().expect("creating tmpdir failed");
631        let file_path = tmpdir.path().join("test.txt");
632        NATIVE_FS
633            .create_file(file_path.to_str().expect("getting str for file failed"))
634            .unwrap();
635        let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 2, &mut []);
636        assert!(result.is_err());
637        let error = result.unwrap_err();
638        if let FilestoreError::ByteConversion(byte_conv_error) = error {
639            if let ByteConversionError::ToSliceTooSmall { found, expected } = byte_conv_error {
640                assert_eq!(found, 0);
641                assert_eq!(expected, 2);
642            } else {
643                panic!("unexpected error");
644            }
645            assert_eq!(
646                error.to_string(),
647                format!("byte conversion: {}", byte_conv_error)
648            );
649        } else {
650            panic!("unexpected error");
651        }
652    }
653
654    #[test]
655    fn test_read_file_on_dir() {
656        let tmpdir = tempdir().expect("creating tmpdir failed");
657        let dir_path = tmpdir.path().join("test");
658        NATIVE_FS
659            .create_dir(dir_path.to_str().expect("getting str for file failed"))
660            .unwrap();
661        let result = NATIVE_FS.read_data(dir_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
662        assert!(result.is_err());
663        let error = result.unwrap_err();
664        if let FilestoreError::IsNotFile = error {
665            assert_eq!(error.to_string(), "is not a file");
666        } else {
667            panic!("unexpected error");
668        }
669    }
670
671    #[test]
672    fn test_write_file_non_existing() {
673        let tmpdir = tempdir().expect("creating tmpdir failed");
674        let file_path = tmpdir.path().join("test.txt");
675        let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
676        assert!(result.is_err());
677        let error = result.unwrap_err();
678        if let FilestoreError::FileDoesNotExist = error {
679        } else {
680            panic!("unexpected error");
681        }
682    }
683
684    #[test]
685    fn test_write_file_on_dir() {
686        let tmpdir = tempdir().expect("creating tmpdir failed");
687        let file_path = tmpdir.path().join("test");
688        NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
689        let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
690        assert!(result.is_err());
691        let error = result.unwrap_err();
692        if let FilestoreError::IsNotFile = error {
693        } else {
694            panic!("unexpected error");
695        }
696    }
697
698    #[test]
699    fn test_filename_extraction() {
700        let tmpdir = tempdir().expect("creating tmpdir failed");
701        let file_path = tmpdir.path().join("test.txt");
702        NATIVE_FS
703            .create_file(file_path.to_str().expect("getting str for file failed"))
704            .unwrap();
705        NativeFilestore::filename_from_full_path(file_path.to_str().unwrap());
706    }
707
708    #[test]
709    fn test_modular_checksum() {
710        let tmpdir = tempdir().expect("creating tmpdir failed");
711        let file_path = tmpdir.path().join("mod-crc.bin");
712        fs::write(file_path.as_path(), EXAMPLE_DATA_CFDP).expect("writing test file failed");
713        // Kind of re-writing the modular checksum impl here which we are trying to test, but the
714        // numbers/correctness were verified manually using calculators, so this is okay.
715        let mut checksum: u32 = 0;
716        let mut buffer: [u8; 4] = [0; 4];
717        for i in 0..3 {
718            buffer = EXAMPLE_DATA_CFDP[i * 4..(i + 1) * 4].try_into().unwrap();
719            checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
720        }
721        buffer[0..3].copy_from_slice(&EXAMPLE_DATA_CFDP[12..15]);
722        buffer[3] = 0;
723        checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
724        let mut verif_buf: [u8; 32] = [0; 32];
725        let result = NATIVE_FS.checksum_verify(
726            checksum,
727            file_path.to_str().unwrap(),
728            ChecksumType::Modular,
729            EXAMPLE_DATA_CFDP.len() as u64,
730            &mut verif_buf,
731        );
732        assert!(result.is_ok());
733    }
734
735    #[test]
736    fn test_null_checksum_impl() {
737        let tmpdir = tempdir().expect("creating tmpdir failed");
738        let file_path = tmpdir.path().join("mod-crc.bin");
739        // The file to check does not even need to exist, and the verification buffer can be
740        // empty: the null checksum is always yields the same result.
741        let result = NATIVE_FS.checksum_verify(
742            0,
743            file_path.to_str().unwrap(),
744            ChecksumType::NullChecksum,
745            0,
746            &mut [],
747        );
748        assert!(result.is_ok());
749        assert!(result.unwrap());
750    }
751
752    #[test]
753    fn test_checksum_not_implemented() {
754        let tmpdir = tempdir().expect("creating tmpdir failed");
755        let file_path = tmpdir.path().join("mod-crc.bin");
756        // The file to check does not even need to exist, and the verification buffer can be
757        // empty: the null checksum is always yields the same result.
758        let result = NATIVE_FS.checksum_verify(
759            0,
760            file_path.to_str().unwrap(),
761            ChecksumType::Crc32Proximity1,
762            0,
763            &mut [],
764        );
765        assert!(result.is_err());
766        let error = result.unwrap_err();
767        if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
768            assert_eq!(
769                error.to_string(),
770                format!("checksum type not implemented: {:?}", cksum_type)
771            );
772        } else {
773            panic!("unexpected error");
774        }
775    }
776}