CFDP extracted to library #201

Closed
muellerr wants to merge 18 commits from continue-cfsp-source-handler into main
7 changed files with 0 additions and 5012 deletions
Showing only changes of commit 8b887f9466 - Show all commits

File diff suppressed because it is too large Load Diff

View File

@ -1,802 +0,0 @@
use alloc::string::{String, ToString};
use core::fmt::Display;
use crc::{Crc, CRC_32_CKSUM};
use spacepackets::cfdp::ChecksumType;
use spacepackets::ByteConversionError;
#[cfg(feature = "std")]
use std::error::Error;
use std::path::Path;
#[cfg(feature = "std")]
pub use std_mod::*;
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
#[derive(Debug, Clone)]
pub enum FilestoreError {
FileDoesNotExist,
FileAlreadyExists,
DirDoesNotExist,
Permission,
IsNotFile,
IsNotDirectory,
ByteConversion(ByteConversionError),
Io {
raw_errno: Option<i32>,
string: String,
},
ChecksumTypeNotImplemented(ChecksumType),
}
impl From<ByteConversionError> for FilestoreError {
fn from(value: ByteConversionError) -> Self {
Self::ByteConversion(value)
}
}
impl Display for FilestoreError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FilestoreError::FileDoesNotExist => {
write!(f, "file does not exist")
}
FilestoreError::FileAlreadyExists => {
write!(f, "file already exists")
}
FilestoreError::DirDoesNotExist => {
write!(f, "directory does not exist")
}
FilestoreError::Permission => {
write!(f, "permission error")
}
FilestoreError::IsNotFile => {
write!(f, "is not a file")
}
FilestoreError::IsNotDirectory => {
write!(f, "is not a directory")
}
FilestoreError::ByteConversion(e) => {
write!(f, "filestore error: {e}")
}
FilestoreError::Io { raw_errno, string } => {
write!(
f,
"filestore generic IO error with raw errno {:?}: {}",
raw_errno, string
)
}
FilestoreError::ChecksumTypeNotImplemented(checksum_type) => {
write!(f, "checksum {:?} not implemented", checksum_type)
}
}
}
}
impl Error for FilestoreError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
FilestoreError::ByteConversion(e) => Some(e),
_ => None,
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for FilestoreError {
fn from(value: std::io::Error) -> Self {
Self::Io {
raw_errno: value.raw_os_error(),
string: value.to_string(),
}
}
}
pub trait VirtualFilestore {
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>;
fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError>;
/// Truncating a file means deleting all its data so the resulting file is empty.
/// This can be more efficient than removing and re-creating a file.
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError>;
fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError>;
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError>;
fn read_data(
&self,
file_path: &str,
offset: u64,
read_len: u64,
buf: &mut [u8],
) -> Result<(), FilestoreError>;
fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError>;
fn filename_from_full_path(path: &str) -> Option<&str>
where
Self: Sized,
{
// Convert the path string to a Path
let path = Path::new(path);
// Extract the file name using the file_name() method
path.file_name().and_then(|name| name.to_str())
}
fn is_file(&self, path: &str) -> Result<bool, FilestoreError>;
fn is_dir(&self, path: &str) -> Result<bool, FilestoreError> {
Ok(!self.is_file(path)?)
}
fn exists(&self, path: &str) -> Result<bool, FilestoreError>;
fn file_size(&self, path: &str) -> Result<u64, FilestoreError>;
/// This special function is the CFDP specific abstraction to calculate the checksum of a file.
/// This allows to keep OS specific details like reading the whole file in the most efficient
/// manner inside the file system abstraction.
///
/// The passed verification buffer argument will be used by the specific implementation as
/// a buffer to read the file into. It is recommended to use common buffer sizes like
/// 4096 or 8192 bytes.
fn calculate_checksum(
&self,
file_path: &str,
checksum_type: ChecksumType,
verification_buf: &mut [u8],
) -> Result<u32, FilestoreError>;
/// This special function is the CFDP specific abstraction to verify the checksum of a file.
/// This allows to keep OS specific details like reading the whole file in the most efficient
/// manner inside the file system abstraction.
///
/// The passed verification buffer argument will be used by the specific implementation as
/// a buffer to read the file into. It is recommended to use common buffer sizes like
/// 4096 or 8192 bytes.
fn checksum_verify(
&self,
file_path: &str,
checksum_type: ChecksumType,
expected_checksum: u32,
verification_buf: &mut [u8],
) -> Result<bool, FilestoreError> {
Ok(
self.calculate_checksum(file_path, checksum_type, verification_buf)?
== expected_checksum,
)
}
}
#[cfg(feature = "std")]
pub mod std_mod {
use super::*;
use std::{
fs::{self, File, OpenOptions},
io::{BufReader, Read, Seek, SeekFrom, Write},
};
#[derive(Default)]
pub struct NativeFilestore {}
impl VirtualFilestore for NativeFilestore {
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError> {
if self.exists(file_path)? {
return Err(FilestoreError::FileAlreadyExists);
}
File::create(file_path)?;
Ok(())
}
fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError> {
if !self.exists(file_path)? {
return Err(FilestoreError::FileDoesNotExist);
}
if !self.is_file(file_path)? {
return Err(FilestoreError::IsNotFile);
}
fs::remove_file(file_path)?;
Ok(())
}
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> {
if !self.exists(file_path)? {
return Err(FilestoreError::FileDoesNotExist);
}
if !self.is_file(file_path)? {
return Err(FilestoreError::IsNotFile);
}
OpenOptions::new()
.write(true)
.truncate(true)
.open(file_path)?;
Ok(())
}
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> {
fs::create_dir(dir_path).map_err(|e| FilestoreError::Io {
raw_errno: e.raw_os_error(),
string: e.to_string(),
})?;
Ok(())
}
fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError> {
if !self.exists(dir_path)? {
return Err(FilestoreError::DirDoesNotExist);
}
if !self.is_dir(dir_path)? {
return Err(FilestoreError::IsNotDirectory);
}
if !all {
fs::remove_dir(dir_path)?;
return Ok(());
}
fs::remove_dir_all(dir_path)?;
Ok(())
}
fn read_data(
&self,
file_name: &str,
offset: u64,
read_len: u64,
buf: &mut [u8],
) -> Result<(), FilestoreError> {
if buf.len() < read_len as usize {
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
expected: read_len as usize,
}
.into());
}
if !self.exists(file_name)? {
return Err(FilestoreError::FileDoesNotExist);
}
if !self.is_file(file_name)? {
return Err(FilestoreError::IsNotFile);
}
let mut file = File::open(file_name)?;
file.seek(SeekFrom::Start(offset))?;
file.read_exact(&mut buf[0..read_len as usize])?;
Ok(())
}
fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError> {
if !self.exists(file)? {
return Err(FilestoreError::FileDoesNotExist);
}
if !self.is_file(file)? {
return Err(FilestoreError::IsNotFile);
}
let mut file = OpenOptions::new().write(true).open(file)?;
file.seek(SeekFrom::Start(offset))?;
file.write_all(buf)?;
Ok(())
}
fn is_file(&self, str_path: &str) -> Result<bool, FilestoreError> {
let path = Path::new(str_path);
if !self.exists(str_path)? {
return Err(FilestoreError::FileDoesNotExist);
}
Ok(path.is_file())
}
fn exists(&self, path: &str) -> Result<bool, FilestoreError> {
let path = Path::new(path);
Ok(self.exists_internal(path))
}
fn file_size(&self, str_path: &str) -> Result<u64, FilestoreError> {
let path = Path::new(str_path);
if !self.exists_internal(path) {
return Err(FilestoreError::FileDoesNotExist);
}
if !path.is_file() {
return Err(FilestoreError::IsNotFile);
}
Ok(path.metadata()?.len())
}
fn calculate_checksum(
&self,
file_path: &str,
checksum_type: ChecksumType,
verification_buf: &mut [u8],
) -> Result<u32, FilestoreError> {
match checksum_type {
ChecksumType::Modular => self.calc_modular_checksum(file_path),
ChecksumType::Crc32 => {
let mut digest = CRC_32.digest();
let file_to_check = File::open(file_path)?;
let mut buf_reader = BufReader::new(file_to_check);
loop {
let bytes_read = buf_reader.read(verification_buf)?;
if bytes_read == 0 {
break;
}
digest.update(&verification_buf[0..bytes_read]);
}
Ok(digest.finalize())
}
ChecksumType::NullChecksum => Ok(0),
_ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)),
}
}
}
impl NativeFilestore {
pub fn calc_modular_checksum(&self, file_path: &str) -> Result<u32, FilestoreError> {
let mut checksum: u32 = 0;
let file = File::open(file_path)?;
let mut buf_reader = BufReader::new(file);
let mut buffer = [0; 4];
loop {
let bytes_read = buf_reader.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
// Perform padding directly in the buffer
(bytes_read..4).for_each(|i| {
buffer[i] = 0;
});
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
}
Ok(checksum)
}
fn exists_internal(&self, path: &Path) -> bool {
if !path.exists() {
return false;
}
true
}
}
}
#[cfg(test)]
mod tests {
use std::{fs, path::Path, println};
use super::*;
use alloc::format;
use tempfile::tempdir;
const EXAMPLE_DATA_CFDP: [u8; 15] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
];
const NATIVE_FS: NativeFilestore = NativeFilestore {};
#[test]
fn test_basic_native_filestore_create() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result =
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
assert!(result.is_ok());
let path = Path::new(&file_path);
assert!(path.exists());
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
}
#[test]
fn test_basic_native_fs_file_exists() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
}
#[test]
fn test_basic_native_fs_dir_exists() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let dir_path = tmpdir.path().join("testdir");
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
NATIVE_FS
.create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
assert!(NATIVE_FS
.is_dir(dir_path.as_path().to_str().unwrap())
.unwrap());
}
#[test]
fn test_basic_native_fs_remove_file() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.expect("creating file failed");
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
NATIVE_FS
.remove_file(file_path.to_str().unwrap())
.expect("removing file failed");
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
}
#[test]
fn test_basic_native_fs_write() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
println!("{}", file_path.to_str().unwrap());
let write_data = "hello world\n";
NATIVE_FS
.write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
.expect("writing to file failed");
let read_back = fs::read_to_string(file_path).expect("reading back data failed");
assert_eq!(read_back, write_data);
}
#[test]
fn test_basic_native_fs_read() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap());
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap());
println!("{}", file_path.to_str().unwrap());
let write_data = "hello world\n";
NATIVE_FS
.write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
.expect("writing to file failed");
let read_back = fs::read_to_string(file_path).expect("reading back data failed");
assert_eq!(read_back, write_data);
}
#[test]
fn test_truncate_file() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.expect("creating file failed");
fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
assert_eq!(fs::read(file_path.clone()).unwrap(), [1, 2, 3, 4]);
NATIVE_FS
.truncate_file(file_path.to_str().unwrap())
.unwrap();
assert_eq!(fs::read(file_path.clone()).unwrap(), []);
}
#[test]
fn test_remove_dir() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let dir_path = tmpdir.path().join("testdir");
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
NATIVE_FS
.create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
NATIVE_FS
.remove_dir(dir_path.to_str().unwrap(), false)
.unwrap();
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
}
#[test]
fn test_read_file() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.expect("creating file failed");
fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
let read_buf: &mut [u8] = &mut [0; 4];
NATIVE_FS
.read_data(file_path.to_str().unwrap(), 0, 4, read_buf)
.unwrap();
assert_eq!([1, 2, 3, 4], read_buf);
NATIVE_FS
.write_data(file_path.to_str().unwrap(), 4, &[5, 6, 7, 8])
.expect("writing to file failed");
NATIVE_FS
.read_data(file_path.to_str().unwrap(), 2, 4, read_buf)
.unwrap();
assert_eq!([3, 4, 5, 6], read_buf);
}
#[test]
fn test_remove_which_does_not_exist() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileDoesNotExist = error {
assert_eq!(error.to_string(), "file does not exist");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_file_already_exists() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result =
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
assert!(result.is_ok());
let result =
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileAlreadyExists = error {
assert_eq!(error.to_string(), "file already exists");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_remove_file_with_dir_api() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotDirectory = error {
assert_eq!(error.to_string(), "is not a directory");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_remove_dir_remove_all() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let dir_path = tmpdir.path().join("test");
NATIVE_FS
.create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap();
let file_path = dir_path.as_path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.remove_dir(dir_path.to_str().unwrap(), true);
assert!(result.is_ok());
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
}
#[test]
fn test_remove_dir_with_file_api() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test");
NATIVE_FS
.create_dir(file_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotFile = error {
assert_eq!(error.to_string(), "is not a file");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_remove_dir_which_does_not_exist() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test");
let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::DirDoesNotExist = error {
assert_eq!(error.to_string(), "directory does not exist");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_remove_file_which_does_not_exist() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileDoesNotExist = error {
assert_eq!(error.to_string(), "file does not exist");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_truncate_file_which_does_not_exist() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileDoesNotExist = error {
assert_eq!(error.to_string(), "file does not exist");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_truncate_file_on_directory() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test");
NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotFile = error {
assert_eq!(error.to_string(), "is not a file");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_byte_conversion_error_when_reading() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 2, &mut []);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::ByteConversion(byte_conv_error) = error {
if let ByteConversionError::ToSliceTooSmall { found, expected } = byte_conv_error {
assert_eq!(found, 0);
assert_eq!(expected, 2);
} else {
panic!("unexpected error");
}
assert_eq!(
error.to_string(),
format!("filestore error: {}", byte_conv_error)
);
} else {
panic!("unexpected error");
}
}
#[test]
fn test_read_file_on_dir() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let dir_path = tmpdir.path().join("test");
NATIVE_FS
.create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.read_data(dir_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotFile = error {
assert_eq!(error.to_string(), "is not a file");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_write_file_non_existing() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileDoesNotExist = error {
} else {
panic!("unexpected error");
}
}
#[test]
fn test_write_file_on_dir() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test");
NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotFile = error {
} else {
panic!("unexpected error");
}
}
#[test]
fn test_filename_extraction() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
NativeFilestore::filename_from_full_path(file_path.to_str().unwrap());
}
#[test]
fn test_modular_checksum() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("mod-crc.bin");
fs::write(file_path.as_path(), EXAMPLE_DATA_CFDP).expect("writing test file failed");
// Kind of re-writing the modular checksum impl here which we are trying to test, but the
// numbers/correctness were verified manually using calculators, so this is okay.
let mut checksum: u32 = 0;
let mut buffer: [u8; 4] = [0; 4];
for i in 0..3 {
buffer = EXAMPLE_DATA_CFDP[i * 4..(i + 1) * 4].try_into().unwrap();
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
}
buffer[0..3].copy_from_slice(&EXAMPLE_DATA_CFDP[12..15]);
buffer[3] = 0;
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
let mut verif_buf: [u8; 32] = [0; 32];
let result = NATIVE_FS.checksum_verify(
file_path.to_str().unwrap(),
ChecksumType::Modular,
checksum,
&mut verif_buf,
);
assert!(result.is_ok());
}
#[test]
fn test_null_checksum_impl() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("mod-crc.bin");
// The file to check does not even need to exist, and the verification buffer can be
// empty: the null checksum is always yields the same result.
let result = NATIVE_FS.checksum_verify(
file_path.to_str().unwrap(),
ChecksumType::NullChecksum,
0,
&mut [],
);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_checksum_not_implemented() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("mod-crc.bin");
// The file to check does not even need to exist, and the verification buffer can be
// empty: the null checksum is always yields the same result.
let result = NATIVE_FS.checksum_verify(
file_path.to_str().unwrap(),
ChecksumType::Crc32Proximity1,
0,
&mut [],
);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
assert_eq!(
error.to_string(),
format!("checksum {:?} not implemented", cksum_type)
);
} else {
panic!("unexpected error");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,644 +0,0 @@
use spacepackets::{
cfdp::{
tlv::{GenericTlv, Tlv, TlvType},
SegmentationControl, TransmissionMode,
},
util::UnsignedByteField,
};
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
#[derive(Debug, PartialEq, Eq)]
pub struct FilePathTooLarge(pub usize);
/// This trait is an abstraction for different Put Request structures which can be used
/// by Put Request consumers.
pub trait ReadablePutRequest {
fn destination_id(&self) -> UnsignedByteField;
fn source_file(&self) -> Option<&str>;
fn dest_file(&self) -> Option<&str>;
fn trans_mode(&self) -> Option<TransmissionMode>;
fn closure_requested(&self) -> Option<bool>;
fn seg_ctrl(&self) -> Option<SegmentationControl>;
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv>>;
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv>>;
fn flow_label(&self) -> Option<Tlv>;
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv>>;
}
#[derive(Debug, PartialEq, Eq)]
pub struct PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> {
pub destination_id: UnsignedByteField,
source_file: Option<&'src_file str>,
dest_file: Option<&'dest_file str>,
pub trans_mode: Option<TransmissionMode>,
pub closure_requested: Option<bool>,
pub seg_ctrl: Option<SegmentationControl>,
pub msgs_to_user: Option<&'msgs_to_user [Tlv<'msgs_to_user>]>,
pub fault_handler_overrides: Option<&'fh_ovrds [Tlv<'fh_ovrds>]>,
pub flow_label: Option<Tlv<'flow_label>>,
pub fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>,
}
impl<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests>
PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests>
{
#[allow(clippy::too_many_arguments)]
pub fn new(
destination_id: UnsignedByteField,
source_file: Option<&'src_file str>,
dest_file: Option<&'dest_file str>,
trans_mode: Option<TransmissionMode>,
closure_requested: Option<bool>,
seg_ctrl: Option<SegmentationControl>,
msgs_to_user: Option<&'msgs_to_user [Tlv<'msgs_to_user>]>,
fault_handler_overrides: Option<&'fh_ovrds [Tlv<'fh_ovrds>]>,
flow_label: Option<Tlv<'flow_label>>,
fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>,
) -> Result<Self, FilePathTooLarge> {
generic_path_checks(source_file, dest_file)?;
Ok(Self {
destination_id,
source_file,
dest_file,
trans_mode,
closure_requested,
seg_ctrl,
msgs_to_user,
fault_handler_overrides,
flow_label,
fs_requests,
})
}
}
impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> {
fn destination_id(&self) -> UnsignedByteField {
self.destination_id
}
fn source_file(&self) -> Option<&str> {
self.source_file
}
fn dest_file(&self) -> Option<&str> {
self.dest_file
}
fn trans_mode(&self) -> Option<TransmissionMode> {
self.trans_mode
}
fn closure_requested(&self) -> Option<bool> {
self.closure_requested
}
fn seg_ctrl(&self) -> Option<SegmentationControl> {
self.seg_ctrl
}
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(msgs_to_user) = self.msgs_to_user {
return Some(msgs_to_user.iter().copied());
}
None
}
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(fh_overrides) = self.fault_handler_overrides {
return Some(fh_overrides.iter().copied());
}
None
}
fn flow_label(&self) -> Option<Tlv> {
self.flow_label
}
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(fs_requests) = self.msgs_to_user {
return Some(fs_requests.iter().copied());
}
None
}
}
pub fn generic_path_checks(
source_file: Option<&str>,
dest_file: Option<&str>,
) -> Result<(), FilePathTooLarge> {
if let Some(src_file) = source_file {
if src_file.len() > u8::MAX as usize {
return Err(FilePathTooLarge(src_file.len()));
}
}
if let Some(dest_file) = dest_file {
if dest_file.len() > u8::MAX as usize {
return Err(FilePathTooLarge(dest_file.len()));
}
}
Ok(())
}
impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static, 'static, 'static> {
pub fn new_regular_request(
dest_id: UnsignedByteField,
source_file: &'src_file str,
dest_file: &'dest_file str,
trans_mode: Option<TransmissionMode>,
closure_requested: Option<bool>,
) -> Result<Self, FilePathTooLarge> {
generic_path_checks(Some(source_file), Some(dest_file))?;
Ok(Self {
destination_id: dest_id,
source_file: Some(source_file),
dest_file: Some(dest_file),
trans_mode,
closure_requested,
seg_ctrl: None,
msgs_to_user: None,
fault_handler_overrides: None,
flow_label: None,
fs_requests: None,
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct TlvWithInvalidType(pub(crate) ());
impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static, 'static> {
pub fn new_msgs_to_user_only(
dest_id: UnsignedByteField,
msgs_to_user: &'msgs_to_user [Tlv<'msgs_to_user>],
) -> Result<Self, TlvWithInvalidType> {
Ok(Self {
destination_id: dest_id,
source_file: None,
dest_file: None,
trans_mode: None,
closure_requested: None,
seg_ctrl: None,
msgs_to_user: Some(msgs_to_user),
fault_handler_overrides: None,
flow_label: None,
fs_requests: None,
})
}
/// Uses [generic_tlv_list_type_check] to check the TLV type validity of all TLV fields.
pub fn check_tlv_type_validities(&self) -> bool {
generic_tlv_list_type_check(self.msgs_to_user, TlvType::MsgToUser);
if let Some(flow_label) = &self.flow_label {
if flow_label.tlv_type().is_none() {
return false;
}
if flow_label.tlv_type().unwrap() != TlvType::FlowLabel {
return false;
}
}
generic_tlv_list_type_check(self.fault_handler_overrides, TlvType::FaultHandler);
generic_tlv_list_type_check(self.fs_requests, TlvType::FilestoreRequest);
true
}
}
pub fn generic_tlv_list_type_check<TlvProvider: GenericTlv>(
opt_tlvs: Option<&[TlvProvider]>,
tlv_type: TlvType,
) -> bool {
if let Some(tlvs) = opt_tlvs {
for tlv in tlvs {
if tlv.tlv_type().is_none() {
return false;
}
if tlv.tlv_type().unwrap() != tlv_type {
return false;
}
}
}
true
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use core::str::Utf8Error;
use super::*;
use alloc::string::ToString;
use spacepackets::{
cfdp::tlv::{msg_to_user::MsgToUserTlv, ReadableTlv, TlvOwned, WritableTlv},
ByteConversionError,
};
/// Owned variant of [PutRequest] with no lifetimes which is also [Clone]able.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PutRequestOwned {
pub destination_id: UnsignedByteField,
source_file: Option<alloc::string::String>,
dest_file: Option<alloc::string::String>,
pub trans_mode: Option<TransmissionMode>,
pub closure_requested: Option<bool>,
pub seg_ctrl: Option<SegmentationControl>,
pub msgs_to_user: Option<alloc::vec::Vec<TlvOwned>>,
pub fault_handler_overrides: Option<alloc::vec::Vec<TlvOwned>>,
pub flow_label: Option<TlvOwned>,
pub fs_requests: Option<alloc::vec::Vec<TlvOwned>>,
}
impl PutRequestOwned {
pub fn new_regular_request(
dest_id: UnsignedByteField,
source_file: &str,
dest_file: &str,
trans_mode: Option<TransmissionMode>,
closure_requested: Option<bool>,
) -> Result<Self, FilePathTooLarge> {
if source_file.len() > u8::MAX as usize {
return Err(FilePathTooLarge(source_file.len()));
}
if dest_file.len() > u8::MAX as usize {
return Err(FilePathTooLarge(dest_file.len()));
}
Ok(Self {
destination_id: dest_id,
source_file: Some(source_file.to_string()),
dest_file: Some(dest_file.to_string()),
trans_mode,
closure_requested,
seg_ctrl: None,
msgs_to_user: None,
fault_handler_overrides: None,
flow_label: None,
fs_requests: None,
})
}
pub fn new_msgs_to_user_only(
dest_id: UnsignedByteField,
msgs_to_user: &[MsgToUserTlv<'_>],
) -> Result<Self, TlvWithInvalidType> {
Ok(Self {
destination_id: dest_id,
source_file: None,
dest_file: None,
trans_mode: None,
closure_requested: None,
seg_ctrl: None,
msgs_to_user: Some(msgs_to_user.iter().map(|msg| msg.tlv.to_owned()).collect()),
fault_handler_overrides: None,
flow_label: None,
fs_requests: None,
})
}
/// Uses [generic_tlv_list_type_check] to check the TLV type validity of all TLV fields.
pub fn check_tlv_type_validities(&self) -> bool {
generic_tlv_list_type_check(self.msgs_to_user.as_deref(), TlvType::MsgToUser);
if let Some(flow_label) = &self.flow_label {
if flow_label.tlv_type().is_none() {
return false;
}
if flow_label.tlv_type().unwrap() != TlvType::FlowLabel {
return false;
}
}
generic_tlv_list_type_check(
self.fault_handler_overrides.as_deref(),
TlvType::FaultHandler,
);
generic_tlv_list_type_check(self.fs_requests.as_deref(), TlvType::FilestoreRequest);
true
}
}
impl From<PutRequest<'_, '_, '_, '_, '_, '_>> for PutRequestOwned {
fn from(req: PutRequest<'_, '_, '_, '_, '_, '_>) -> Self {
Self {
destination_id: req.destination_id,
source_file: req.source_file.map(|s| s.into()),
dest_file: req.dest_file.map(|s| s.into()),
trans_mode: req.trans_mode,
closure_requested: req.closure_requested,
seg_ctrl: req.seg_ctrl,
msgs_to_user: req
.msgs_to_user
.map(|msgs_to_user| msgs_to_user.iter().map(|msg| msg.to_owned()).collect()),
fault_handler_overrides: req
.msgs_to_user
.map(|fh_overides| fh_overides.iter().map(|msg| msg.to_owned()).collect()),
flow_label: req
.flow_label
.map(|flow_label_tlv| flow_label_tlv.to_owned()),
fs_requests: req
.fs_requests
.map(|fs_requests| fs_requests.iter().map(|msg| msg.to_owned()).collect()),
}
}
}
impl ReadablePutRequest for PutRequestOwned {
fn destination_id(&self) -> UnsignedByteField {
self.destination_id
}
fn source_file(&self) -> Option<&str> {
self.source_file.as_deref()
}
fn dest_file(&self) -> Option<&str> {
self.dest_file.as_deref()
}
fn trans_mode(&self) -> Option<TransmissionMode> {
self.trans_mode
}
fn closure_requested(&self) -> Option<bool> {
self.closure_requested
}
fn seg_ctrl(&self) -> Option<SegmentationControl> {
self.seg_ctrl
}
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(msgs_to_user) = &self.msgs_to_user {
return Some(msgs_to_user.iter().map(|tlv_owned| tlv_owned.as_tlv()));
}
None
}
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(fh_overrides) = &self.fault_handler_overrides {
return Some(fh_overrides.iter().map(|tlv_owned| tlv_owned.as_tlv()));
}
None
}
fn flow_label(&self) -> Option<Tlv> {
self.flow_label.as_ref().map(|tlv| tlv.as_tlv())
}
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(requests) = &self.fs_requests {
return Some(requests.iter().map(|tlv_owned| tlv_owned.as_tlv()));
}
None
}
}
pub struct StaticPutRequestFields {
pub destination_id: UnsignedByteField,
/// Static buffer to store source file path.
pub source_file_buf: [u8; u8::MAX as usize],
/// Current source path length.
pub source_file_len: usize,
/// Static buffer to store dest file path.
pub dest_file_buf: [u8; u8::MAX as usize],
/// Current destination path length.
pub dest_file_len: usize,
pub trans_mode: Option<TransmissionMode>,
pub closure_requested: Option<bool>,
pub seg_ctrl: Option<SegmentationControl>,
}
impl Default for StaticPutRequestFields {
fn default() -> Self {
Self {
destination_id: UnsignedByteField::new(0, 0),
source_file_buf: [0; u8::MAX as usize],
source_file_len: Default::default(),
dest_file_buf: [0; u8::MAX as usize],
dest_file_len: Default::default(),
trans_mode: Default::default(),
closure_requested: Default::default(),
seg_ctrl: Default::default(),
}
}
}
impl StaticPutRequestFields {
pub fn clear(&mut self) {
self.destination_id = UnsignedByteField::new(0, 0);
self.source_file_len = 0;
self.dest_file_len = 0;
self.trans_mode = None;
self.closure_requested = None;
self.seg_ctrl = None;
}
}
/// This is a put request cache structure which can be used to cache [ReadablePutRequest]s
/// without requiring run-time allocation. The user must specify the static buffer sizes used
/// to store TLVs or list of TLVs.
pub struct StaticPutRequestCacher {
pub static_fields: StaticPutRequestFields,
opts_buf: alloc::vec::Vec<u8>,
opts_len: usize, // fs_request_start_end_pos: Option<(usize, usize)>
}
impl StaticPutRequestCacher {
pub fn new(max_len_opts_buf: usize) -> Self {
Self {
static_fields: StaticPutRequestFields::default(),
opts_buf: alloc::vec![0; max_len_opts_buf],
opts_len: 0,
}
}
pub fn set(
&mut self,
put_request: &impl ReadablePutRequest,
) -> Result<(), ByteConversionError> {
self.static_fields.destination_id = put_request.destination_id();
if let Some(source_file) = put_request.source_file() {
if source_file.len() > u8::MAX as usize {
return Err(ByteConversionError::ToSliceTooSmall {
found: self.static_fields.source_file_buf.len(),
expected: source_file.len(),
});
}
self.static_fields.source_file_buf[..source_file.len()]
.copy_from_slice(source_file.as_bytes());
self.static_fields.source_file_len = source_file.len();
}
if let Some(dest_file) = put_request.dest_file() {
if dest_file.len() > u8::MAX as usize {
return Err(ByteConversionError::ToSliceTooSmall {
found: self.static_fields.source_file_buf.len(),
expected: dest_file.len(),
});
}
self.static_fields.dest_file_buf[..dest_file.len()]
.copy_from_slice(dest_file.as_bytes());
self.static_fields.dest_file_len = dest_file.len();
}
self.static_fields.trans_mode = put_request.trans_mode();
self.static_fields.closure_requested = put_request.closure_requested();
self.static_fields.seg_ctrl = put_request.seg_ctrl();
let mut current_idx = 0;
let mut store_tlv = |tlv: &Tlv| {
if current_idx + tlv.len_full() > self.opts_buf.len() {
return Err(ByteConversionError::ToSliceTooSmall {
found: self.opts_buf.len(),
expected: current_idx + tlv.len_full(),
});
}
// We checked the buffer lengths, so this should never fail.
tlv.write_to_bytes(&mut self.opts_buf[current_idx..current_idx + tlv.len_full()])
.unwrap();
current_idx += tlv.len_full();
Ok(())
};
if let Some(fs_req) = put_request.fs_requests() {
for fs_req in fs_req {
store_tlv(&fs_req)?;
}
}
if let Some(msgs_to_user) = put_request.msgs_to_user() {
for msg_to_user in msgs_to_user {
store_tlv(&msg_to_user)?;
}
}
self.opts_len = current_idx;
Ok(())
}
pub fn has_source_file(&self) -> bool {
self.static_fields.source_file_len > 0
}
pub fn has_dest_file(&self) -> bool {
self.static_fields.dest_file_len > 0
}
pub fn source_file(&self) -> Result<&str, Utf8Error> {
core::str::from_utf8(
&self.static_fields.source_file_buf[0..self.static_fields.source_file_len],
)
}
pub fn dest_file(&self) -> Result<&str, Utf8Error> {
core::str::from_utf8(
&self.static_fields.dest_file_buf[0..self.static_fields.dest_file_len],
)
}
pub fn opts_len(&self) -> usize {
self.opts_len
}
pub fn opts_slice(&self) -> &[u8] {
&self.opts_buf[0..self.opts_len]
}
/// This clears the cacher structure. This is a cheap operation because it only
/// sets [Option]al values to [None] and the length of stores TLVs to 0.
///
/// Please note that this method will not set the values in the buffer to 0.
pub fn clear(&mut self) {
self.static_fields.clear();
self.opts_len = 0;
}
}
}
#[cfg(test)]
mod tests {
use spacepackets::util::UbfU16;
use super::*;
pub const DEST_ID: UbfU16 = UbfU16::new(5);
#[test]
fn test_put_request_basic() {
let src_file = "/tmp/hello.txt";
let dest_file = "/tmp/hello2.txt";
let put_request =
PutRequest::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None)
.unwrap();
assert_eq!(put_request.source_file(), Some(src_file));
assert_eq!(put_request.dest_file(), Some(dest_file));
assert_eq!(put_request.destination_id(), DEST_ID.into());
assert_eq!(put_request.seg_ctrl(), None);
assert_eq!(put_request.closure_requested(), None);
assert_eq!(put_request.trans_mode(), None);
assert!(put_request.fs_requests().is_none());
assert!(put_request.msgs_to_user().is_none());
assert!(put_request.fault_handler_overrides().is_none());
assert!(put_request.flow_label().is_none());
}
#[test]
fn test_put_request_owned_basic() {
let src_file = "/tmp/hello.txt";
let dest_file = "/tmp/hello2.txt";
let put_request =
PutRequestOwned::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None)
.unwrap();
assert_eq!(put_request.source_file(), Some(src_file));
assert_eq!(put_request.dest_file(), Some(dest_file));
assert_eq!(put_request.destination_id(), DEST_ID.into());
assert_eq!(put_request.seg_ctrl(), None);
assert_eq!(put_request.closure_requested(), None);
assert_eq!(put_request.trans_mode(), None);
assert!(put_request.flow_label().is_none());
assert!(put_request.fs_requests().is_none());
assert!(put_request.msgs_to_user().is_none());
assert!(put_request.fault_handler_overrides().is_none());
assert!(put_request.flow_label().is_none());
let put_request_cloned = put_request.clone();
assert_eq!(put_request, put_request_cloned);
}
#[test]
fn test_put_request_cacher_basic() {
let put_request_cached = StaticPutRequestCacher::new(128);
assert_eq!(put_request_cached.static_fields.source_file_len, 0);
assert_eq!(put_request_cached.static_fields.dest_file_len, 0);
assert_eq!(put_request_cached.opts_len(), 0);
assert_eq!(put_request_cached.opts_slice(), &[]);
}
#[test]
fn test_put_request_cacher_set() {
let mut put_request_cached = StaticPutRequestCacher::new(128);
let src_file = "/tmp/hello.txt";
let dest_file = "/tmp/hello2.txt";
let put_request =
PutRequest::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None)
.unwrap();
put_request_cached.set(&put_request).unwrap();
assert_eq!(
put_request_cached.static_fields.source_file_len,
src_file.len()
);
assert_eq!(
put_request_cached.static_fields.dest_file_len,
dest_file.len()
);
assert_eq!(put_request_cached.source_file().unwrap(), src_file);
assert_eq!(put_request_cached.dest_file().unwrap(), dest_file);
assert_eq!(put_request_cached.opts_len(), 0);
}
#[test]
fn test_put_request_cacher_set_and_clear() {
let mut put_request_cached = StaticPutRequestCacher::new(128);
let src_file = "/tmp/hello.txt";
let dest_file = "/tmp/hello2.txt";
let put_request =
PutRequest::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None)
.unwrap();
put_request_cached.set(&put_request).unwrap();
put_request_cached.clear();
assert_eq!(put_request_cached.static_fields.source_file_len, 0);
assert_eq!(put_request_cached.static_fields.dest_file_len, 0);
assert_eq!(put_request_cached.opts_len(), 0);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,96 +0,0 @@
use spacepackets::{
cfdp::{
pdu::{
file_data::SegmentMetadata,
finished::{DeliveryCode, FileStatus},
},
tlv::{msg_to_user::MsgToUserTlv, WritableTlv},
ConditionCode,
},
util::UnsignedByteField,
};
use super::TransactionId;
#[derive(Debug, Copy, Clone)]
pub struct TransactionFinishedParams {
pub id: TransactionId,
pub condition_code: ConditionCode,
pub delivery_code: DeliveryCode,
pub file_status: FileStatus,
}
#[derive(Debug)]
pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> {
pub id: TransactionId,
pub source_id: UnsignedByteField,
pub file_size: u64,
pub src_file_name: &'src_file str,
pub dest_file_name: &'dest_file str,
pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>],
}
#[cfg(feature = "alloc")]
#[derive(Debug)]
pub struct OwnedMetadataRecvdParams {
pub id: TransactionId,
pub source_id: UnsignedByteField,
pub file_size: u64,
pub src_file_name: alloc::string::String,
pub dest_file_name: alloc::string::String,
pub msgs_to_user: alloc::vec::Vec<alloc::vec::Vec<u8>>,
}
#[cfg(feature = "alloc")]
impl From<MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
fn from(value: MetadataReceivedParams) -> Self {
Self::from(&value)
}
}
#[cfg(feature = "alloc")]
impl From<&MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
fn from(value: &MetadataReceivedParams) -> Self {
Self {
id: value.id,
source_id: value.source_id,
file_size: value.file_size,
src_file_name: value.src_file_name.into(),
dest_file_name: value.dest_file_name.into(),
msgs_to_user: value.msgs_to_user.iter().map(|tlv| tlv.to_vec()).collect(),
}
}
}
#[derive(Debug)]
pub struct FileSegmentRecvdParams<'seg_meta> {
pub id: TransactionId,
pub offset: u64,
pub length: usize,
pub segment_metadata: Option<&'seg_meta SegmentMetadata<'seg_meta>>,
}
pub trait CfdpUser {
fn transaction_indication(&mut self, id: &TransactionId);
fn eof_sent_indication(&mut self, id: &TransactionId);
fn transaction_finished_indication(&mut self, finished_params: &TransactionFinishedParams);
fn metadata_recvd_indication(&mut self, md_recvd_params: &MetadataReceivedParams);
fn file_segment_recvd_indication(&mut self, segment_recvd_params: &FileSegmentRecvdParams);
// TODO: The standard does not strictly specify how the report information looks..
fn report_indication(&mut self, id: &TransactionId);
fn suspended_indication(&mut self, id: &TransactionId, condition_code: ConditionCode);
fn resumed_indication(&mut self, id: &TransactionId, progress: u64);
fn fault_indication(
&mut self,
id: &TransactionId,
condition_code: ConditionCode,
progress: u64,
);
fn abandoned_indication(
&mut self,
id: &TransactionId,
condition_code: ConditionCode,
progress: u64,
);
fn eof_recvd_indication(&mut self, id: &TransactionId);
}

View File

@ -22,8 +22,6 @@ extern crate downcast_rs;
#[cfg(any(feature = "std", test))]
extern crate std;
#[cfg(feature = "alloc")]
pub mod cfdp;
pub mod encoding;
pub mod event_man;
pub mod events;