1
use alloc::string::{String, ToString};
2
use core::fmt::Display;
3
use spacepackets::cfdp::ChecksumType;
4
use spacepackets::ByteConversionError;
5
#[cfg(feature = "std")]
6
use std::error::Error;
7
use std::path::Path;
8
#[cfg(feature = "std")]
9
pub use std_mod::*;
10

            
11
#[derive(Debug, Clone)]
12
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
14
#[non_exhaustive]
15
pub 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
    Utf8Error,
29
    Other,
30
}
31

            
32
impl From<ByteConversionError> for FilestoreError {
33
2
    fn from(value: ByteConversionError) -> Self {
34
2
        Self::ByteConversion(value)
35
2
    }
36
}
37

            
38
impl Display for FilestoreError {
39
22
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40
22
        match self {
41
            FilestoreError::FileDoesNotExist => {
42
6
                write!(f, "file does not exist")
43
            }
44
            FilestoreError::FileAlreadyExists => {
45
2
                write!(f, "file already exists")
46
            }
47
            FilestoreError::DirDoesNotExist => {
48
2
                write!(f, "directory does not exist")
49
            }
50
            FilestoreError::Permission => {
51
                write!(f, "permission error")
52
            }
53
            FilestoreError::IsNotFile => {
54
6
                write!(f, "is not a file")
55
            }
56
            FilestoreError::IsNotDirectory => {
57
2
                write!(f, "is not a directory")
58
            }
59
2
            FilestoreError::ByteConversion(e) => {
60
2
                write!(f, "filestore error: {e}")
61
            }
62
            FilestoreError::Io { raw_errno, string } => {
63
                write!(
64
                    f,
65
                    "filestore generic IO error with raw errno {:?}: {}",
66
                    raw_errno, string
67
                )
68
            }
69
2
            FilestoreError::ChecksumTypeNotImplemented(checksum_type) => {
70
2
                write!(f, "checksum {:?} not implemented", checksum_type)
71
            }
72
            FilestoreError::Utf8Error => {
73
                write!(f, "utf8 error")
74
            }
75
            FilestoreError::Other => {
76
                write!(f, "some filestore error occured")
77
            }
78
        }
79
22
    }
80
}
81

            
82
impl Error for FilestoreError {
83
    fn source(&self) -> Option<&(dyn Error + 'static)> {
84
        match self {
85
            FilestoreError::ByteConversion(e) => Some(e),
86
            _ => None,
87
        }
88
    }
89
}
90

            
91
#[cfg(feature = "std")]
92
impl From<std::io::Error> for FilestoreError {
93
    fn from(value: std::io::Error) -> Self {
94
        Self::Io {
95
            raw_errno: value.raw_os_error(),
96
            string: value.to_string(),
97
        }
98
    }
99
}
100

            
101
pub trait VirtualFilestore {
102
    fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>;
103

            
104
    fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError>;
105

            
106
    /// Truncating a file means deleting all its data so the resulting file is empty.
107
    /// This can be more efficient than removing and re-creating a file.
108
    fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError>;
109

            
110
    fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError>;
111
    fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError>;
112

            
113
    fn read_data(
114
        &self,
115
        file_path: &str,
116
        offset: u64,
117
        read_len: u64,
118
        buf: &mut [u8],
119
    ) -> Result<(), FilestoreError>;
120

            
121
    fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError>;
122

            
123
2
    fn filename_from_full_path(path: &str) -> Option<&str>
124
2
    where
125
2
        Self: Sized,
126
2
    {
127
2
        // Convert the path string to a Path
128
2
        let path = Path::new(path);
129
2

            
130
2
        // Extract the file name using the file_name() method
131
3
        path.file_name().and_then(|name| name.to_str())
132
2
    }
133

            
134
    fn is_file(&self, path: &str) -> Result<bool, FilestoreError>;
135

            
136
8
    fn is_dir(&self, path: &str) -> Result<bool, FilestoreError> {
137
8
        Ok(!self.is_file(path)?)
138
8
    }
139

            
140
    fn exists(&self, path: &str) -> Result<bool, FilestoreError>;
141

            
142
    /// Extract the file name part of a full path.
143
    ///
144
    /// This method should behave similarly to the [std::path::Path::file_name] method.
145
    fn file_name<'a>(&self, full_path: &'a str) -> Result<Option<&'a str>, FilestoreError>;
