improve error handling, add as_tlv method for TlvOwned
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
Rust/spacepackets/pipeline/pr-main There was a failure building this commit

This commit is contained in:
Robin Müller 2024-06-05 16:18:00 +02:00
parent e0cd096460
commit 2a4dc666d8
Signed by: muellerr
GPG Key ID: A649FB78196E3849
5 changed files with 93 additions and 48 deletions

View File

@ -13,11 +13,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Added
- Added new `cfdp::tlv::TlvOwned` type which erases the lifetime and is clonable.
- Dedicated `cfdp::tlv::TlvLvDataTooLarge` error struct for APIs where this is the only possible
API error.
## Added and Changed
- Added new `ReadableTlv` to avoid some boilerplate code and have a common abstraction implemented
for both `Tlv` and `TlvOwned` to read the raw TLV data field and its length.
- Replaced `cfdp::tlv::TlvLvError` by `cfdp::tlv::TlvLvDataTooLarge` where applicable.
# [v0.11.2] 2024-05-19

View File

@ -1,5 +1,4 @@
//! Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8.
use crate::cfdp::TlvLvError;
use crate::ByteConversionError;
use core::str::Utf8Error;
#[cfg(feature = "serde")]
@ -7,6 +6,8 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::string::String;
use super::TlvLvDataTooLarge;
pub const MIN_LV_LEN: usize = 1;
/// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8.
@ -63,9 +64,9 @@ pub(crate) fn generic_len_check_deserialization(
impl<'data> Lv<'data> {
#[inline]
pub fn new(data: &[u8]) -> Result<Lv, TlvLvError> {
pub fn new(data: &[u8]) -> Result<Lv, TlvLvDataTooLarge> {
if data.len() > u8::MAX as usize {
return Err(TlvLvError::DataTooLarge(data.len()));
return Err(TlvLvDataTooLarge(data.len()));
}
Ok(Lv {
data,
@ -85,7 +86,7 @@ impl<'data> Lv<'data> {
/// Helper function to build a string LV. This is especially useful for the file or directory
/// path LVs
#[inline]
pub fn new_from_str(str_slice: &str) -> Result<Lv, TlvLvError> {
pub fn new_from_str(str_slice: &str) -> Result<Lv, TlvLvDataTooLarge> {
Self::new(str_slice.as_bytes())
}
@ -93,7 +94,7 @@ impl<'data> Lv<'data> {
/// path LVs
#[cfg(feature = "std")]
#[inline]
pub fn new_from_string(string: &'data String) -> Result<Lv<'data>, TlvLvError> {
pub fn new_from_string(string: &'data String) -> Result<Lv<'data>, TlvLvDataTooLarge> {
Self::new(string.as_bytes())
}
@ -178,7 +179,6 @@ impl<'data> Lv<'data> {
#[cfg(test)]
pub mod tests {
use super::*;
use alloc::string::ToString;
use crate::cfdp::TlvLvError;
use crate::ByteConversionError;
@ -271,7 +271,7 @@ pub mod tests {
let lv = Lv::new(&data_big);
assert!(lv.is_err());
let error = lv.unwrap_err();
if let TlvLvError::DataTooLarge(size) = error {
if let TlvLvDataTooLarge(size) = error {
assert_eq!(size, u8::MAX as usize + 1);
assert_eq!(
error.to_string(),

View File

@ -176,11 +176,30 @@ impl Default for ChecksumType {
pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4];
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TlvLvDataTooLarge(pub usize);
impl Display for TlvLvDataTooLarge {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(
f,
"data with size {} larger than allowed {} bytes",
self.0,
u8::MAX
)
}
}
#[cfg(feature = "std")]
impl Error for TlvLvDataTooLarge {}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TlvLvError {
DataTooLarge(usize),
DataTooLarge(TlvLvDataTooLarge),
ByteConversion(ByteConversionError),
/// First value: Found value. Second value: Expected value if there is one.
InvalidTlvTypeField {
@ -197,6 +216,12 @@ pub enum TlvLvError {
InvalidFilestoreActionCode(u8),
}
impl From<TlvLvDataTooLarge> for TlvLvError {
fn from(value: TlvLvDataTooLarge) -> Self {
Self::DataTooLarge(value)
}
}
impl From<ByteConversionError> for TlvLvError {
fn from(value: ByteConversionError) -> Self {
Self::ByteConversion(value)
@ -206,13 +231,8 @@ impl From<ByteConversionError> for TlvLvError {
impl Display for TlvLvError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
TlvLvError::DataTooLarge(data_len) => {
write!(
f,
"data with size {} larger than allowed {} bytes",
data_len,
u8::MAX
)
TlvLvError::DataTooLarge(e) => {
write!(f, "{}", e)
}
TlvLvError::ByteConversion(e) => {
write!(f, "tlv or lv byte conversion: {}", e)
@ -240,6 +260,7 @@ impl Display for TlvLvError {
impl Error for TlvLvError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
TlvLvError::DataTooLarge(e) => Some(e),
TlvLvError::ByteConversion(e) => Some(e),
_ => None,
}

View File

@ -15,6 +15,8 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::TlvLvDataTooLarge;
pub mod msg_to_user;
pub const MIN_TLV_LEN: usize = 2;
@ -151,14 +153,14 @@ pub struct Tlv<'data> {
}
impl<'data> Tlv<'data> {
pub fn new(tlv_type: TlvType, data: &[u8]) -> Result<Tlv, TlvLvError> {
pub fn new(tlv_type: TlvType, data: &[u8]) -> Result<Tlv, TlvLvDataTooLarge> {
Ok(Tlv {
tlv_type_field: TlvTypeField::Standard(tlv_type),
lv: Lv::new(data)?,
})
}
pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Result<Tlv, TlvLvError> {
pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Result<Tlv, TlvLvDataTooLarge> {
Ok(Tlv {
tlv_type_field: TlvTypeField::Custom(tlv_type),
lv: Lv::new(data)?,
@ -237,6 +239,8 @@ impl GenericTlv for Tlv<'_> {
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use crate::cfdp::TlvLvDataTooLarge;
use super::*;
/// Owned variant of [Tlv] which is consequently [Clone]able and does not have a lifetime
@ -250,18 +254,24 @@ pub mod alloc_mod {
}
impl TlvOwned {
pub fn new(tlv_type: TlvType, data: &[u8]) -> Self {
Self {
pub fn new(tlv_type: TlvType, data: &[u8]) -> Result<Self, TlvLvDataTooLarge> {
if data.len() > u8::MAX as usize {
return Err(TlvLvDataTooLarge(data.len()));
}
Ok(Self {
tlv_type_field: TlvTypeField::Standard(tlv_type),
data: data.to_vec(),
}
})
}
pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Self {
Self {
pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Result<Self, TlvLvDataTooLarge> {
if data.len() > u8::MAX as usize {
return Err(TlvLvDataTooLarge(data.len()));
}
Ok(Self {
tlv_type_field: TlvTypeField::Custom(tlv_type),
data: data.to_vec(),
}
})
}
/// Creates a TLV with an empty value field.
@ -271,6 +281,15 @@ pub mod alloc_mod {
data: Vec::new(),
}
}
pub fn as_tlv(&self) -> Tlv<'_> {
Tlv {
tlv_type_field: self.tlv_type_field,
// The API should ensure that the data length is never to large, so the unwrap for the
// LV creation should never be an issue.
lv: Lv::new(&self.data).expect("lv creation failed unexpectedly"),
}
}
}
impl ReadableTlv for TlvOwned {
@ -324,7 +343,7 @@ impl EntityIdTlv {
Self { entity_id }
}
fn len_check(buf: &[u8]) -> Result<(), ByteConversionError> {
fn check_min_len(buf: &[u8]) -> Result<(), ByteConversionError> {
if buf.len() < 2 {
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
@ -347,7 +366,7 @@ impl EntityIdTlv {
}
pub fn from_bytes(buf: &[u8]) -> Result<Self, TlvLvError> {
Self::len_check(buf)?;
Self::check_min_len(buf)?;
verify_tlv_type(buf[0], TlvType::EntityId)?;
let len = buf[1];
if len != 1 && len != 2 && len != 4 && len != 8 {
@ -360,25 +379,29 @@ impl EntityIdTlv {
/// Convert to a generic [Tlv], which also erases the type information.
pub fn to_tlv(self, buf: &mut [u8]) -> Result<Tlv, ByteConversionError> {
Self::len_check(buf)?;
Self::check_min_len(buf)?;
self.entity_id
.write_to_be_bytes(&mut buf[2..2 + self.entity_id.size()])?;
Tlv::new(TlvType::EntityId, &buf[2..2 + self.entity_id.size()]).map_err(|e| match e {
TlvLvError::ByteConversion(e) => e,
// All other errors are impossible.
_ => panic!("unexpected TLV error"),
})
if buf.len() < self.len_value() {
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
expected: self.len_value(),
});
}
// We performed all checks necessary to ensure this call never panics.
Ok(Tlv::new(TlvType::EntityId, &buf[2..2 + self.entity_id.size()]).unwrap())
}
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
TlvOwned::new(TlvType::EntityId, &self.entity_id.to_vec())
// Unwrap is okay here, entity ID should never be larger than maximum allowed size.
TlvOwned::new(TlvType::EntityId, &self.entity_id.to_vec()).unwrap()
}
}
impl WritableTlv for EntityIdTlv {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
Self::len_check(buf)?;
Self::check_min_len(buf)?;
buf[0] = TlvType::EntityId as u8;
buf[1] = self.entity_id.size() as u8;
Ok(2 + self.entity_id.write_to_be_bytes(&mut buf[2..])?)
@ -620,7 +643,8 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> {
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
TlvOwned::new(TlvType::FilestoreRequest, &self.to_vec()[2..])
// The API should ensure the data field is never too large, so unwrapping here is okay.
TlvOwned::new(TlvType::FilestoreRequest, &self.to_vec()[2..]).unwrap()
}
}
@ -810,7 +834,8 @@ impl<'first_name, 'second_name, 'fs_msg> FilestoreResponseTlv<'first_name, 'seco
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
TlvOwned::new(TlvType::FilestoreResponse, &self.to_vec()[2..])
// The API should ensure the data field is never too large, so unwrap is okay here.
TlvOwned::new(TlvType::FilestoreResponse, &self.to_vec()[2..]).unwrap()
}
}
@ -1054,15 +1079,11 @@ mod tests {
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);
assert_eq!(
error.to_string(),
"data with size 256 larger than allowed 255 bytes"
);
} else {
panic!("unexpected error {:?}", error);
}
assert_eq!(error.0, u8::MAX as usize + 1);
assert_eq!(
error.to_string(),
"data with size 256 larger than allowed 255 bytes"
);
}
#[test]
@ -1436,7 +1457,7 @@ mod tests {
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 = TlvOwned::new(TlvType::EntityId, &buf[0..1]);
let tlv_res = TlvOwned::new(TlvType::EntityId, &buf[0..1]).expect("creating TLV failed");
assert_eq!(
tlv_res.tlv_type_field(),
TlvTypeField::Standard(TlvType::EntityId)
@ -1463,7 +1484,7 @@ mod tests {
#[test]
fn test_owned_tlv_custom_type() {
let tlv_res = TlvOwned::new_with_custom_type(32, &[]);
let tlv_res = TlvOwned::new_with_custom_type(32, &[]).unwrap();
assert_eq!(tlv_res.tlv_type_field(), TlvTypeField::Custom(32));
assert_eq!(tlv_res.len_full(), 2);
assert_eq!(tlv_res.value().len(), 0);

View File

@ -2,7 +2,7 @@
#[cfg(feature = "alloc")]
use super::TlvOwned;
use super::{GenericTlv, ReadableTlv, Tlv, TlvLvError, TlvType, TlvTypeField, WritableTlv};
use crate::ByteConversionError;
use crate::{cfdp::TlvLvDataTooLarge, ByteConversionError};
use delegate::delegate;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -12,7 +12,7 @@ pub struct MsgToUserTlv<'data> {
impl<'data> MsgToUserTlv<'data> {
/// Create a new message to user TLV where the type field is set correctly.
pub fn new(value: &'data [u8]) -> Result<MsgToUserTlv<'data>, TlvLvError> {
pub fn new(value: &'data [u8]) -> Result<MsgToUserTlv<'data>, TlvLvDataTooLarge> {
Ok(Self {
tlv: Tlv::new(TlvType::MsgToUser, value)?,
})