8 Commits

Author SHA1 Message Date
ff0c9d8c70 Update and simplify Metadata PDU creator API
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2024-07-09 16:30:48 +02:00
c40bc855a2 Merge pull request 'add owned TLV type' (#98) from cfdp-tlv-owned-type into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #98
2024-07-09 16:08:53 +02:00
81423fc6e8 add owned TLV type
Some checks are pending
Rust/spacepackets/pipeline/head Build queued...
Rust/spacepackets/pipeline/pr-main Build queued...
2024-07-09 16:04:08 +02:00
a399b11a8e Merge pull request 'update documentation build' (#99) from update-docs-build into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #99
2024-07-03 16:14:46 +02:00
9d4c7446a3 Merge branch 'main' into update-docs-build
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2024-06-25 16:20:08 +02:00
b87f7d73b1 Merge pull request 'clippy fix' (#100) from clippy-fix into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #100
2024-06-25 16:20:01 +02:00
80744eea16 clippy fix
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2024-06-25 16:19:30 +02:00
a5918bfd4a update documentation build
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2024-06-25 16:07:07 +02:00
14 changed files with 424 additions and 110 deletions

View File

@ -61,7 +61,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]'
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
clippy:
name: Clippy

View File

@ -8,6 +8,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.12.0]
## 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.
## Changed
- Minor documentation build updates.
# [v0.11.2] 2024-05-19
- Bumped MSRV to 1.68.2

View File

@ -1,6 +1,6 @@
[package]
name = "spacepackets"
version = "0.11.2"
version = "0.12.0"
edition = "2021"
rust-version = "1.68.2"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
@ -60,11 +60,11 @@ chrono = "0.4"
default = ["std"]
std = ["chrono/std", "chrono/clock", "alloc", "thiserror"]
serde = ["dep:serde", "chrono/serde"]
alloc = ["postcard/alloc", "chrono/alloc"]
alloc = ["postcard/alloc", "chrono/alloc", "defmt/alloc", "serde/alloc"]
chrono = ["dep:chrono"]
timelib = ["dep:time"]
defmt = ["dep:defmt"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]
rustdoc-args = ["--generate-link-to-definition"]

View File

@ -21,7 +21,9 @@ pipeline {
}
stage('Docs') {
steps {
sh 'cargo +nightly doc --all-features'
sh """
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
"""
}
}
stage('Rustfmt') {

View File

@ -4,7 +4,9 @@ Checklist for new releases
# Pre-Release
1. Make sure any new modules are documented sufficiently enough and check docs with
`cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]' --open`.
`RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features --open`
or `cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docsrs" --generate-link-to-definition"]' --open`
(was problematic on more recent nightly versions).
2. Bump version specifier in `Cargo.toml`.
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
`unreleased` section.

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())
}
@ -177,10 +178,10 @@ impl<'data> Lv<'data> {
#[cfg(test)]
pub mod tests {
use super::*;
use alloc::string::ToString;
use crate::cfdp::TlvLvError;
use super::*;
use crate::ByteConversionError;
use std::string::String;
@ -271,15 +272,11 @@ pub mod tests {
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);
assert_eq!(
error.to_string(),
"data with size 256 larger than allowed 255 bytes"
);
} else {
panic!("invalid exception {:?}", 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]

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

@ -10,6 +10,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::tlv::ReadableTlv;
use super::{CfdpPdu, WritablePduPacket};
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]

View File

@ -1,3 +1,5 @@
#[cfg(feature = "alloc")]
use super::tlv::TlvOwned;
use crate::cfdp::lv::Lv;
use crate::cfdp::pdu::{
add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field,
@ -11,6 +13,7 @@ use alloc::vec::Vec;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::tlv::ReadableTlv;
use super::{CfdpPdu, WritablePduPacket};
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
@ -51,6 +54,15 @@ pub fn build_metadata_opts_from_vec(
build_metadata_opts_from_slice(buf, tlvs.as_slice())
}
#[cfg(feature = "alloc")]
pub fn build_metadata_opts_from_owned_slice(tlvs: &[TlvOwned]) -> Vec<u8> {
let mut sum_vec = Vec::new();
for tlv in tlvs {
sum_vec.extend(tlv.to_vec());
}
sum_vec
}
/// Metadata PDU creator abstraction.
///
/// This abstraction exposes a specialized API for creating metadata PDUs as specified in
@ -61,7 +73,7 @@ pub struct MetadataPduCreator<'src_name, 'dest_name, 'opts> {
metadata_params: MetadataGenericParams,
src_file_name: Lv<'src_name>,
dest_file_name: Lv<'dest_name>,
options: &'opts [Tlv<'opts>],
options: &'opts [u8],
}
impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'opts> {
@ -85,7 +97,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
metadata_params: MetadataGenericParams,
src_file_name: Lv<'src_name>,
dest_file_name: Lv<'dest_name>,
options: &'opts [Tlv<'opts>],
options: &'opts [u8],
) -> Self {
Self::new(
pdu_header,
@ -101,7 +113,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
metadata_params: MetadataGenericParams,
src_file_name: Lv<'src_name>,
dest_file_name: Lv<'dest_name>,
options: &'opts [Tlv<'opts>],
options: &'opts [u8],
) -> Self {
pdu_header.pdu_type = PduType::FileDirective;
pdu_header.pdu_conf.direction = Direction::TowardsReceiver;
@ -128,10 +140,19 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
self.dest_file_name
}
pub fn options(&self) -> &'opts [Tlv<'opts>] {
pub fn options(&self) -> &'opts [u8] {
self.options
}
/// Yield an iterator which can be used to loop through all options. Returns [None] if the
/// options field is empty.
pub fn options_iter(&self) -> OptionsIter<'_> {
OptionsIter {
opt_buf: self.options,
current_idx: 0,
}
}
fn calc_pdu_datafield_len(&self) -> usize {
// One directve type octet and one byte of the directive parameter field.
let mut len = 2;
@ -142,9 +163,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
}
len += self.src_file_name.len_full();
len += self.dest_file_name.len_full();
for tlv in self.options() {
len += tlv.len_full()
}
len += self.options().len();
if self.crc_flag() == CrcFlag::WithCrc {
len += 2;
}
@ -190,10 +209,8 @@ impl WritablePduPacket for MetadataPduCreator<'_, '_, '_> {
current_idx += self
.dest_file_name
.write_to_be_bytes(&mut buf[current_idx..])?;
for opt in self.options() {
opt.write_to_bytes(&mut buf[current_idx..current_idx + opt.len_full()])?;
current_idx += opt.len_full();
}
buf[current_idx..current_idx + self.options.len()].copy_from_slice(self.options);
current_idx += self.options.len();
if self.crc_flag() == CrcFlag::WithCrc {
current_idx = add_pdu_crc(buf, current_idx);
}
@ -354,7 +371,7 @@ pub mod tests {
};
use crate::cfdp::pdu::{CfdpPdu, PduError, WritablePduPacket};
use crate::cfdp::pdu::{FileDirectiveType, PduHeader};
use crate::cfdp::tlv::{Tlv, TlvType};
use crate::cfdp::tlv::{ReadableTlv, Tlv, TlvOwned, TlvType, WritableTlv};
use crate::cfdp::{
ChecksumType, CrcFlag, Direction, LargeFileFlag, PduType, SegmentMetadataFlag,
SegmentationControl, TransmissionMode,
@ -364,16 +381,16 @@ pub mod tests {
const SRC_FILENAME: &str = "hello-world.txt";
const DEST_FILENAME: &str = "hello-world2.txt";
fn generic_metadata_pdu<'opts>(
fn generic_metadata_pdu(
crc_flag: CrcFlag,
checksum_type: ChecksumType,
closure_requested: bool,
fss: LargeFileFlag,
opts: &'opts [Tlv],
opts: &[u8],
) -> (
Lv<'static>,
Lv<'static>,
MetadataPduCreator<'static, 'static, 'opts>,
MetadataPduCreator<'static, 'static, '_>,
) {
let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0);
let metadata_params = MetadataGenericParams::new(closure_requested, checksum_type, 0x1010);
@ -543,9 +560,9 @@ pub mod tests {
assert_eq!(written.metadata_params(), read.metadata_params());
assert_eq!(written.src_file_name(), read.src_file_name());
assert_eq!(written.dest_file_name(), read.dest_file_name());
let opts = written.options();
for (tlv_written, tlv_read) in opts.iter().zip(read.options_iter().unwrap()) {
assert_eq!(tlv_written, &tlv_read);
let opts = written.options_iter();
for (tlv_written, tlv_read) in opts.zip(read.options_iter().unwrap()) {
assert_eq!(&tlv_written, &tlv_read);
}
}
@ -660,14 +677,14 @@ pub mod tests {
let tlv1 = Tlv::new_empty(TlvType::FlowLabel);
let msg_to_user: [u8; 4] = [1, 2, 3, 4];
let tlv2 = Tlv::new(TlvType::MsgToUser, &msg_to_user).unwrap();
let tlv_vec = vec![tlv1, tlv2];
let opts_len = tlv1.len_full() + tlv2.len_full();
let mut tlv_buf: [u8; 64] = [0; 64];
let opts_len = build_metadata_opts_from_slice(&mut tlv_buf, &[tlv1, tlv2]).unwrap();
let (src_filename, dest_filename, metadata_pdu) = generic_metadata_pdu(
CrcFlag::NoCrc,
ChecksumType::Crc32,
false,
LargeFileFlag::Normal,
&tlv_vec,
&tlv_buf[0..opts_len],
);
let mut buf: [u8; 128] = [0; 128];
let write_res = metadata_pdu.write_to_bytes(&mut buf);
@ -690,7 +707,55 @@ pub mod tests {
let opts_iter = opts_iter.unwrap();
let mut accumulated_len = 0;
for (idx, opt) in opts_iter.enumerate() {
assert_eq!(tlv_vec[idx], opt);
if idx == 0 {
assert_eq!(tlv1, opt);
} else if idx == 1 {
assert_eq!(tlv2, opt);
}
accumulated_len += opt.len_full();
}
assert_eq!(accumulated_len, pdu_read_back.options().len());
}
#[test]
fn test_with_owned_opts() {
let tlv1 = TlvOwned::new_empty(TlvType::FlowLabel);
let msg_to_user: [u8; 4] = [1, 2, 3, 4];
let tlv2 = TlvOwned::new(TlvType::MsgToUser, &msg_to_user).unwrap();
let mut all_tlvs = tlv1.to_vec();
all_tlvs.extend(tlv2.to_vec());
let (src_filename, dest_filename, metadata_pdu) = generic_metadata_pdu(
CrcFlag::NoCrc,
ChecksumType::Crc32,
false,
LargeFileFlag::Normal,
&all_tlvs,
);
let mut buf: [u8; 128] = [0; 128];
let write_res = metadata_pdu.write_to_bytes(&mut buf);
assert!(write_res.is_ok());
let written = write_res.unwrap();
assert_eq!(
written,
metadata_pdu.pdu_header.header_len()
+ 1
+ 1
+ 4
+ src_filename.len_full()
+ dest_filename.len_full()
+ all_tlvs.len()
);
let pdu_read_back = MetadataPduReader::from_bytes(&buf).unwrap();
compare_read_pdu_to_written_pdu(&metadata_pdu, &pdu_read_back);
let opts_iter = pdu_read_back.options_iter();
assert!(opts_iter.is_some());
let opts_iter = opts_iter.unwrap();
let mut accumulated_len = 0;
for (idx, opt) in opts_iter.enumerate() {
if idx == 0 {
assert_eq!(tlv1, opt);
} else if idx == 1 {
assert_eq!(tlv2, opt);
}
accumulated_len += opt.len_full();
}
assert_eq!(accumulated_len, pdu_read_back.options().len());

View File

@ -9,10 +9,14 @@ use crate::ByteConversionError;
use alloc::vec;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
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;
@ -39,6 +43,26 @@ pub trait GenericTlv {
}
}
pub trait ReadableTlv {
fn value(&self) -> &[u8];
/// Checks whether the value field is empty.
fn is_empty(&self) -> bool {
self.value().is_empty()
}
/// Helper method to retrieve the length of the value. Simply calls the [slice::len] method of
/// [Self::value]
fn len_value(&self) -> usize {
self.value().len()
}
/// Returns the full raw length, including the length byte.
fn len_full(&self) -> usize {
self.len_value() + 2
}
}
pub trait WritableTlv {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
fn len_written(&self) -> usize;
@ -129,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)?,
@ -151,26 +175,6 @@ impl<'data> Tlv<'data> {
}
}
pub fn value(&self) -> &[u8] {
self.lv.value()
}
/// Checks whether the value field is empty.
pub fn is_empty(&self) -> bool {
self.value().is_empty()
}
/// Helper method to retrieve the length of the value. Simply calls the [slice::len] method of
/// [Self::value]
pub fn len_value(&self) -> usize {
self.value().len()
}
/// Returns the full raw length, including the length byte.
pub fn len_full(&self) -> usize {
self.len_value() + 2
}
/// 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
@ -192,6 +196,27 @@ impl<'data> Tlv<'data> {
pub fn raw_data(&self) -> Option<&[u8]> {
self.lv.raw_data()
}
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
TlvOwned {
tlv_type_field: self.tlv_type_field,
data: self.value().to_vec(),
}
}
}
#[cfg(feature = "alloc")]
impl PartialEq<TlvOwned> for Tlv<'_> {
fn eq(&self, other: &TlvOwned) -> bool {
self.tlv_type_field == other.tlv_type_field && self.value() == other.value()
}
}
impl ReadableTlv for Tlv<'_> {
fn value(&self) -> &[u8] {
self.lv.value()
}
}
impl WritableTlv for Tlv<'_> {
@ -212,18 +237,98 @@ impl GenericTlv for Tlv<'_> {
}
}
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 {
found: raw_type,
expected: Some(expected_tlv_type.into()),
})?;
if tlv_type != expected_tlv_type {
return Err(TlvLvError::InvalidTlvTypeField {
found: tlv_type as u8,
expected: Some(expected_tlv_type as u8),
});
#[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
/// associated to a data slice.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TlvOwned {
pub(crate) tlv_type_field: TlvTypeField,
pub(crate) data: Vec<u8>,
}
impl TlvOwned {
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]) -> 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.
pub fn new_empty(tlv_type: TlvType) -> Self {
Self {
tlv_type_field: TlvTypeField::Standard(tlv_type),
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 {
fn value(&self) -> &[u8] {
&self.data
}
}
impl WritableTlv for TlvOwned {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
generic_len_check_data_serialization(buf, self.data.len(), MIN_TLV_LEN)?;
buf[0] = self.tlv_type_field.into();
buf[1] = self.data.len() as u8;
buf[2..2 + self.data.len()].copy_from_slice(&self.data);
Ok(self.len_written())
}
fn len_written(&self) -> usize {
self.data.len() + 2
}
}
impl GenericTlv for TlvOwned {
fn tlv_type_field(&self) -> TlvTypeField {
self.tlv_type_field
}
}
impl From<Tlv<'_>> for TlvOwned {
fn from(value: Tlv<'_>) -> Self {
value.to_owned()
}
}
impl PartialEq<Tlv<'_>> for TlvOwned {
fn eq(&self, other: &Tlv) -> bool {
self.tlv_type_field == other.tlv_type_field && self.data == other.value()
}
}
Ok(())
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -238,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(),
@ -261,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 {
@ -272,22 +377,31 @@ impl EntityIdTlv {
Ok(Self { entity_id })
}
/// Convert to a generic [Tlv], which also erases the programmatic type information.
/// 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 {
// 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..])?)
@ -526,6 +640,12 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> {
},
})
}
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
// 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()
}
}
impl WritableTlv for FilestoreRequestTlv<'_, '_> {
@ -711,6 +831,12 @@ impl<'first_name, 'second_name, 'fs_msg> FilestoreResponseTlv<'first_name, 'seco
filestore_message,
})
}
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
// 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()
}
}
impl WritableTlv for FilestoreResponseTlv<'_, '_, '_> {
@ -752,6 +878,20 @@ impl GenericTlv for FilestoreResponseTlv<'_, '_, '_> {
}
}
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 {
found: raw_type,
expected: Some(expected_tlv_type.into()),
})?;
if tlv_type != expected_tlv_type {
return Err(TlvLvError::InvalidTlvTypeField {
found: tlv_type as u8,
expected: Some(expected_tlv_type as u8),
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
@ -939,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]
@ -1300,4 +1436,71 @@ mod tests {
assert_eq!(tlv_as_vec[0], 20);
assert_eq!(tlv_as_vec[1], 0);
}
#[test]
fn test_tlv_to_owned() {
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 tlv_owned = tlv_res.to_owned();
assert_eq!(tlv_res, tlv_owned);
let tlv_owned_from_conversion: TlvOwned = tlv_res.into();
assert_eq!(tlv_owned_from_conversion, tlv_owned);
assert_eq!(tlv_owned_from_conversion, tlv_res);
}
#[test]
fn test_owned_tlv() {
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]).expect("creating TLV failed");
assert_eq!(
tlv_res.tlv_type_field(),
TlvTypeField::Standard(TlvType::EntityId)
);
assert_eq!(tlv_res.len_full(), 3);
assert_eq!(tlv_res.value().len(), 1);
assert_eq!(tlv_res.len_value(), 1);
assert!(!tlv_res.is_empty());
assert_eq!(tlv_res.value()[0], 5);
}
#[test]
fn test_owned_tlv_empty() {
let tlv_res = TlvOwned::new_empty(TlvType::FlowLabel);
assert_eq!(
tlv_res.tlv_type_field(),
TlvTypeField::Standard(TlvType::FlowLabel)
);
assert_eq!(tlv_res.len_full(), 2);
assert_eq!(tlv_res.value().len(), 0);
assert_eq!(tlv_res.len_value(), 0);
assert!(tlv_res.is_empty());
}
#[test]
fn test_owned_tlv_custom_type() {
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);
assert_eq!(tlv_res.len_value(), 0);
assert!(tlv_res.is_empty());
}
#[test]
fn test_owned_tlv_conversion_to_bytes() {
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 tlv_owned_from_conversion: TlvOwned = tlv_res.into();
assert_eq!(tlv_res.to_vec(), tlv_owned_from_conversion.to_vec());
}
}

