satrs/cfdp/
filestore.rs

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