146

            
147
    fn file_size(&self, path: &str) -> Result<u64, FilestoreError>;
148

            
149
    /// This special function is the CFDP specific abstraction to calculate the checksum of a file.
150
    /// This allows to keep OS specific details like reading the whole file in the most efficient
151
    /// manner inside the file system abstraction.
152
    ///
153
    /// The passed verification buffer argument will be used by the specific implementation as
154
    /// a buffer to read the file into. It is recommended to use common buffer sizes like
155
    /// 4096 or 8192 bytes.
156
    fn calculate_checksum(
157
        &self,
158
        file_path: &str,
159
        checksum_type: ChecksumType,
160
        size_to_verify: u64,
161
        verification_buf: &mut [u8],
162
    ) -> Result<u32, FilestoreError>;
163

            
164
    /// This special function is the CFDP specific abstraction to verify the checksum of a file.
165
    /// This allows to keep OS specific details like reading the whole file in the most efficient
166
    /// manner inside the file system abstraction.
167
    ///
168
    /// The passed verification buffer argument will be used by the specific implementation as
169
    /// a buffer to read the file into. It is recommended to use common buffer sizes like
170
    /// 4096 or 8192 bytes.
171
30
    fn checksum_verify(
172
30
        &self,
173
30
        expected_checksum: u32,
174
30
        file_path: &str,
175
30
        checksum_type: ChecksumType,
176
30
        size_to_verify: u64,
177
30
        verification_buf: &mut [u8],
178
30
    ) -> Result<bool, FilestoreError> {
179
30
        Ok(
180
30
            self.calculate_checksum(file_path, checksum_type, size_to_verify, verification_buf)?
181
28
                == expected_checksum,
182
        )
183
30
    }
184
}
185

            
186
#[cfg(feature = "std")]
187
pub mod std_mod {
188

            
189
    use crc::Crc;
190

            
191
    use crate::{CRC_32, CRC_32C};
192

            
193
    use super::*;
194
    use std::{
195
        fs::{self, File, OpenOptions},
196
        io::{BufReader, Read, Seek, SeekFrom, Write},
197
    };
198

            
199
    #[derive(Default)]
200
    pub struct NativeFilestore {}
201

            
202
    impl VirtualFilestore for NativeFilestore {
203
68
        fn create_file(&self, file_path: &str) -> Result<(), FilestoreError> {
204
68
            if self.exists(file_path)? {
205
2
                return Err(FilestoreError::FileAlreadyExists);
206
66
            }
207
66
            File::create(file_path)?;
208
66
            Ok(())
209
68
        }
210

            
211
8
        fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError> {
212
8
            if !self.exists(file_path)? {
213
2
                return Err(FilestoreError::FileDoesNotExist);
214
6
            }
215
6
            if !self.is_file(file_path)? {
216
2
                return Err(FilestoreError::IsNotFile);
217
4
            }
218
4
            fs::remove_file(file_path)?;
219
4
            Ok(())
220
8
        }
221

            
222
        fn file_name<'a>(&self, full_path: &'a str) -> Result<Option<&'a str>, FilestoreError> {
223
            let path = Path::new(full_path);
224
            path.file_name()
225
                .map(|s| s.to_str())
226
                .ok_or(FilestoreError::Utf8Error)
227
        }
228

            
229
6
        fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> {
230
6
            if !self.exists(file_path)? {
231
2
                return Err(FilestoreError::FileDoesNotExist);
232
4
            }
233
4
            if !self.is_file(file_path)? {
234
2
                return Err(FilestoreError::IsNotFile);
235
2
            }
236
2
            OpenOptions::new()
237
2
                .write(true)
238
2
                .truncate(true)
239
2
                .open(file_path)?;
240
2
            Ok(())
241
6
        }
242

            
243
14
        fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> {
244
14
            fs::create_dir(dir_path).map_err(|e| FilestoreError::Io {
245
                raw_errno: e.raw_os_error(),
246
                string: e.to_string(),
247
14
            })?;
248
14
            Ok(())
249
14
        }
250

            
251
8
        fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError> {
252
8
            if !self.exists(dir_path)? {
253
2
                return Err(FilestoreError::DirDoesNotExist);
254
6
            }
255
6
            if !self.is_dir(dir_path)? {
256
2
                return Err(FilestoreError::IsNotDirectory);
257
4
            }
258
4
            if !all {
259
2
                fs::remove_dir(dir_path)?;
260
2
                return Ok(());
261
2
            }
262
2
            fs::remove_dir_all(dir_path)?;
263
2
            Ok(())
264
8
        }
