2023-05-19 00:42:31 +02:00
|
|
|
//! Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9.
|
2023-05-18 15:01:08 +02:00
|
|
|
use crate::cfdp::lv::{
|
|
|
|
generic_len_check_data_serialization, generic_len_check_deserialization, Lv, MIN_LV_LEN,
|
|
|
|
};
|
|
|
|
use crate::cfdp::TlvLvError;
|
2023-06-07 01:12:07 +02:00
|
|
|
use crate::util::{UnsignedByteField, UnsignedByteFieldError, UnsignedEnum};
|
|
|
|
use crate::{ByteConversionError, SizeMissmatch};
|
2023-05-18 14:05:51 +02:00
|
|
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
pub const MIN_TLV_LEN: usize = 2;
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
|
|
#[repr(u8)]
|
|
|
|
pub enum TlvType {
|
|
|
|
FilestoreRequest = 0x00,
|
|
|
|
FilestoreResponse = 0x01,
|
|
|
|
MsgToUser = 0x02,
|
|
|
|
FaultHandler = 0x04,
|
|
|
|
FlowLabel = 0x05,
|
|
|
|
EntityId = 0x06,
|
|
|
|
}
|
|
|
|
|
2023-05-21 20:30:16 +02:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
|
|
pub enum TlvTypeField {
|
|
|
|
Standard(TlvType),
|
|
|
|
Custom(u8),
|
|
|
|
}
|
|
|
|
|
2023-06-12 04:02:18 +02:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
|
|
#[repr(u8)]
|
2023-06-12 14:26:40 +02:00
|
|
|
pub enum FilestoreActionCode {
|
2023-06-12 04:02:18 +02:00
|
|
|
CreateFile = 0b0000,
|
|
|
|
DeleteFile = 0b0001,
|
|
|
|
RenameFile = 0b0010,
|
2023-06-12 14:26:40 +02:00
|
|
|
/// This operation appends one file to another. The first specified name will form the first
|
|
|
|
/// part of the new file and the name of the new file. This function can be used to get
|
|
|
|
/// similar functionality to the UNIX cat utility (albeit for only two files).
|
2023-06-12 04:02:18 +02:00
|
|
|
AppendFile = 0b0011,
|
2023-06-12 14:26:40 +02:00
|
|
|
/// This operation replaces the content of the first specified file with the content of
|
|
|
|
/// the secondly specified file.
|
2023-06-12 04:02:18 +02:00
|
|
|
ReplaceFile = 0b0100,
|
|
|
|
CreateDirectory = 0b0101,
|
|
|
|
RemoveDirectory = 0b0110,
|
|
|
|
DenyFile = 0b0111,
|
|
|
|
DenyDirectory = 0b1000,
|
|
|
|
}
|
|
|
|
|
2023-05-21 20:30:16 +02:00
|
|
|
impl From<u8> for TlvTypeField {
|
|
|
|
fn from(value: u8) -> Self {
|
|
|
|
match TlvType::try_from(value) {
|
|
|
|
Ok(tlv_type) => TlvTypeField::Standard(tlv_type),
|
|
|
|
Err(_) => TlvTypeField::Custom(value),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<TlvTypeField> for u8 {
|
|
|
|
fn from(value: TlvTypeField) -> Self {
|
|
|
|
match value {
|
|
|
|
TlvTypeField::Standard(std) => std as u8,
|
|
|
|
TlvTypeField::Custom(custom) => custom,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-18 20:37:41 +02:00
|
|
|
/// Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9.
|
2023-05-21 20:30:16 +02:00
|
|
|
///
|
|
|
|
/// # Lifetimes
|
|
|
|
/// * `data`: If the TLV is generated from a raw bytestream, this will be the lifetime of
|
|
|
|
/// the raw bytestream. If the TLV is generated from a raw slice or a similar data reference,
|
|
|
|
/// this will be the lifetime of that data reference.
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
|
|
pub struct Tlv<'data> {
|
|
|
|
tlv_type_field: TlvTypeField,
|
|
|
|
#[cfg_attr(feature = "serde", serde(borrow))]
|
|
|
|
lv: Lv<'data>,
|
2023-05-18 14:05:51 +02:00
|
|
|
}
|
|
|
|
|
2023-05-21 20:30:16 +02:00
|
|
|
impl<'data> Tlv<'data> {
|
2023-05-18 15:01:08 +02:00
|
|
|
pub fn new(tlv_type: TlvType, data: &[u8]) -> Result<Tlv, TlvLvError> {
|
|
|
|
Ok(Tlv {
|
2023-05-21 20:30:16 +02:00
|
|
|
tlv_type_field: TlvTypeField::Standard(tlv_type),
|
2023-05-18 15:01:08 +02:00
|
|
|
lv: Lv::new(data)?,
|
|
|
|
})
|
2023-05-18 14:05:51 +02:00
|
|
|
}
|
|
|
|
|
2023-05-18 20:37:41 +02:00
|
|
|
/// Creates a TLV with an empty value field.
|
2023-05-21 20:30:16 +02:00
|
|
|
pub fn new_empty(tlv_type: TlvType) -> Tlv<'data> {
|
2023-05-18 20:37:41 +02:00
|
|
|
Tlv {
|
2023-05-21 20:30:16 +02:00
|
|
|
tlv_type_field: TlvTypeField::Standard(tlv_type),
|
2023-05-18 20:37:41 +02:00
|
|
|
lv: Lv::new_empty(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-21 20:30:16 +02:00
|
|
|
pub fn tlv_type_field(&self) -> TlvTypeField {
|
|
|
|
self.tlv_type_field
|
|
|
|
}
|
|
|
|
|
2023-06-07 01:12:07 +02:00
|
|
|
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
2023-05-18 20:37:41 +02:00
|
|
|
generic_len_check_data_serialization(buf, self.len_value(), MIN_TLV_LEN)?;
|
2023-05-21 20:30:16 +02:00
|
|
|
buf[0] = self.tlv_type_field.into();
|
2023-05-18 15:01:08 +02:00
|
|
|
self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]);
|
2023-05-18 20:37:41 +02:00
|
|
|
Ok(self.len_full())
|
2023-05-18 14:05:51 +02:00
|
|
|
}
|
|
|
|
|
2023-05-18 20:37:41 +02:00
|
|
|
pub fn value(&self) -> Option<&[u8]> {
|
2023-05-18 15:01:08 +02:00
|
|
|
self.lv.value()
|
|
|
|
}
|
|
|
|
|
2023-05-18 20:37:41 +02:00
|
|
|
/// Returns the length of the value part, not including the length byte.
|
|
|
|
pub fn len_value(&self) -> usize {
|
2023-05-21 20:30:16 +02:00
|
|
|
self.lv.len_value()
|
2023-05-18 20:37:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the full raw length, including the length byte.
|
|
|
|
pub fn len_full(&self) -> usize {
|
2023-05-21 20:30:16 +02:00
|
|
|
self.lv.len_full() + 1
|
2023-05-18 20:37:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Checks whether the value field is empty.
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.lv.is_empty()
|
|
|
|
}
|
|
|
|
|
2023-05-21 20:30:16 +02:00
|
|
|
/// Creates a TLV give a raw bytestream. Please note that is is not necessary to pass the
|
|
|
|
/// bytestream with the exact size of the expected TLV. This function will take care
|
|
|
|
/// of parsing the length byte, and the length of the parsed TLV can be retrieved using
|
2023-06-12 04:13:41 +02:00
|
|
|
/// [Self::len_full].
|
2023-05-29 23:38:07 +02:00
|
|
|
pub fn from_bytes(buf: &'data [u8]) -> Result<Tlv<'data>, TlvLvError> {
|
2023-05-18 15:01:08 +02:00
|
|
|
generic_len_check_deserialization(buf, MIN_TLV_LEN)?;
|
2023-05-18 14:05:51 +02:00
|
|
|
Ok(Self {
|
2023-05-21 20:30:16 +02:00
|
|
|
tlv_type_field: TlvTypeField::from(buf[0]),
|
2023-05-29 23:38:07 +02:00
|
|
|
lv: Lv::from_bytes(&buf[MIN_LV_LEN..])?,
|
2023-05-18 14:05:51 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-05-19 00:42:31 +02:00
|
|
|
|
2023-06-12 14:26:40 +02:00
|
|
|
pub(crate) fn verify_tlv_type(raw_type: u8, expected_tlv_type: TlvType) -> Result<(), TlvLvError> {
|
|
|
|
let tlv_type = TlvType::try_from(raw_type)
|
|
|
|
.map_err(|_| TlvLvError::InvalidTlvTypeField((raw_type, Some(expected_tlv_type as u8))))?;
|
|
|
|
if tlv_type != expected_tlv_type {
|
|
|
|
return Err(TlvLvError::InvalidTlvTypeField((
|
|
|
|
tlv_type as u8,
|
|
|
|
Some(expected_tlv_type as u8),
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-06-07 01:12:07 +02:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
|
|
pub struct EntityIdTlv {
|
|
|
|
entity_id: UnsignedByteField,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EntityIdTlv {
|
|
|
|
pub fn new(entity_id: UnsignedByteField) -> Self {
|
|
|
|
Self { entity_id }
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:50:18 +02:00
|
|
|
fn len_check(buf: &[u8]) -> Result<(), ByteConversionError> {
|
2023-06-07 01:12:07 +02:00
|
|
|
if buf.len() < 2 {
|
|
|
|
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
|
|
|
found: buf.len(),
|
|
|
|
expected: 2,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:50:18 +02:00
|
|
|
pub fn len_value(&self) -> usize {
|
|
|
|
self.entity_id.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn len_full(&self) -> usize {
|
|
|
|
2 + self.entity_id.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
2023-06-07 01:12:07 +02:00
|
|
|
Self::len_check(buf)?;
|
|
|
|
buf[0] = TlvType::EntityId as u8;
|
|
|
|
buf[1] = self.entity_id.len() as u8;
|
|
|
|
self.entity_id.write_to_be_bytes(&mut buf[2..])
|
|
|
|
}
|
|
|
|
|
2023-06-08 20:50:18 +02:00
|
|
|
pub fn from_bytes(buf: &[u8]) -> Result<Self, TlvLvError> {
|
|
|
|
Self::len_check(buf)?;
|
2023-06-12 14:26:40 +02:00
|
|
|
verify_tlv_type(buf[0], TlvType::EntityId)?;
|
2023-06-08 20:50:18 +02:00
|
|
|
let len = buf[1];
|
|
|
|
if len != 1 && len != 2 && len != 4 && len != 8 {
|
2023-06-12 14:26:40 +02:00
|
|
|
return Err(TlvLvError::InvalidValueLength(len as usize));
|
2023-06-08 20:50:18 +02:00
|
|
|
}
|
|
|
|
// Okay to unwrap here. The checks before make sure that the deserialization never fails
|
|
|
|
let entity_id = UnsignedByteField::new_from_be_bytes(len as usize, &buf[2..]).unwrap();
|
|
|
|
Ok(Self { entity_id })
|
|
|
|
}
|
|
|
|
|
2023-06-12 14:26:40 +02:00
|
|
|
/// Convert to a generic [Tlv], which also erases the programmatic type information.
|
2023-06-07 01:12:07 +02:00
|
|
|
pub fn to_tlv(self, buf: &mut [u8]) -> Result<Tlv, ByteConversionError> {
|
|
|
|
Self::len_check(buf)?;
|
|
|
|
self.entity_id
|
|
|
|
.write_to_be_bytes(&mut buf[2..2 + self.entity_id.len()])?;
|
|
|
|
Tlv::new(TlvType::EntityId, &buf[2..2 + self.entity_id.len()]).map_err(|e| match e {
|
|
|
|
TlvLvError::ByteConversionError(e) => e,
|
|
|
|
// All other errors are impossible.
|
|
|
|
_ => panic!("unexpected TLV error"),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'data> TryFrom<Tlv<'data>> for EntityIdTlv {
|
|
|
|
type Error = TlvLvError;
|
|
|
|
|
|
|
|
fn try_from(value: Tlv) -> Result<Self, Self::Error> {
|
|
|
|
match value.tlv_type_field {
|
|
|
|
TlvTypeField::Standard(tlv_type) => {
|
|
|
|
if tlv_type != TlvType::EntityId {
|
|
|
|
return Err(TlvLvError::InvalidTlvTypeField((
|
|
|
|
tlv_type as u8,
|
2023-06-12 03:57:38 +02:00
|
|
|
Some(TlvType::EntityId as u8),
|
2023-06-07 01:12:07 +02:00
|
|
|
)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TlvTypeField::Custom(val) => {
|
|
|
|
return Err(TlvLvError::InvalidTlvTypeField((
|
|
|
|
val,
|
2023-06-12 03:57:38 +02:00
|
|
|
Some(TlvType::EntityId as u8),
|
2023-06-07 01:12:07 +02:00
|
|
|
)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if value.len_value() != 1
|
|
|
|
&& value.len_value() != 2
|
|
|
|
&& value.len_value() != 4
|
|
|
|
&& value.len_value() != 8
|
|
|
|
{
|
2023-06-12 14:26:40 +02:00
|
|
|
return Err(TlvLvError::InvalidValueLength(value.len_value()));
|
2023-06-07 01:12:07 +02:00
|
|
|
}
|
|
|
|
Ok(Self::new(
|
|
|
|
UnsignedByteField::new_from_be_bytes(value.len_value(), value.value().unwrap())
|
|
|
|
.map_err(|e| match e {
|
|
|
|
UnsignedByteFieldError::ByteConversionError(e) => e,
|
|
|
|
// This can not happen, we checked for the length validity, and the data is always smaller than
|
|
|
|
// 255 bytes.
|
|
|
|
_ => panic!("unexpected error"),
|
|
|
|
})?,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-12 14:26:40 +02:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
|
|
pub struct FilestoreRequestTlv<'first_name, 'second_name> {
|
|
|
|
action_code: FilestoreActionCode,
|
|
|
|
#[cfg_attr(feature = "serde", serde(borrow))]
|
|
|
|
first_name: Lv<'first_name>,
|
|
|
|
#[cfg_attr(feature = "serde", serde(borrow))]
|
|
|
|
second_name: Option<Lv<'second_name>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> {
|
|
|
|
pub fn new_create_file(first_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
|
|
|
|
Self::new(FilestoreActionCode::CreateFile, first_name, None)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_delete_file(first_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
|
|
|
|
Self::new(FilestoreActionCode::DeleteFile, first_name, None)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_rename_file(
|
|
|
|
source_name: Lv<'first_name>,
|
|
|
|
target_name: Lv<'second_name>,
|
|
|
|
) -> Result<Self, TlvLvError> {
|
|
|
|
Self::new(
|
|
|
|
FilestoreActionCode::RenameFile,
|
|
|
|
source_name,
|
|
|
|
Some(target_name),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This operation appends one file to another. The first specified name will form the first
|
|
|
|
/// part of the new file and the name of the new file. This function can be used to get
|
|
|
|
/// similar functionality to the UNIX cat utility (albeit for only two files).
|
|
|
|
pub fn new_append_file(
|
|
|
|
first_file: Lv<'first_name>,
|
|
|
|
second_file: Lv<'second_name>,
|
|
|
|
) -> Result<Self, TlvLvError> {
|
|
|
|
Self::new(
|
|
|
|
FilestoreActionCode::AppendFile,
|
|
|
|
first_file,
|
|
|
|
Some(second_file),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This operation replaces the content of the first specified file with the content of
|
|
|
|
/// the secondly specified file. This function can be used to get similar functionality to
|
|
|
|
/// the UNIX copy (cp) utility if the target file already exists.
|
|
|
|
pub fn new_replace_file(
|
|
|
|
replaced_file: Lv<'first_name>,
|
|
|
|
new_file: Lv<'second_name>,
|
|
|
|
) -> Result<Self, TlvLvError> {
|
|
|
|
Self::new(
|
|
|
|
FilestoreActionCode::ReplaceFile,
|
|
|
|
replaced_file,
|
|
|
|
Some(new_file),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_create_directory(dir_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
|
|
|
|
Self::new(FilestoreActionCode::CreateDirectory, dir_name, None)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_remove_directory(dir_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
|
|
|
|
Self::new(FilestoreActionCode::RemoveDirectory, dir_name, None)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_deny_file(file_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
|
|
|
|
Self::new(FilestoreActionCode::DenyFile, file_name, None)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_deny_directory(dir_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
|
2023-06-12 16:19:20 +02:00
|
|
|
Self::new(FilestoreActionCode::DenyDirectory, dir_name, None)
|
2023-06-12 14:26:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// This function will return [None] if the respective action code requires two names but
|
|
|
|
/// only one is passed. It will also returns [None] if the cumulative length of the first
|
|
|
|
/// name and the second name exceeds 255 bytes.
|
|
|
|
///
|
|
|
|
/// This is the case for the rename, append and replace filestore request.
|
|
|
|
pub fn new(
|
|
|
|
action_code: FilestoreActionCode,
|
|
|
|
first_name: Lv<'first_name>,
|
|
|
|
second_name: Option<Lv<'second_name>>,
|
|
|
|
) -> Result<Self, TlvLvError> {
|
|
|
|
let mut base_value_len = first_name.len_full();
|
|
|
|
if Self::has_second_filename(action_code) {
|
|
|
|
if second_name.is_none() {
|
|
|
|
return Err(TlvLvError::SecondNameMissing);
|
|
|
|
}
|
|
|
|
base_value_len += second_name.as_ref().unwrap().len_full();
|
|
|
|
}
|
|
|
|
if base_value_len > u8::MAX as usize {
|
|
|
|
return Err(TlvLvError::InvalidValueLength(base_value_len));
|
|
|
|
}
|
|
|
|
Ok(Self {
|
|
|
|
action_code,
|
|
|
|
first_name,
|
|
|
|
second_name,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn has_second_filename(action_code: FilestoreActionCode) -> bool {
|
|
|
|
if action_code == FilestoreActionCode::RenameFile
|
|
|
|
|| action_code == FilestoreActionCode::AppendFile
|
|
|
|
|| action_code == FilestoreActionCode::ReplaceFile
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2023-06-12 16:04:32 +02:00
|
|
|
pub fn action_code(&self) -> FilestoreActionCode {
|
|
|
|
self.action_code
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn first_name(&self) -> Lv<'first_name> {
|
|
|
|
self.first_name
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn second_name(&self) -> Option<Lv<'second_name>> {
|
|
|
|
self.second_name
|
|
|
|
}
|
|
|
|
|
2023-06-12 14:26:40 +02:00
|
|
|
pub fn len_value(&self) -> usize {
|
|
|
|
let mut len = 1 + self.first_name.len_full();
|
|
|
|
if let Some(second_name) = self.second_name {
|
|
|
|
len += second_name.len_full();
|
|
|
|
}
|
|
|
|
len
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn len_full(&self) -> usize {
|
|
|
|
2 + self.len_value()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
|
|
|
if buf.len() < self.len_full() {
|
|
|
|
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
|
|
|
found: buf.len(),
|
|
|
|
expected: self.len_full(),
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
buf[0] = TlvType::FilestoreRequest as u8;
|
|
|
|
buf[1] = self.len_value() as u8;
|
|
|
|
buf[2] = (self.action_code as u8) << 4;
|
2023-07-02 17:18:33 +02:00
|
|
|
let mut current_idx = 3;
|
2023-06-12 14:26:40 +02:00
|
|
|
// Length checks were already performed.
|
|
|
|
self.first_name.write_to_be_bytes_no_len_check(
|
|
|
|
&mut buf[current_idx..current_idx + self.first_name.len_full()],
|
|
|
|
);
|
|
|
|
current_idx += self.first_name.len_full();
|
|
|
|
if let Some(second_name) = self.second_name {
|
|
|
|
second_name.write_to_be_bytes_no_len_check(
|
|
|
|
&mut buf[current_idx..current_idx + second_name.len_full()],
|
|
|
|
);
|
|
|
|
current_idx += second_name.len_full();
|
|
|
|
}
|
|
|
|
Ok(current_idx)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_bytes<'longest: 'first_name + 'second_name>(
|
|
|
|
buf: &'longest [u8],
|
|
|
|
) -> Result<Self, TlvLvError> {
|
|
|
|
if buf.len() < 2 {
|
|
|
|
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
|
|
|
found: buf.len(),
|
|
|
|
expected: 2,
|
|
|
|
})
|
|
|
|
.into());
|
|
|
|
}
|
|
|
|
verify_tlv_type(buf[0], TlvType::FilestoreRequest)?;
|
|
|
|
let len = buf[1] as usize;
|
|
|
|
let mut current_idx = 2;
|
|
|
|
let action_code = FilestoreActionCode::try_from((buf[2] >> 4) & 0b1111)
|
|
|
|
.map_err(|_| TlvLvError::InvalidFilestoreActionCode((buf[2] >> 4) & 0b1111))?;
|
2023-07-02 17:18:33 +02:00
|
|
|
current_idx += 1;
|
2023-06-12 14:26:40 +02:00
|
|
|
let first_name = Lv::from_bytes(&buf[current_idx..])?;
|
|
|
|
let mut second_name = None;
|
|
|
|
|
|
|
|
current_idx += first_name.len_full();
|
|
|
|
if Self::has_second_filename(action_code) {
|
|
|
|
if current_idx >= 2 + len {
|
|
|
|
return Err(TlvLvError::SecondNameMissing);
|
|
|
|
}
|
|
|
|
second_name = Some(Lv::from_bytes(&buf[current_idx..])?);
|
|
|
|
}
|
|
|
|
Ok(Self {
|
|
|
|
action_code,
|
|
|
|
first_name,
|
|
|
|
second_name,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-19 00:42:31 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2023-06-12 15:52:10 +02:00
|
|
|
use crate::cfdp::lv::Lv;
|
2023-06-12 16:04:32 +02:00
|
|
|
use crate::cfdp::tlv::{FilestoreActionCode, FilestoreRequestTlv, Tlv, TlvType, TlvTypeField};
|
2023-05-21 20:30:16 +02:00
|
|
|
use crate::cfdp::TlvLvError;
|
|
|
|
use crate::util::{UbfU8, UnsignedEnum};
|
|
|
|
|
2023-07-02 17:18:33 +02:00
|
|
|
const TLV_TEST_STR_0: &'static str = "hello.txt";
|
|
|
|
const TLV_TEST_STR_1: &'static str = "hello2.txt";
|
|
|
|
|
2023-05-21 20:30:16 +02:00
|
|
|
#[test]
|
|
|
|
fn test_basic() {
|
|
|
|
let entity_id = UbfU8::new(5);
|
|
|
|
let mut buf: [u8; 4] = [0; 4];
|
|
|
|
assert!(entity_id.write_to_be_bytes(&mut buf).is_ok());
|
|
|
|
let tlv_res = Tlv::new(TlvType::EntityId, &buf[0..1]);
|
|
|
|
assert!(tlv_res.is_ok());
|
|
|
|
let tlv_res = tlv_res.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
tlv_res.tlv_type_field(),
|
|
|
|
TlvTypeField::Standard(TlvType::EntityId)
|
|
|
|
);
|
|
|
|
assert_eq!(tlv_res.len_full(), 3);
|
|
|
|
assert_eq!(tlv_res.len_value(), 1);
|
|
|
|
assert!(!tlv_res.is_empty());
|
|
|
|
assert!(tlv_res.value().is_some());
|
|
|
|
assert_eq!(tlv_res.value().unwrap()[0], 5);
|
|
|
|
}
|
|
|
|
|
2023-05-19 00:42:31 +02:00
|
|
|
#[test]
|
2023-05-21 20:30:16 +02:00
|
|
|
fn test_serialization() {
|
|
|
|
let entity_id = UbfU8::new(5);
|
|
|
|
let mut buf: [u8; 4] = [0; 4];
|
|
|
|
assert!(entity_id.write_to_be_bytes(&mut buf).is_ok());
|
|
|
|
let tlv_res = Tlv::new(TlvType::EntityId, &buf[0..1]);
|
|
|
|
assert!(tlv_res.is_ok());
|
|
|
|
let tlv_res = tlv_res.unwrap();
|
|
|
|
let mut ser_buf: [u8; 4] = [0; 4];
|
2023-06-07 01:12:07 +02:00
|
|
|
assert!(tlv_res.write_to_bytes(&mut ser_buf).is_ok());
|
2023-05-21 20:30:16 +02:00
|
|
|
assert_eq!(ser_buf[0], TlvType::EntityId as u8);
|
|
|
|
assert_eq!(ser_buf[1], 1);
|
|
|
|
assert_eq!(ser_buf[2], 5);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_deserialization() {
|
|
|
|
let entity_id = UbfU8::new(5);
|
|
|
|
let mut buf: [u8; 4] = [0; 4];
|
|
|
|
assert!(entity_id.write_to_be_bytes(&mut buf[2..]).is_ok());
|
|
|
|
buf[0] = TlvType::EntityId as u8;
|
|
|
|
buf[1] = 1;
|
2023-05-29 23:38:07 +02:00
|
|
|
let tlv_from_raw = Tlv::from_bytes(&mut buf);
|
2023-05-21 20:30:16 +02:00
|
|
|
assert!(tlv_from_raw.is_ok());
|
|
|
|
let tlv_from_raw = tlv_from_raw.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
tlv_from_raw.tlv_type_field(),
|
|
|
|
TlvTypeField::Standard(TlvType::EntityId)
|
|
|
|
);
|
|
|
|
assert_eq!(tlv_from_raw.len_value(), 1);
|
|
|
|
assert_eq!(tlv_from_raw.len_full(), 3);
|
|
|
|
assert!(tlv_from_raw.value().is_some());
|
|
|
|
assert_eq!(tlv_from_raw.value().unwrap()[0], 5);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_empty() {
|
|
|
|
let tlv_empty = Tlv::new_empty(TlvType::MsgToUser);
|
|
|
|
assert!(tlv_empty.value().is_none());
|
|
|
|
assert!(tlv_empty.is_empty());
|
|
|
|
assert_eq!(tlv_empty.len_full(), 2);
|
|
|
|
assert_eq!(tlv_empty.len_value(), 0);
|
|
|
|
assert_eq!(
|
|
|
|
tlv_empty.tlv_type_field(),
|
|
|
|
TlvTypeField::Standard(TlvType::MsgToUser)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_empty_serialization() {
|
|
|
|
let tlv_empty = Tlv::new_empty(TlvType::MsgToUser);
|
|
|
|
let mut buf: [u8; 4] = [0; 4];
|
2023-06-07 01:12:07 +02:00
|
|
|
assert!(tlv_empty.write_to_bytes(&mut buf).is_ok());
|
2023-05-21 20:30:16 +02:00
|
|
|
assert_eq!(buf[0], TlvType::MsgToUser as u8);
|
|
|
|
assert_eq!(buf[1], 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_empty_deserialization() {
|
|
|
|
let mut buf: [u8; 4] = [0; 4];
|
|
|
|
buf[0] = TlvType::MsgToUser as u8;
|
|
|
|
buf[1] = 0;
|
2023-05-29 23:38:07 +02:00
|
|
|
let tlv_empty = Tlv::from_bytes(&mut buf);
|
2023-05-21 20:30:16 +02:00
|
|
|
assert!(tlv_empty.is_ok());
|
|
|
|
let tlv_empty = tlv_empty.unwrap();
|
|
|
|
assert!(tlv_empty.is_empty());
|
|
|
|
assert!(tlv_empty.value().is_none());
|
|
|
|
assert_eq!(
|
|
|
|
tlv_empty.tlv_type_field(),
|
|
|
|
TlvTypeField::Standard(TlvType::MsgToUser)
|
|
|
|
);
|
|
|
|
assert_eq!(tlv_empty.len_full(), 2);
|
|
|
|
assert_eq!(tlv_empty.len_value(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_buf_too_large() {
|
|
|
|
let buf_too_large: [u8; u8::MAX as usize + 1] = [0; u8::MAX as usize + 1];
|
|
|
|
let tlv_res = Tlv::new(TlvType::MsgToUser, &buf_too_large);
|
|
|
|
assert!(tlv_res.is_err());
|
|
|
|
let error = tlv_res.unwrap_err();
|
|
|
|
if let TlvLvError::DataTooLarge(size) = error {
|
|
|
|
assert_eq!(size, u8::MAX as usize + 1);
|
|
|
|
} else {
|
|
|
|
panic!("unexpected error {:?}", error);
|
|
|
|
}
|
|
|
|
}
|
2023-05-29 01:29:04 +02:00
|
|
|
|
2023-05-21 20:30:16 +02:00
|
|
|
#[test]
|
|
|
|
fn test_deserialization_custom_tlv_type() {
|
|
|
|
let mut buf: [u8; 4] = [0; 4];
|
|
|
|
buf[0] = 3;
|
|
|
|
buf[1] = 1;
|
|
|
|
buf[2] = 5;
|
2023-05-29 23:38:07 +02:00
|
|
|
let tlv = Tlv::from_bytes(&mut buf);
|
2023-05-21 20:30:16 +02:00
|
|
|
assert!(tlv.is_ok());
|
|
|
|
let tlv = tlv.unwrap();
|
|
|
|
assert_eq!(tlv.tlv_type_field(), TlvTypeField::Custom(3));
|
|
|
|
assert_eq!(tlv.len_value(), 1);
|
|
|
|
assert_eq!(tlv.len_full(), 3);
|
|
|
|
}
|
2023-06-12 15:52:10 +02:00
|
|
|
|
2023-07-02 17:18:33 +02:00
|
|
|
fn generic_fs_request_test_one_file(
|
|
|
|
action_code: FilestoreActionCode,
|
|
|
|
) -> FilestoreRequestTlv<'static, 'static> {
|
2023-06-12 16:19:20 +02:00
|
|
|
assert!(!FilestoreRequestTlv::has_second_filename(action_code));
|
2023-07-02 17:18:33 +02:00
|
|
|
let first_name = Lv::new_from_str(TLV_TEST_STR_0).unwrap();
|
2023-06-12 16:19:20 +02:00
|
|
|
let fs_request = match action_code {
|
|
|
|
FilestoreActionCode::CreateFile => FilestoreRequestTlv::new_create_file(first_name),
|
|
|
|
FilestoreActionCode::DeleteFile => FilestoreRequestTlv::new_delete_file(first_name),
|
|
|
|
FilestoreActionCode::CreateDirectory => {
|
|
|
|
FilestoreRequestTlv::new_create_directory(first_name)
|
|
|
|
}
|
|
|
|
FilestoreActionCode::RemoveDirectory => {
|
|
|
|
FilestoreRequestTlv::new_remove_directory(first_name)
|
|
|
|
}
|
|
|
|
FilestoreActionCode::DenyFile => FilestoreRequestTlv::new_deny_file(first_name),
|
|
|
|
FilestoreActionCode::DenyDirectory => {
|
|
|
|
FilestoreRequestTlv::new_deny_directory(first_name)
|
|
|
|
}
|
|
|
|
_ => panic!("invalid action code"),
|
|
|
|
};
|
2023-06-12 15:52:10 +02:00
|
|
|
assert!(fs_request.is_ok());
|
|
|
|
let fs_request = fs_request.unwrap();
|
2023-06-12 16:19:20 +02:00
|
|
|
assert_eq!(fs_request.len_value(), 1 + first_name.len_full());
|
|
|
|
assert_eq!(fs_request.len_full(), fs_request.len_value() + 2);
|
|
|
|
assert_eq!(fs_request.action_code(), action_code);
|
2023-06-12 16:04:32 +02:00
|
|
|
assert_eq!(fs_request.first_name(), first_name);
|
|
|
|
assert_eq!(fs_request.second_name(), None);
|
2023-07-02 17:18:33 +02:00
|
|
|
fs_request
|
2023-06-12 15:52:10 +02:00
|
|
|
}
|
2023-06-12 16:19:20 +02:00
|
|
|
|
2023-07-02 17:18:33 +02:00
|
|
|
fn generic_fs_request_test_two_files(
|
|
|
|
action_code: FilestoreActionCode,
|
|
|
|
) -> FilestoreRequestTlv<'static, 'static> {
|
2023-06-12 16:19:20 +02:00
|
|
|
assert!(FilestoreRequestTlv::has_second_filename(action_code));
|
2023-07-02 17:18:33 +02:00
|
|
|
let first_name = Lv::new_from_str(TLV_TEST_STR_0).unwrap();
|
|
|
|
let second_name = Lv::new_from_str(TLV_TEST_STR_1).unwrap();
|
2023-06-12 16:19:20 +02:00
|
|
|
let fs_request = match action_code {
|
|
|
|
FilestoreActionCode::ReplaceFile => {
|
|
|
|
FilestoreRequestTlv::new_replace_file(first_name, second_name)
|
|
|
|
}
|
|
|
|
FilestoreActionCode::AppendFile => {
|
|
|
|
FilestoreRequestTlv::new_append_file(first_name, second_name)
|
|
|
|
}
|
|
|
|
FilestoreActionCode::RenameFile => {
|
|
|
|
FilestoreRequestTlv::new_rename_file(first_name, second_name)
|
|
|
|
}
|
|
|
|
_ => panic!("invalid action code"),
|
|
|
|
};
|
|
|
|
assert!(fs_request.is_ok());
|
|
|
|
let fs_request = fs_request.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
fs_request.len_value(),
|
|
|
|
1 + first_name.len_full() + second_name.len_full()
|
|
|
|
);
|
|
|
|
assert_eq!(fs_request.len_full(), fs_request.len_value() + 2);
|
|
|
|
assert_eq!(fs_request.action_code(), action_code);
|
|
|
|
assert_eq!(fs_request.first_name(), first_name);
|
|
|
|
assert!(fs_request.second_name().is_some());
|
|
|
|
assert_eq!(fs_request.second_name().unwrap(), second_name);
|
2023-07-02 17:18:33 +02:00
|
|
|
fs_request
|
2023-06-12 16:19:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_fs_request_basic_create_file() {
|
|
|
|
generic_fs_request_test_one_file(FilestoreActionCode::CreateFile);
|
|
|
|
}
|
2023-07-02 17:18:33 +02:00
|
|
|
|
2023-06-12 16:19:20 +02:00
|
|
|
#[test]
|
|
|
|
fn test_fs_request_basic_delete() {
|
|
|
|
generic_fs_request_test_one_file(FilestoreActionCode::DeleteFile);
|
|
|
|
}
|
2023-07-02 17:18:33 +02:00
|
|
|
|
2023-06-12 16:19:20 +02:00
|
|
|
#[test]
|
|
|
|
fn test_fs_request_basic_create_dir() {
|
|
|
|
generic_fs_request_test_one_file(FilestoreActionCode::CreateDirectory);
|
|
|
|
}
|
2023-07-02 17:18:33 +02:00
|
|
|
|
2023-06-12 16:19:20 +02:00
|
|
|
#[test]
|
|
|
|
fn test_fs_request_basic_remove_dir() {
|
|
|
|
generic_fs_request_test_one_file(FilestoreActionCode::RemoveDirectory);
|
|
|
|
}
|
2023-07-02 17:18:33 +02:00
|
|
|
|
2023-06-12 16:19:20 +02:00
|
|
|
#[test]
|
|
|
|
fn test_fs_request_basic_deny_file() {
|
|
|
|
generic_fs_request_test_one_file(FilestoreActionCode::DenyFile);
|
|
|
|
}
|
2023-07-02 17:18:33 +02:00
|
|
|
|
2023-06-12 16:19:20 +02:00
|
|
|
#[test]
|
|
|
|
fn test_fs_request_basic_deny_dir() {
|
|
|
|
generic_fs_request_test_one_file(FilestoreActionCode::DenyDirectory);
|
|
|
|
}
|
2023-07-02 17:18:33 +02:00
|
|
|
|
2023-06-12 16:19:20 +02:00
|
|
|
#[test]
|
|
|
|
fn test_fs_request_basic_append_file() {
|
|
|
|
generic_fs_request_test_two_files(FilestoreActionCode::AppendFile);
|
|
|
|
}
|
2023-07-02 17:18:33 +02:00
|
|
|
|
2023-06-12 16:19:20 +02:00
|
|
|
#[test]
|
|
|
|
fn test_fs_request_basic_rename_file() {
|
|
|
|
generic_fs_request_test_two_files(FilestoreActionCode::RenameFile);
|
|
|
|
}
|
2023-07-02 17:18:33 +02:00
|
|
|
|
2023-06-12 16:19:20 +02:00
|
|
|
#[test]
|
|
|
|
fn test_fs_request_basic_replace_file() {
|
|
|
|
generic_fs_request_test_two_files(FilestoreActionCode::ReplaceFile);
|
|
|
|
}
|
2023-07-02 17:18:33 +02:00
|
|
|
|
|
|
|
fn check_fs_request_first_part(
|
|
|
|
buf: &[u8],
|
|
|
|
action_code: FilestoreActionCode,
|
|
|
|
expected_val_len: u8,
|
|
|
|
) -> usize {
|
|
|
|
assert_eq!(buf[0], TlvType::FilestoreRequest as u8);
|
|
|
|
assert_eq!(buf[1], expected_val_len);
|
|
|
|
assert_eq!((buf[2] >> 4) & 0b1111, action_code as u8);
|
|
|
|
let lv = Lv::from_bytes(&buf[3..]);
|
|
|
|
assert!(lv.is_ok());
|
|
|
|
let lv = lv.unwrap();
|
|
|
|
assert_eq!(lv.value_as_str().unwrap().unwrap(), TLV_TEST_STR_0);
|
|
|
|
3 + lv.len_full()
|
|
|
|
}
|
|
|
|
|
2023-06-12 16:19:20 +02:00
|
|
|
#[test]
|
2023-07-02 17:18:33 +02:00
|
|
|
fn test_fs_request_serialization_one_file() {
|
|
|
|
let req = generic_fs_request_test_one_file(FilestoreActionCode::CreateFile);
|
|
|
|
let mut buf: [u8; 64] = [0; 64];
|
|
|
|
let res = req.write_to_bytes(&mut buf);
|
|
|
|
assert!(res.is_ok());
|
|
|
|
let written = res.unwrap();
|
|
|
|
assert_eq!(written, 3 + 1 + TLV_TEST_STR_0.len());
|
|
|
|
assert_eq!(written, req.len_full());
|
|
|
|
check_fs_request_first_part(
|
|
|
|
&buf,
|
|
|
|
FilestoreActionCode::CreateFile,
|
|
|
|
1 + 1 + TLV_TEST_STR_0.len() as u8,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_fs_request_deserialization_one_file() {
|
|
|
|
let req = generic_fs_request_test_one_file(FilestoreActionCode::CreateFile);
|
|
|
|
let mut buf: [u8; 64] = [0; 64];
|
|
|
|
let res = req.write_to_bytes(&mut buf);
|
|
|
|
assert!(res.is_ok());
|
|
|
|
let req_conv_back = FilestoreRequestTlv::from_bytes(&buf);
|
|
|
|
assert!(req_conv_back.is_ok());
|
|
|
|
let req_conv_back = req_conv_back.unwrap();
|
|
|
|
assert_eq!(req_conv_back, req);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_fs_request_serialization_two_files() {
|
|
|
|
let req = generic_fs_request_test_two_files(FilestoreActionCode::RenameFile);
|
|
|
|
let mut buf: [u8; 64] = [0; 64];
|
|
|
|
let res = req.write_to_bytes(&mut buf);
|
|
|
|
assert!(res.is_ok());
|
|
|
|
let written = res.unwrap();
|
|
|
|
assert_eq!(written, req.len_full());
|
|
|
|
assert_eq!(
|
|
|
|
written,
|
|
|
|
3 + 1 + TLV_TEST_STR_0.len() + 1 + TLV_TEST_STR_1.len()
|
|
|
|
);
|
|
|
|
let current_idx = check_fs_request_first_part(
|
|
|
|
&buf,
|
|
|
|
FilestoreActionCode::RenameFile,
|
|
|
|
1 + 1 + TLV_TEST_STR_0.len() as u8 + 1 + TLV_TEST_STR_1.len() as u8,
|
|
|
|
);
|
|
|
|
let second_lv = Lv::from_bytes(&buf[current_idx..]);
|
|
|
|
assert!(second_lv.is_ok());
|
|
|
|
let second_lv = second_lv.unwrap();
|
|
|
|
assert_eq!(second_lv.value_as_str().unwrap().unwrap(), TLV_TEST_STR_1);
|
|
|
|
assert_eq!(current_idx + second_lv.len_full(), req.len_full());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_fs_request_deserialization_two_files() {
|
|
|
|
let req = generic_fs_request_test_two_files(FilestoreActionCode::RenameFile);
|
|
|
|
let mut buf: [u8; 64] = [0; 64];
|
|
|
|
req.write_to_bytes(&mut buf).unwrap();
|
|
|
|
let req_conv_back = FilestoreRequestTlv::from_bytes(&buf);
|
|
|
|
assert!(req_conv_back.is_ok());
|
|
|
|
let req_conv_back = req_conv_back.unwrap();
|
|
|
|
assert_eq!(req_conv_back, req);
|
|
|
|
}
|
2023-05-19 00:42:31 +02:00
|
|
|
}
|