View File

@ -1,6 +1,8 @@
//! Abstractions for the Message to User CFDP TLV subtype.
use super::{GenericTlv, Tlv, TlvLvError, TlvType, TlvTypeField, WritableTlv};
use crate::ByteConversionError;
#[cfg(feature = "alloc")]
use super::TlvOwned;
use super::{GenericTlv, ReadableTlv, Tlv, TlvLvError, TlvType, TlvTypeField, WritableTlv};
use crate::{cfdp::TlvLvDataTooLarge, ByteConversionError};
use delegate::delegate;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -10,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)?,
})
@ -75,6 +77,11 @@ impl<'data> MsgToUserTlv<'data> {
}
Ok(msg_to_user)
}
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
self.tlv.to_owned()
}
}
impl WritableTlv for MsgToUserTlv<'_> {

View File

@ -55,7 +55,7 @@
//! println!("{:x?}", &ccsds_buf[0..6]);
//! ```
#![no_std]
#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(any(feature = "std", test))]

View File

@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize};
use core::fmt::{Debug, Display, Formatter};
use core::ops::{Add, AddAssign};
use core::time::Duration;
use core::u64;
use crate::ByteConversionError;

View File

@ -6,7 +6,6 @@ use core::cmp::Ordering;
use core::fmt::{Display, Formatter};
use core::ops::{Add, AddAssign, Sub};
use core::time::Duration;
use core::u8;
#[allow(unused_imports)]
#[cfg(not(feature = "std"))]