265

            
266
32
        fn read_data(
267
32
            &self,
268
32
            file_name: &str,
269
32
            offset: u64,
270
32
            read_len: u64,
271
32
            buf: &mut [u8],
272
32
        ) -> Result<(), FilestoreError> {
273
32
            if buf.len() < read_len as usize {
274
2
                return Err(ByteConversionError::ToSliceTooSmall {
275
2
                    found: buf.len(),
276
2
                    expected: read_len as usize,
277
2
                }
278
2
                .into());
279
30
            }
280
30
            if !self.exists(file_name)? {
281
2
                return Err(FilestoreError::FileDoesNotExist);
282
28
            }
283
28
            if !self.is_file(file_name)? {
284
2
                return Err(FilestoreError::IsNotFile);
285
26
            }
286
26
            let mut file = File::open(file_name)?;
287
26
            file.seek(SeekFrom::Start(offset))?;
288
26
            file.read_exact(&mut buf[0..read_len as usize])?;
289
26
            Ok(())
290
32
        }
291

            
292
40
        fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError> {
293
40
            if !self.exists(file)? {
294
2
                return Err(FilestoreError::FileDoesNotExist);
295
38
            }
296
38
            if !self.is_file(file)? {
297
2
                return Err(FilestoreError::IsNotFile);
298
36
            }
299
36
            let mut file = OpenOptions::new().write(true).open(file)?;
300
36
            file.seek(SeekFrom::Start(offset))?;
301
36
            file.write_all(buf)?;
302
36
            Ok(())
303
40
        }
304

            
305
94
        fn is_file(&self, str_path: &str) -> Result<bool, FilestoreError> {
306
94
            let path = Path::new(str_path);
307
94
            if !self.exists(str_path)? {
308
                return Err(FilestoreError::FileDoesNotExist);
309
94
            }
310
94
            Ok(path.is_file())
311
94
        }
312

            
313
422
        fn exists(&self, path: &str) -> Result<bool, FilestoreError> {
314
422
            let path = Path::new(path);
315
422
            Ok(self.exists_internal(path))
316
422
        }
317

            
318
24
        fn file_size(&self, str_path: &str) -> Result<u64, FilestoreError> {
319
24
            let path = Path::new(str_path);
320
24
            if !self.exists_internal(path) {
321
                return Err(FilestoreError::FileDoesNotExist);
322
24
            }
323
24
            if !path.is_file() {
324
                return Err(FilestoreError::IsNotFile);
325
24
            }
326
24
            Ok(path.metadata()?.len())
327
24
        }
328

            
329
62
        fn calculate_checksum(
330
62
            &self,
331
62
            file_path: &str,
332
62
            checksum_type: ChecksumType,
333
62
            size_to_verify: u64,
334
62
            verification_buf: &mut [u8],
335
62
        ) -> Result<u32, FilestoreError> {
336
86
            let mut calc_with_crc_lib = |crc: Crc<u32>| -> Result<u32, FilestoreError> {
337
56
                let mut digest = crc.digest();
338
56
                let mut buf_reader = BufReader::new(File::open(file_path)?);
339
56
                let mut remaining_bytes = size_to_verify;
340
102
                while remaining_bytes > 0 {
341
                    // Read the smaller of the remaining bytes or the buffer size
342
46
                    let bytes_to_read = remaining_bytes.min(verification_buf.len() as u64) as usize;
343
46
                    let bytes_read = buf_reader.read(&mut verification_buf[0..bytes_to_read])?;
344

            
345
46
                    if bytes_read == 0 {
346
                        break; // Reached end of file
347
46
                    }
348
46
                    digest.update(&verification_buf[0..bytes_read]);
349
46
                    remaining_bytes -= bytes_read as u64;
350
                }
351
56
                Ok(digest.finalize())
352
56
            };
353
62
            match checksum_type {
354
2
                ChecksumType::Modular => self.calc_modular_checksum(file_path),
355
56
                ChecksumType::Crc32 => calc_with_crc_lib(CRC_32),
356
                ChecksumType::Crc32C => calc_with_crc_lib(CRC_32C),
357
2
                ChecksumType::NullChecksum => Ok(0),
358
2
                _ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)),
