start adding LV tests
This commit is contained in:
parent
b37d932e4f
commit
9a9694981a
179
src/cfdp/lv.rs
179
src/cfdp/lv.rs
@ -1,25 +1,30 @@
|
|||||||
use crate::cfdp::TlvLvError;
|
use crate::cfdp::TlvLvError;
|
||||||
use crate::{ByteConversionError, SizeMissmatch};
|
use crate::{ByteConversionError, SizeMissmatch};
|
||||||
use std::prelude::v1::String;
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::string::String;
|
||||||
|
|
||||||
pub const MIN_LV_LEN: usize = 1;
|
pub const MIN_LV_LEN: usize = 1;
|
||||||
|
|
||||||
/// Generic CFDP length-value (LV) abstraction.
|
/// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8.
|
||||||
///
|
///
|
||||||
/// This is just a thin wrapper around a raw slice which performs some additional error handling.
|
/// This is just a thin wrapper around a raw slice which performs some additional error handling.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct Lv<'a> {
|
pub struct Lv<'a> {
|
||||||
data: &'a [u8],
|
data: Option<&'a [u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn generic_len_check_data_serialization(
|
pub(crate) fn generic_len_check_data_serialization(
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
data: &[u8],
|
data_len: usize,
|
||||||
min_overhead: usize,
|
min_overhead: usize,
|
||||||
) -> Result<(), ByteConversionError> {
|
) -> Result<(), ByteConversionError> {
|
||||||
if buf.len() < data.len() + min_overhead {
|
if buf.len() < data_len + min_overhead {
|
||||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||||
found: buf.len(),
|
found: buf.len(),
|
||||||
expected: data.len() + min_overhead,
|
expected: data_len + min_overhead,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -27,22 +32,28 @@ pub(crate) fn generic_len_check_data_serialization(
|
|||||||
|
|
||||||
pub(crate) fn generic_len_check_deserialization(
|
pub(crate) fn generic_len_check_deserialization(
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
min_overheader: usize,
|
min_overhead: usize,
|
||||||
) -> Result<(), ByteConversionError> {
|
) -> Result<(), ByteConversionError> {
|
||||||
if buf.len() < min_overheader {
|
if buf.len() < min_overhead {
|
||||||
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
||||||
found: buf.len(),
|
found: buf.len(),
|
||||||
expected: MIN_LV_LEN,
|
expected: min_overhead,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Lv<'a> {
|
impl<'a> Lv<'a> {
|
||||||
pub fn new(data: &[u8]) -> Result<Lv, TlvLvError> {
|
pub fn new(data: &[u8]) -> Result<Lv, TlvLvError> {
|
||||||
if data.len() > u8::MAX as usize {
|
if data.len() > u8::MAX as usize {
|
||||||
return Err(TlvLvError::DataTooLarge(data.len()));
|
return Err(TlvLvError::DataTooLarge(data.len()));
|
||||||
}
|
}
|
||||||
Ok(Lv { data })
|
Ok(Lv { data: Some(data) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a LV with an empty value field.
|
||||||
|
pub fn new_empty() -> Lv<'a> {
|
||||||
|
Lv { data: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to build a string LV. This is especially useful for the file or directory
|
/// Helper function to build a string LV. This is especially useful for the file or directory
|
||||||
@ -58,14 +69,32 @@ impl<'a> Lv<'a> {
|
|||||||
Self::new(string.as_bytes())
|
Self::new(string.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(&self) -> &[u8] {
|
/// Returns the length of the value part, not including the length byte.
|
||||||
|
pub fn len_value(&self) -> usize {
|
||||||
|
if self.data.is_none() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
self.data.unwrap().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full raw length, including the length byte.
|
||||||
|
pub fn len_raw(&self) -> usize {
|
||||||
|
self.len_value() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether the value field is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.data.is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> Option<&[u8]> {
|
||||||
self.data
|
self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the LV to a raw buffer. Please note that the first byte will contain the length
|
/// Writes the LV to a raw buffer. Please note that the first byte will contain the length
|
||||||
/// of the value, but the values may not exceed a length of [u8::MAX].
|
/// of the value, but the values may not exceed a length of [u8::MAX].
|
||||||
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||||
generic_len_check_data_serialization(buf, self.data, MIN_LV_LEN)?;
|
generic_len_check_data_serialization(buf, self.len_value(), MIN_LV_LEN)?;
|
||||||
Ok(self.write_to_be_bytes_no_len_check(buf))
|
Ok(self.write_to_be_bytes_no_len_check(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,23 +105,125 @@ impl<'a> Lv<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write_to_be_bytes_no_len_check(&self, buf: &mut [u8]) -> usize {
|
pub(crate) fn write_to_be_bytes_no_len_check(&self, buf: &mut [u8]) -> usize {
|
||||||
|
if self.data.is_none() {
|
||||||
|
buf[0] = 0;
|
||||||
|
return MIN_LV_LEN;
|
||||||
|
}
|
||||||
|
let data = self.data.unwrap();
|
||||||
// Length check in constructor ensures the length always has a valid value.
|
// Length check in constructor ensures the length always has a valid value.
|
||||||
buf[0] = self.data.len() as u8;
|
buf[0] = data.len() as u8;
|
||||||
buf[MIN_LV_LEN..self.data.len() + MIN_LV_LEN].copy_from_slice(self.data);
|
buf[MIN_LV_LEN..data.len() + MIN_LV_LEN].copy_from_slice(data);
|
||||||
MIN_LV_LEN + self.data.len()
|
MIN_LV_LEN + data.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_be_bytes_no_len_check(buf: &'a [u8]) -> Result<Lv<'a>, TlvLvError> {
|
pub(crate) fn from_be_bytes_no_len_check(buf: &'a [u8]) -> Result<Lv<'a>, TlvLvError> {
|
||||||
let value_len = buf[0] as usize;
|
let value_len = buf[0] as usize;
|
||||||
if buf.len() < value_len + MIN_LV_LEN {
|
generic_len_check_deserialization(buf, value_len + MIN_LV_LEN)?;
|
||||||
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
let mut data = None;
|
||||||
found: buf.len(),
|
if value_len > 0 {
|
||||||
expected: MIN_LV_LEN + value_len,
|
data = Some(&buf[MIN_LV_LEN..MIN_LV_LEN + value_len])
|
||||||
})
|
}
|
||||||
.into());
|
Ok(Self { data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use crate::cfdp::lv::Lv;
|
||||||
|
use crate::cfdp::TlvLvError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
let lv_data: [u8; 4] = [1, 2, 3, 4];
|
||||||
|
let lv_res = Lv::new(&lv_data);
|
||||||
|
assert!(lv_res.is_ok());
|
||||||
|
let lv = lv_res.unwrap();
|
||||||
|
assert!(lv.value().is_some());
|
||||||
|
let val = lv.value().unwrap();
|
||||||
|
assert_eq!(val[0], 1);
|
||||||
|
assert_eq!(val[1], 2);
|
||||||
|
assert_eq!(val[2], 3);
|
||||||
|
assert_eq!(val[3], 4);
|
||||||
|
assert!(!lv.is_empty());
|
||||||
|
assert_eq!(lv.len_raw(), 5);
|
||||||
|
assert_eq!(lv.len_value(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty() {
|
||||||
|
let lv_empty = Lv::new_empty();
|
||||||
|
assert_eq!(lv_empty.len_value(), 0);
|
||||||
|
assert_eq!(lv_empty.len_raw(), 1);
|
||||||
|
assert!(lv_empty.is_empty());
|
||||||
|
assert_eq!(lv_empty.value(), None);
|
||||||
|
let mut buf: [u8; 4] = [0xff; 4];
|
||||||
|
let res = lv_empty.write_to_be_bytes(&mut buf);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let written = res.unwrap();
|
||||||
|
assert_eq!(written, 1);
|
||||||
|
assert_eq!(buf[0], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization() {
|
||||||
|
let lv_data: [u8; 4] = [1, 2, 3, 4];
|
||||||
|
let lv_res = Lv::new(&lv_data);
|
||||||
|
assert!(lv_res.is_ok());
|
||||||
|
let lv = lv_res.unwrap();
|
||||||
|
let mut buf: [u8; 16] = [0; 16];
|
||||||
|
let res = lv.write_to_be_bytes(&mut buf);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let written = res.unwrap();
|
||||||
|
assert_eq!(written, 5);
|
||||||
|
assert_eq!(buf[0], 4);
|
||||||
|
assert_eq!(buf[1], 1);
|
||||||
|
assert_eq!(buf[2], 2);
|
||||||
|
assert_eq!(buf[3], 3);
|
||||||
|
assert_eq!(buf[4], 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialization() {
|
||||||
|
let mut buf: [u8; 16] = [0; 16];
|
||||||
|
buf[0] = 4;
|
||||||
|
buf[1] = 1;
|
||||||
|
buf[2] = 2;
|
||||||
|
buf[3] = 3;
|
||||||
|
buf[4] = 4;
|
||||||
|
let lv = Lv::from_be_bytes(&buf);
|
||||||
|
assert!(lv.is_ok());
|
||||||
|
let lv = lv.unwrap();
|
||||||
|
assert!(!lv.is_empty());
|
||||||
|
assert!(lv.value().is_some());
|
||||||
|
assert_eq!(lv.len_value(), 4);
|
||||||
|
assert_eq!(lv.len_raw(), 5);
|
||||||
|
let val = lv.value().unwrap();
|
||||||
|
assert_eq!(val[0], 1);
|
||||||
|
assert_eq!(val[1], 2);
|
||||||
|
assert_eq!(val[2], 3);
|
||||||
|
assert_eq!(val[3], 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialization_empty() {
|
||||||
|
let buf: [u8; 2] = [0; 2];
|
||||||
|
let lv_empty = Lv::from_be_bytes(&buf);
|
||||||
|
assert!(lv_empty.is_ok());
|
||||||
|
let lv_empty = lv_empty.unwrap();
|
||||||
|
assert!(lv_empty.is_empty());
|
||||||
|
assert!(lv_empty.value().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_data_too_large() {
|
||||||
|
let data_big: [u8; u8::MAX as usize + 1] = [0; u8::MAX as usize + 1];
|
||||||
|
let lv = Lv::new(&data_big);
|
||||||
|
assert!(lv.is_err());
|
||||||
|
let error = lv.unwrap_err();
|
||||||
|
if let TlvLvError::DataTooLarge(size) = error {
|
||||||
|
assert_eq!(size, u8::MAX as usize + 1);
|
||||||
|
} else {
|
||||||
|
panic!("invalid exception {:?}", error)
|
||||||
}
|
}
|
||||||
Ok(Self {
|
|
||||||
data: &buf[MIN_LV_LEN..MIN_LV_LEN + value_len],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
use crate::cfdp::lv::Lv;
|
use crate::cfdp::lv::Lv;
|
||||||
use crate::cfdp::pdu::{FileDirectiveType, PduHeader};
|
use crate::cfdp::pdu::{FileDirectiveType, PduHeader};
|
||||||
use crate::cfdp::ChecksumType;
|
use crate::cfdp::ChecksumType;
|
||||||
|
@ -21,6 +21,9 @@ pub enum TlvType {
|
|||||||
EntityId = 0x06,
|
EntityId = 0x06,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9.
|
||||||
|
///
|
||||||
|
/// This is just a thin wrapper around a length-value (LV) object, which add the [TlvType].
|
||||||
pub struct Tlv<'a> {
|
pub struct Tlv<'a> {
|
||||||
tlv_type: TlvType,
|
tlv_type: TlvType,
|
||||||
lv: Lv<'a>,
|
lv: Lv<'a>,
|
||||||
@ -37,17 +40,40 @@ impl<'a> Tlv<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
/// Creates a TLV with an empty value field.
|
||||||
generic_len_check_data_serialization(buf, self.value(), MIN_TLV_LEN)?;
|
pub fn new_empty(tlv_type: TlvType) -> Tlv<'a> {
|
||||||
buf[0] = self.tlv_type as u8;
|
Tlv {
|
||||||
self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]);
|
tlv_type,
|
||||||
Ok(MIN_TLV_LEN + self.value().len())
|
lv: Lv::new_empty(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(&self) -> &[u8] {
|
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||||
|
generic_len_check_data_serialization(buf, self.len_value(), MIN_TLV_LEN)?;
|
||||||
|
buf[0] = self.tlv_type as u8;
|
||||||
|
self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]);
|
||||||
|
Ok(self.len_full())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> Option<&[u8]> {
|
||||||
self.lv.value()
|
self.lv.value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the length of the value part, not including the length byte.
|
||||||
|
pub fn len_value(&self) -> usize {
|
||||||
|
self.lv.len_value() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full raw length, including the length byte.
|
||||||
|
pub fn len_full(&self) -> usize {
|
||||||
|
self.lv.len_raw() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether the value field is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.lv.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_be_bytes(buf: &'a [u8]) -> Result<Tlv<'a>, TlvLvError> {
|
pub fn from_be_bytes(buf: &'a [u8]) -> Result<Tlv<'a>, TlvLvError> {
|
||||||
generic_len_check_deserialization(buf, MIN_TLV_LEN)?;
|
generic_len_check_deserialization(buf, MIN_TLV_LEN)?;
|
||||||
let tlv_type_res = TlvType::try_from(buf[0]);
|
let tlv_type_res = TlvType::try_from(buf[0]);
|
||||||
|
Loading…
Reference in New Issue
Block a user