359
            }
360
62
        }
361
    }
362

            
363
    impl NativeFilestore {
364
2
        pub fn calc_modular_checksum(&self, file_path: &str) -> Result<u32, FilestoreError> {
365
2
            let mut checksum: u32 = 0;
366
2
            let file = File::open(file_path)?;
367
2
            let mut buf_reader = BufReader::new(file);
368
2
            let mut buffer = [0; 4];
369

            
370
            loop {
371
10
                let bytes_read = buf_reader.read(&mut buffer)?;
372
10
                if bytes_read == 0 {
373
2
                    break;
374
8
                }
375
8
                // Perform padding directly in the buffer
376
9
                (bytes_read..4).for_each(|i| {
377
2
                    buffer[i] = 0;
378
9
                });
379
8

            
380
8
                checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
381
            }
382
2
            Ok(checksum)
383
2
        }
384

            
385
446
        fn exists_internal(&self, path: &Path) -> bool {
386
446
            if !path.exists() {
387
178
                return false;
388
268
            }
389
268
            true
390
446
        }
391
    }
392
}
393

            
394
#[cfg(test)]
395
mod tests {
396
    use std::{fs, path::Path, println};
397

            
398
    use super::*;
399
    use alloc::format;
400
    use tempfile::tempdir;
401

            
402
    const EXAMPLE_DATA_CFDP: [u8; 15] = [
403
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
404
    ];
405

            
406
    const NATIVE_FS: NativeFilestore = NativeFilestore {};
407

            
408
    #[test]
409
2
    fn test_basic_native_filestore_create() {
410
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
411
2
        let file_path = tmpdir.path().join("test.txt");
412
2
        let result =
413
2
            NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
414
2
        assert!(result.is_ok());
415
2
        let path = Path::new(&file_path);
416
2
        assert!(path.exists());
417
2
        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
418
2
        assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
419
2
    }
420

            
421
    #[test]
422
2
    fn test_basic_native_fs_file_exists() {
423
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
424
2
        let file_path = tmpdir.path().join("test.txt");
425
2
        assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
426
2
        NATIVE_FS
427
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
428
2
            .unwrap();
429
2
        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
430
2
        assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
431
2
    }
432

            
433
    #[test]
434
2
    fn test_basic_native_fs_dir_exists() {
435
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
436
2
        let dir_path = tmpdir.path().join("testdir");
437
2
        assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
438
2
        NATIVE_FS
439
2
            .create_dir(dir_path.to_str().expect("getting str for file failed"))
440
2
            .unwrap();
441
2
        assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
442
2
        assert!(NATIVE_FS
443
2
            .is_dir(dir_path.as_path().to_str().unwrap())
444
2
            .unwrap());
445
2
    }
446

            
447
    #[test]
448
2
    fn test_basic_native_fs_remove_file() {
449
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
450
2
        let file_path = tmpdir.path().join("test.txt");
451
2
        NATIVE_FS
452
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
453
2
            .expect("creating file failed");
454
2
        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
455
2
        NATIVE_FS
456
2
            .remove_file(file_path.to_str().unwrap())
457
2
            .expect("removing file failed");
458
2
        assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
459
2
    }
460

            
461
    #[test]
462
2
    fn test_basic_native_fs_write() {
463
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
464
2
        let file_path = tmpdir.path().join("test.txt");
465
2
        assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
466
2
        NATIVE_FS
467
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
468
2
            .unwrap();
469
2
        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
470
2
        assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
471
2
        println!("{}", file_path.to_str().unwrap());
472
2
        let write_data = "hello world\n";
473
2
        NATIVE_FS
474
2
            .write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
475
2
            .expect("writing to file failed");
476
2
        let read_back = fs::read_to_string(file_path).expect("reading back data failed");
477
2
        assert_eq!(read_back, write_data);
478
2
    }
479

            
480
    #[test]
481
2
    fn test_basic_native_fs_read() {
482
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
483
2
        let file_path = tmpdir.path().join("test.txt");
484
2
        assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
485
2
        NATIVE_FS
486
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
487
2
            .unwrap();
488
2
        assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
489
2
        assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
490
2
        println!("{}", file_path.to_str().unwrap());
491
2
        let write_data = "hello world\n";
492
2
        NATIVE_FS
493
2
            .write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
494
2
            .expect("writing to file failed");
495
2
        let read_back = fs::read_to_string(file_path).expect("reading back data failed");
496
2
        assert_eq!(read_back, write_data);
497
2
    }
498

            
499
    #[test]
500
2
    fn test_truncate_file() {
501
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
502
2
        let file_path = tmpdir.path().join("test.txt");
503
2
        NATIVE_FS
504
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
505
2
            .expect("creating file failed");
506
2
        fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
507
2
        assert_eq!(fs::read(file_path.clone()).unwrap(), [1, 2, 3, 4]);
508
2
        NATIVE_FS
509
2
            .truncate_file(file_path.to_str().unwrap())
510
2
            .unwrap();
511
2
        assert_eq!(fs::read(file_path.clone()).unwrap(), []);
512
2
    }
513

            
514
    #[test]
515
2
    fn test_remove_dir() {
516
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
517
2
        let dir_path = tmpdir.path().join("testdir");
518
2
        assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
519
2
        NATIVE_FS
520
2
            .create_dir(dir_path.to_str().expect("getting str for file failed"))
521
2
            .unwrap();
522
2
        assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
523
2
        NATIVE_FS
524
2
            .remove_dir(dir_path.to_str().unwrap(), false)
525
2
            .unwrap();
526
2
        assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
527
2
    }
528

            
529
    #[test]
530
2
    fn test_read_file() {
531
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
532
2
        let file_path = tmpdir.path().join("test.txt");
533
2
        NATIVE_FS
534
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
535
2
            .expect("creating file failed");
536
2
        fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
537
2
        let read_buf: &mut [u8] = &mut [0; 4];
538
2
        NATIVE_FS
539
2
            .read_data(file_path.to_str().unwrap(), 0, 4, read_buf)
540
2
            .unwrap();
541
2
        assert_eq!([1, 2, 3, 4], read_buf);
542
2
        NATIVE_FS
543
2
            .write_data(file_path.to_str().unwrap(), 4, &[5, 6, 7, 8])
544
2
            .expect("writing to file failed");
545
2
        NATIVE_FS
546
2
            .read_data(file_path.to_str().unwrap(), 2, 4, read_buf)
547
2
            .unwrap();
548
2
        assert_eq!([3, 4, 5, 6], read_buf);
549
2
    }
550

            
551
    #[test]
552
2
    fn test_remove_which_does_not_exist() {
553
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
554
2
        let file_path = tmpdir.path().join("test.txt");
555
2
        let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
556
2
        assert!(result.is_err());
557
2
        let error = result.unwrap_err();
558
2
        if let FilestoreError::FileDoesNotExist = error {
559
2
            assert_eq!(error.to_string(), "file does not exist");
560
        } else {
561
            panic!("unexpected error");
562
        }
563
2
    }
564

            
565
    #[test]
566
2
    fn test_file_already_exists() {
567
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
568
2
        let file_path = tmpdir.path().join("test.txt");
569
2
        let result =
570
2
            NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
571
2
        assert!(result.is_ok());
572
2
        let result =
573
2
            NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
574
2
        assert!(result.is_err());
575
2
        let error = result.unwrap_err();
576
2
        if let FilestoreError::FileAlreadyExists = error {
577
2
            assert_eq!(error.to_string(), "file already exists");
578
        } else {
579
            panic!("unexpected error");
580
        }
581
2
    }
582

            
583
    #[test]
584
2
    fn test_remove_file_with_dir_api() {
585
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
586
2
        let file_path = tmpdir.path().join("test.txt");
587
2
        NATIVE_FS
588
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
589
2
            .unwrap();
590
2
        let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
591
2
        assert!(result.is_err());
592
2
        let error = result.unwrap_err();
593
2
        if let FilestoreError::IsNotDirectory = error {
594
2
            assert_eq!(error.to_string(), "is not a directory");
595
        } else {
596
            panic!("unexpected error");
597
        }
598
2
    }
599

            
600
    #[test]
601
2
    fn test_remove_dir_remove_all() {
602
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
603
2
        let dir_path = tmpdir.path().join("test");
604
2
        NATIVE_FS
605
2
            .create_dir(dir_path.to_str().expect("getting str for file failed"))
606
2
            .unwrap();
607
2
        let file_path = dir_path.as_path().join("test.txt");
608
2
        NATIVE_FS
609
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
610
2
            .unwrap();
611
2
        let result = NATIVE_FS.remove_dir(dir_path.to_str().unwrap(), true);
612
2
        assert!(result.is_ok());
613
2
        assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
614
2
    }
615

            
616
    #[test]
617
2
    fn test_remove_dir_with_file_api() {
618
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
619
2
        let file_path = tmpdir.path().join("test");
620
2
        NATIVE_FS
621
2
            .create_dir(file_path.to_str().expect("getting str for file failed"))
622
2
            .unwrap();
623
2
        let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
624
2
        assert!(result.is_err());
625
2
        let error = result.unwrap_err();
626
2
        if let FilestoreError::IsNotFile = error {
627
2
            assert_eq!(error.to_string(), "is not a file");
628
        } else {
629
            panic!("unexpected error");
630
        }
631
2
    }
632

            
633
    #[test]
634
2
    fn test_remove_dir_which_does_not_exist() {
635
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
636
2
        let file_path = tmpdir.path().join("test");
637
2
        let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
638
2
        assert!(result.is_err());
639
2
        let error = result.unwrap_err();
640
2
        if let FilestoreError::DirDoesNotExist = error {
641
2
            assert_eq!(error.to_string(), "directory does not exist");
642
        } else {
643
            panic!("unexpected error");
644
        }
645
2
    }
646

            
647
    #[test]
648
2
    fn test_remove_file_which_does_not_exist() {
649
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
650
2
        let file_path = tmpdir.path().join("test.txt");
651
2
        let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
652
2
        assert!(result.is_err());
653
2
        let error = result.unwrap_err();
654
2
        if let FilestoreError::FileDoesNotExist = error {
655
2
            assert_eq!(error.to_string(), "file does not exist");
656
        } else {
657
            panic!("unexpected error");
658
        }
659
2
    }
660

            
661
    #[test]
662
2
    fn test_truncate_file_which_does_not_exist() {
663
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
664
2
        let file_path = tmpdir.path().join("test.txt");
665
2
        let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
666
2
        assert!(result.is_err());
667
2
        let error = result.unwrap_err();
668
2
        if let FilestoreError::FileDoesNotExist = error {
669
2
            assert_eq!(error.to_string(), "file does not exist");
670
        } else {
671
            panic!("unexpected error");
672
        }
673
2
    }
674

            
675
    #[test]
676
2
    fn test_truncate_file_on_directory() {
677
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
678
2
        let file_path = tmpdir.path().join("test");
679
2
        NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
680
2
        let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
681
2
        assert!(result.is_err());
682
2
        let error = result.unwrap_err();
683
2
        if let FilestoreError::IsNotFile = error {
684
2
            assert_eq!(error.to_string(), "is not a file");
685
        } else {
686
            panic!("unexpected error");
687
        }
688
2
    }
689

            
690
    #[test]
691
2
    fn test_byte_conversion_error_when_reading() {
692
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
693
2
        let file_path = tmpdir.path().join("test.txt");
694
2
        NATIVE_FS
695
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
696
2
            .unwrap();
697
2
        let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 2, &mut []);
698
2
        assert!(result.is_err());
699
2
        let error = result.unwrap_err();
700
2
        if let FilestoreError::ByteConversion(byte_conv_error) = error {
701
2
            if let ByteConversionError::ToSliceTooSmall { found, expected } = byte_conv_error {
702
2
                assert_eq!(found, 0);
703
2
                assert_eq!(expected, 2);
704
            } else {
705
                panic!("unexpected error");
706
            }
707
2
            assert_eq!(
708
2
                error.to_string(),
709
2
                format!("filestore error: {}", byte_conv_error)
710
2
            );
711
        } else {
712
            panic!("unexpected error");
713
        }
714
2
    }
715

            
716
    #[test]
717
2
    fn test_read_file_on_dir() {
718
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
719
2
        let dir_path = tmpdir.path().join("test");
720
2
        NATIVE_FS
721
2
            .create_dir(dir_path.to_str().expect("getting str for file failed"))
722
2
            .unwrap();
723
2
        let result = NATIVE_FS.read_data(dir_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
724
2
        assert!(result.is_err());
725
2
        let error = result.unwrap_err();
726
2
        if let FilestoreError::IsNotFile = error {
727
2
            assert_eq!(error.to_string(), "is not a file");
728
        } else {
729
            panic!("unexpected error");
730
        }
731
2
    }
732

            
733
    #[test]
734
2
    fn test_write_file_non_existing() {
735
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
736
2
        let file_path = tmpdir.path().join("test.txt");
737
2
        let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
738
2
        assert!(result.is_err());
739
2
        let error = result.unwrap_err();
740
2
        if let FilestoreError::FileDoesNotExist = error {
741
2
        } else {
742
            panic!("unexpected error");
743
        }
744
2
    }
745

            
746
    #[test]
747
2
    fn test_write_file_on_dir() {
748
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
749
2
        let file_path = tmpdir.path().join("test");
750
2
        NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
751
2
        let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
752
2
        assert!(result.is_err());
753
2
        let error = result.unwrap_err();
754
2
        if let FilestoreError::IsNotFile = error {
755
2
        } else {
756
            panic!("unexpected error");
757
        }
758
2
    }
759

            
760
    #[test]
761
2
    fn test_filename_extraction() {
762
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
763
2
        let file_path = tmpdir.path().join("test.txt");
764
2
        NATIVE_FS
765
2
            .create_file(file_path.to_str().expect("getting str for file failed"))
766
2
            .unwrap();
767
2
        NativeFilestore::filename_from_full_path(file_path.to_str().unwrap());
768
2
    }
769

            
770
    #[test]
771
2
    fn test_modular_checksum() {
772
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
773
2
        let file_path = tmpdir.path().join("mod-crc.bin");
774
2
        fs::write(file_path.as_path(), EXAMPLE_DATA_CFDP).expect("writing test file failed");
775
2
        // Kind of re-writing the modular checksum impl here which we are trying to test, but the
776
2
        // numbers/correctness were verified manually using calculators, so this is okay.
777
2
        let mut checksum: u32 = 0;
778
2
        let mut buffer: [u8; 4] = [0; 4];
779
8
        for i in 0..3 {
780
6
            buffer = EXAMPLE_DATA_CFDP[i * 4..(i + 1) * 4].try_into().unwrap();
781
6
            checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
782
6
        }
783
2
        buffer[0..3].copy_from_slice(&EXAMPLE_DATA_CFDP[12..15]);
784
2
        buffer[3] = 0;
785
2
        checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
786
2
        let mut verif_buf: [u8; 32] = [0; 32];
787
2
        let result = NATIVE_FS.checksum_verify(
788
2
            checksum,
789
2
            file_path.to_str().unwrap(),
790
2
            ChecksumType::Modular,
791
2
            EXAMPLE_DATA_CFDP.len() as u64,
792
2
            &mut verif_buf,
793
2
        );
794
2
        assert!(result.is_ok());
795
2
    }
796

            
797
    #[test]
798
2
    fn test_null_checksum_impl() {
799
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
800
2
        let file_path = tmpdir.path().join("mod-crc.bin");
801
2
        // The file to check does not even need to exist, and the verification buffer can be
802
2
        // empty: the null checksum is always yields the same result.
803
2
        let result = NATIVE_FS.checksum_verify(
804
2
            0,
805
2
            file_path.to_str().unwrap(),
806
2
            ChecksumType::NullChecksum,
807
2
            0,
808
2
            &mut [],
809
2
        );
810
2
        assert!(result.is_ok());
811
2
        assert!(result.unwrap());
812
2
    }
813

            
814
    #[test]
815
2
    fn test_checksum_not_implemented() {
816
2
        let tmpdir = tempdir().expect("creating tmpdir failed");
817
2
        let file_path = tmpdir.path().join("mod-crc.bin");
818
2
        // The file to check does not even need to exist, and the verification buffer can be
819
2
        // empty: the null checksum is always yields the same result.
820
2
        let result = NATIVE_FS.checksum_verify(
821
2
            0,
822
2
            file_path.to_str().unwrap(),
823
2
            ChecksumType::Crc32Proximity1,
824
2
            0,
825
2
            &mut [],
826
2
        );
827
2
        assert!(result.is_err());
828
2
        let error = result.unwrap_err();
829
2
        if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
830
2
            assert_eq!(
831
2
                error.to_string(),
832
2
                format!("checksum {:?} not implemented", cksum_type)
833
2
            );
834
        } else {
835
            panic!("unexpected error");
836
        }
837
2
    }
838
}