Compare commits
16 Commits
v0.17.0
...
missing-us
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42f87dad99 | ||
| e5b165def3 | |||
|
|
9882d3cdc1 | ||
|
|
d8fb5e1ca5 | ||
| 9035ecd139 | |||
|
|
2a3e280d96 | ||
| cf9306d992 | |||
|
|
73b11d0ae2 | ||
| b7efa0378d | |||
|
|
c6d10422d5 | ||
| 416a89b807 | |||
|
|
8136f554b5 | ||
| 665cb3b107 | |||
|
|
989ace786e | ||
| 402331c725 | |||
| 3e3f33a7d1 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
|
||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features --no-deps
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -8,6 +8,28 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.18.0] ?
|
||||
|
||||
## Changed
|
||||
|
||||
- Added distinction between `CcsdsPacketReader::user_data` and `CcsdsPacketReader::packet_data`.
|
||||
- Added distinction between `CcsdsPacketCreatorWithReservedData::user_data` and
|
||||
`CcsdsPacketCreatorWithReservedData::packet_data`, including mutable variants as well.
|
||||
- `SequenceCounter::MAX_BIT_WIDTH` is now a regular trait method `SequenceCounter::max_bit_width`
|
||||
to allow dyn compatibility and easier usage in trait objects.
|
||||
- Improved type level support in USLP module by using VC ID type `u6` and MAP ID type `u4`.
|
||||
|
||||
## Added
|
||||
|
||||
- `checksum` getter for `CcsdsPacketReader`.
|
||||
- Added `SequenceCounterOnFile` which persists the sequence counter by writing it to a file.
|
||||
- Added `SequenceCounter::set` method which allows manually setting an initial value.
|
||||
- Added `CcsdsPacketReader::raw_data` full data getter.
|
||||
|
||||
## Removed
|
||||
|
||||
- `SequenceCounter::increment_mut` and `SequenceCounter::get_and_increment_mut`
|
||||
|
||||
# [v0.17.0] 2025-11-06
|
||||
|
||||
## Changed
|
||||
|
||||
@@ -41,6 +41,7 @@ timelib = ["dep:time"]
|
||||
[dev-dependencies]
|
||||
postcard = { version = "1", features = ["alloc"] }
|
||||
chrono = "0.4"
|
||||
tempfile = "3"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
2
justfile
2
justfile
@@ -24,7 +24,7 @@ build:
|
||||
cargo build --all-features
|
||||
|
||||
docs:
|
||||
RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --all-features
|
||||
RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --all-features --no-deps
|
||||
|
||||
docs-html:
|
||||
RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --all-features --open
|
||||
|
||||
168
src/lib.rs
168
src/lib.rs
@@ -1008,9 +1008,21 @@ impl CcsdsPacketCreatorWithReservedData<'_> {
|
||||
CcsdsPacketIdAndPsc::new_from_ccsds_packet(self)
|
||||
}
|
||||
|
||||
/// Mutable access to the packet data field.
|
||||
#[inline]
|
||||
/// Mutable access to the full packet data field.
|
||||
pub fn packet_data_mut(&mut self) -> &mut [u8] {
|
||||
let len = self.packet_len();
|
||||
&mut self.buf[CCSDS_HEADER_LEN..len]
|
||||
}
|
||||
|
||||
/// Read-only access to the full packet data field.
|
||||
pub fn packet_data(&mut self) -> &[u8] {
|
||||
let len = self.packet_len();
|
||||
&self.buf[CCSDS_HEADER_LEN..len]
|
||||
}
|
||||
|
||||
/// Mutable access to the user data field which excludes the optional CRC16.
|
||||
#[inline]
|
||||
pub fn user_data_mut(&mut self) -> &mut [u8] {
|
||||
let len = self.packet_len();
|
||||
match self.checksum {
|
||||
Some(ChecksumType::WithCrc16) | Some(ChecksumType::WithCrc16ButIgnored) => {
|
||||
@@ -1020,9 +1032,9 @@ impl CcsdsPacketCreatorWithReservedData<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-only access to the packet data field.
|
||||
/// Read-only access to the user data field which excludes the optional CRC16.
|
||||
#[inline]
|
||||
pub fn packet_data(&mut self) -> &[u8] {
|
||||
pub fn user_data(&mut self) -> &[u8] {
|
||||
let len = self.packet_len();
|
||||
match self.checksum {
|
||||
Some(ChecksumType::WithCrc16) | Some(ChecksumType::WithCrc16ButIgnored) => {
|
||||
@@ -1272,7 +1284,7 @@ impl<'app_data> CcsdsPacketCreator<'app_data> {
|
||||
packet_type: PacketType,
|
||||
packet_data: &'app_data [u8],
|
||||
checksum: Option<ChecksumType>,
|
||||
) -> Result<Self, CcsdsPacketCreationError> {
|
||||
) -> Result<Self, InvalidPayloadLengthError> {
|
||||
let common =
|
||||
CcsdsPacketCreatorCommon::new(sp_header, packet_type, packet_data.len(), checksum)?;
|
||||
Ok(Self {
|
||||
@@ -1286,7 +1298,7 @@ impl<'app_data> CcsdsPacketCreator<'app_data> {
|
||||
sp_header: SpHeader,
|
||||
packet_type: PacketType,
|
||||
app_data: &'app_data [u8],
|
||||
) -> Result<Self, CcsdsPacketCreationError> {
|
||||
) -> Result<Self, InvalidPayloadLengthError> {
|
||||
Self::new(
|
||||
sp_header,
|
||||
packet_type,
|
||||
@@ -1299,7 +1311,7 @@ impl<'app_data> CcsdsPacketCreator<'app_data> {
|
||||
pub fn new_tm_with_checksum(
|
||||
sp_header: SpHeader,
|
||||
app_data: &'app_data [u8],
|
||||
) -> Result<Self, CcsdsPacketCreationError> {
|
||||
) -> Result<Self, InvalidPayloadLengthError> {
|
||||
Self::new(
|
||||
sp_header,
|
||||
PacketType::Tm,
|
||||
@@ -1312,7 +1324,7 @@ impl<'app_data> CcsdsPacketCreator<'app_data> {
|
||||
pub fn new_tc_with_checksum(
|
||||
sp_header: SpHeader,
|
||||
app_data: &'app_data [u8],
|
||||
) -> Result<Self, CcsdsPacketCreationError> {
|
||||
) -> Result<Self, InvalidPayloadLengthError> {
|
||||
Self::new(
|
||||
sp_header,
|
||||
PacketType::Tc,
|
||||
@@ -1387,7 +1399,7 @@ impl CcsdsPacket for CcsdsPacketCreator<'_> {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct CcsdsPacketCreatorOwned {
|
||||
common: CcsdsPacketCreatorCommon,
|
||||
packet_data: alloc::vec::Vec<u8>,
|
||||
user_data: alloc::vec::Vec<u8>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
@@ -1406,14 +1418,14 @@ impl CcsdsPacketCreatorOwned {
|
||||
pub fn new(
|
||||
sp_header: SpHeader,
|
||||
packet_type: PacketType,
|
||||
packet_data: &[u8],
|
||||
user_data: &[u8],
|
||||
checksum: Option<ChecksumType>,
|
||||
) -> Result<Self, CcsdsPacketCreationError> {
|
||||
) -> Result<Self, InvalidPayloadLengthError> {
|
||||
let common =
|
||||
CcsdsPacketCreatorCommon::new(sp_header, packet_type, packet_data.len(), checksum)?;
|
||||
CcsdsPacketCreatorCommon::new(sp_header, packet_type, user_data.len(), checksum)?;
|
||||
Ok(Self {
|
||||
common,
|
||||
packet_data: packet_data.to_vec(),
|
||||
user_data: user_data.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1421,12 +1433,12 @@ impl CcsdsPacketCreatorOwned {
|
||||
pub fn new_with_checksum(
|
||||
sp_header: SpHeader,
|
||||
packet_type: PacketType,
|
||||
packet_data: &[u8],
|
||||
) -> Result<Self, CcsdsPacketCreationError> {
|
||||
user_data: &[u8],
|
||||
) -> Result<Self, InvalidPayloadLengthError> {
|
||||
Self::new(
|
||||
sp_header,
|
||||
packet_type,
|
||||
packet_data,
|
||||
user_data,
|
||||
Some(ChecksumType::WithCrc16),
|
||||
)
|
||||
}
|
||||
@@ -1434,12 +1446,12 @@ impl CcsdsPacketCreatorOwned {
|
||||
/// Constructor for telemetry which always appends a CRC16 checksum at the packet end.
|
||||
pub fn new_tm_with_checksum(
|
||||
sp_header: SpHeader,
|
||||
packet_data: &[u8],
|
||||
) -> Result<Self, CcsdsPacketCreationError> {
|
||||
user_data: &[u8],
|
||||
) -> Result<Self, InvalidPayloadLengthError> {
|
||||
Self::new(
|
||||
sp_header,
|
||||
PacketType::Tm,
|
||||
packet_data,
|
||||
user_data,
|
||||
Some(ChecksumType::WithCrc16),
|
||||
)
|
||||
}
|
||||
@@ -1447,25 +1459,25 @@ impl CcsdsPacketCreatorOwned {
|
||||
/// Constructor for telecommands which always appends a CRC16 checksum at the packet end.
|
||||
pub fn new_tc_with_checksum(
|
||||
sp_header: SpHeader,
|
||||
packet_data: &[u8],
|
||||
) -> Result<Self, CcsdsPacketCreationError> {
|
||||
user_data: &[u8],
|
||||
) -> Result<Self, InvalidPayloadLengthError> {
|
||||
Self::new(
|
||||
sp_header,
|
||||
PacketType::Tc,
|
||||
packet_data,
|
||||
user_data,
|
||||
Some(ChecksumType::WithCrc16),
|
||||
)
|
||||
}
|
||||
|
||||
/// Full length when written to bytes.
|
||||
pub fn len_written(&self) -> usize {
|
||||
self.common.len_written(self.packet_data.len())
|
||||
self.common.len_written(self.user_data.len())
|
||||
}
|
||||
|
||||
/// Write the CCSDS packet to the provided buffer.
|
||||
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
self.common
|
||||
.write_to_bytes(buf, self.len_written(), &self.packet_data)
|
||||
.write_to_bytes(buf, self.len_written(), &self.user_data)
|
||||
}
|
||||
|
||||
/// CCSDS space packet header.
|
||||
@@ -1483,7 +1495,7 @@ impl CcsdsPacketCreatorOwned {
|
||||
/// Create a CCSDS packet as a vector.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
|
||||
self.common.to_vec(self.len_written(), &self.packet_data)
|
||||
self.common.to_vec(self.len_written(), &self.user_data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1528,12 +1540,19 @@ pub enum CcsdsPacketReadError {
|
||||
}
|
||||
|
||||
/// CCSDS packet reader structure.
|
||||
///
|
||||
/// This implementation makes one assumption about the passed data: It allows verifying a
|
||||
/// CRC16-CCITT checksum at the last two bytes of the packet data field and excluding it from the
|
||||
/// [Self::packet_data] slice. For that purpose, it makes a distinction between the full
|
||||
/// [Self::packet_data] including the checksum, and [Self::user_data] which does not include
|
||||
/// the checksum.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct CcsdsPacketReader<'buf> {
|
||||
sp_header: SpHeader,
|
||||
packet_data: &'buf [u8],
|
||||
raw_data: &'buf [u8],
|
||||
checksum: Option<u16>,
|
||||
}
|
||||
|
||||
impl<'buf> CcsdsPacketReader<'buf> {
|
||||
@@ -1550,7 +1569,7 @@ impl<'buf> CcsdsPacketReader<'buf> {
|
||||
/// Generic constructor.
|
||||
pub fn new(
|
||||
buf: &'buf [u8],
|
||||
checksum: Option<ChecksumType>,
|
||||
checksum_type: Option<ChecksumType>,
|
||||
) -> Result<Self, CcsdsPacketReadError> {
|
||||
let sp_header = SpHeader::from_be_bytes(&buf[0..CCSDS_HEADER_LEN])?.0;
|
||||
if sp_header.packet_len() > buf.len() {
|
||||
@@ -1560,21 +1579,26 @@ impl<'buf> CcsdsPacketReader<'buf> {
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let user_data = match checksum {
|
||||
let checksum = match checksum_type {
|
||||
Some(ChecksumType::WithCrc16) => {
|
||||
if CRC_CCITT_FALSE.checksum(&buf[0..sp_header.packet_len()]) != 0 {
|
||||
return Err(CcsdsPacketReadError::CrcError);
|
||||
}
|
||||
&buf[CCSDS_HEADER_LEN..sp_header.packet_len() - 2]
|
||||
Some(u16::from_be_bytes([
|
||||
buf[sp_header.packet_len() - 2],
|
||||
buf[sp_header.packet_len() - 1],
|
||||
]))
|
||||
}
|
||||
Some(ChecksumType::WithCrc16ButIgnored) => {
|
||||
&buf[CCSDS_HEADER_LEN..sp_header.packet_len() - 2]
|
||||
}
|
||||
None => &buf[CCSDS_HEADER_LEN..sp_header.packet_len()],
|
||||
Some(ChecksumType::WithCrc16ButIgnored) => Some(u16::from_be_bytes([
|
||||
buf[sp_header.packet_len() - 2],
|
||||
buf[sp_header.packet_len() - 1],
|
||||
])),
|
||||
None => None,
|
||||
};
|
||||
Ok(Self {
|
||||
sp_header,
|
||||
packet_data: user_data,
|
||||
raw_data: buf[0..sp_header.packet_len()].as_ref(),
|
||||
checksum,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1598,10 +1622,32 @@ impl CcsdsPacketReader<'_> {
|
||||
self.sp_header.packet_id.packet_type
|
||||
}
|
||||
|
||||
/// Read-only access to the packet data field.
|
||||
/// Full raw data.
|
||||
#[inline]
|
||||
pub fn raw_data(&self) -> &[u8] {
|
||||
self.raw_data
|
||||
}
|
||||
|
||||
/// Read-only access to the full packet data field.
|
||||
///
|
||||
/// This might also include the checksum but does not include the raw [SpacePacketHeader].
|
||||
/// [Self::user_data] can be used to only retrieve the user data slice without the checksum
|
||||
/// part.
|
||||
#[inline]
|
||||
pub fn packet_data(&self) -> &[u8] {
|
||||
self.packet_data
|
||||
self.raw_data[CCSDS_HEADER_LEN..self.raw_data.len()].as_ref()
|
||||
}
|
||||
|
||||
/// Read-only access to the user data field.
|
||||
///
|
||||
/// This is the [Self::packet_data] without the checksum, if the packet has one.
|
||||
#[inline]
|
||||
pub fn user_data(&self) -> &[u8] {
|
||||
if self.checksum.is_some() {
|
||||
self.packet_data()[0..self.packet_data().len() - 2].as_ref()
|
||||
} else {
|
||||
self.packet_data()
|
||||
}
|
||||
}
|
||||
|
||||
/// 11-bit Application Process ID field.
|
||||
@@ -1633,6 +1679,12 @@ impl CcsdsPacketReader<'_> {
|
||||
pub fn data_len(&self) -> u16 {
|
||||
self.sp_header.data_len()
|
||||
}
|
||||
|
||||
/// Packet checksum if available.
|
||||
#[inline]
|
||||
pub fn checksum(&self) -> Option<u16> {
|
||||
self.checksum
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPacket for CcsdsPacketReader<'_> {
|
||||
@@ -2075,8 +2127,12 @@ pub(crate) mod tests {
|
||||
packet_creator.psc(),
|
||||
PacketSequenceControl::new(SequenceFlags::Unsegmented, u14::new(0))
|
||||
);
|
||||
assert_eq!(packet_creator.packet_data_mut(), &mut [0, 0, 0, 0]);
|
||||
assert_eq!(packet_creator.packet_data(), &[0, 0, 0, 0]);
|
||||
assert_eq!(packet_creator.user_data_mut(), &mut [0, 0, 0, 0]);
|
||||
assert_eq!(packet_creator.user_data(), &[0, 0, 0, 0]);
|
||||
// Includes the unwritten checksum bytes.
|
||||
assert_eq!(packet_creator.packet_data_mut(), &mut [0, 0, 0, 0, 0, 0]);
|
||||
assert_eq!(packet_creator.packet_data(), &[0, 0, 0, 0, 0, 0]);
|
||||
assert_eq!(packet_creator.user_data(), &[0, 0, 0, 0]);
|
||||
assert_eq!(packet_creator.ccsds_version(), u3::new(0b000));
|
||||
}
|
||||
|
||||
@@ -2104,8 +2160,8 @@ pub(crate) mod tests {
|
||||
packet_creator.psc(),
|
||||
PacketSequenceControl::new(SequenceFlags::Unsegmented, u14::new(0))
|
||||
);
|
||||
assert_eq!(packet_creator.packet_data_mut(), &mut [0, 0, 0, 0]);
|
||||
assert_eq!(packet_creator.packet_data(), &[0, 0, 0, 0]);
|
||||
assert_eq!(packet_creator.user_data_mut(), &mut [0, 0, 0, 0]);
|
||||
assert_eq!(packet_creator.user_data(), &[0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2119,7 +2175,7 @@ pub(crate) mod tests {
|
||||
&mut buf,
|
||||
)
|
||||
.unwrap();
|
||||
packet_creator.packet_data_mut().copy_from_slice(&data);
|
||||
packet_creator.user_data_mut().copy_from_slice(&data);
|
||||
let written_len = packet_creator.finish();
|
||||
assert_eq!(
|
||||
CcsdsPacketCreatorWithReservedData::packet_len_for_user_data_with_checksum(4).unwrap(),
|
||||
@@ -2151,7 +2207,7 @@ pub(crate) mod tests {
|
||||
Some(ChecksumType::WithCrc16),
|
||||
)
|
||||
.unwrap();
|
||||
packet_creator.packet_data_mut().copy_from_slice(&data);
|
||||
packet_creator.user_data_mut().copy_from_slice(&data);
|
||||
let written_len = packet_creator.finish();
|
||||
assert_eq!(
|
||||
CcsdsPacketCreatorWithReservedData::packet_len_for_user_data_with_checksum(4).unwrap(),
|
||||
@@ -2189,7 +2245,9 @@ pub(crate) mod tests {
|
||||
let reader = CcsdsPacketReader::new(&buf[0..7], None).unwrap();
|
||||
// Enforced 1 byte packet length.
|
||||
assert_eq!(reader.packet_data(), &[0]);
|
||||
assert_eq!(reader.raw_data(), &buf[0..7]);
|
||||
assert_eq!(reader.packet_len(), 7);
|
||||
assert!(reader.checksum().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2204,15 +2262,17 @@ pub(crate) mod tests {
|
||||
Some(ChecksumType::WithCrc16ButIgnored),
|
||||
)
|
||||
.unwrap();
|
||||
packet_creator.packet_data_mut().copy_from_slice(&data);
|
||||
packet_creator.user_data_mut().copy_from_slice(&data);
|
||||
// Special case.
|
||||
assert_eq!(packet_creator.packet_len(), 13);
|
||||
packet_creator.finish();
|
||||
let reader =
|
||||
CcsdsPacketReader::new(&buf[0..13], Some(ChecksumType::WithCrc16ButIgnored)).unwrap();
|
||||
// Enforced 1 byte packet length.
|
||||
assert_eq!(reader.packet_data(), &data);
|
||||
assert_eq!(reader.user_data(), &data);
|
||||
assert_eq!(reader.raw_data(), &buf[0..13]);
|
||||
assert_eq!(reader.packet_len(), 13);
|
||||
assert!(reader.checksum().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2245,7 +2305,7 @@ pub(crate) mod tests {
|
||||
&mut buf,
|
||||
)
|
||||
.unwrap();
|
||||
packet_creator.packet_data_mut().copy_from_slice(&data);
|
||||
packet_creator.user_data_mut().copy_from_slice(&data);
|
||||
let written_len = packet_creator.finish();
|
||||
assert_eq!(
|
||||
CcsdsPacketCreatorWithReservedData::packet_len_for_user_data_with_checksum(4).unwrap(),
|
||||
@@ -2276,7 +2336,7 @@ pub(crate) mod tests {
|
||||
&mut buf,
|
||||
)
|
||||
.unwrap();
|
||||
packet_creator.packet_data_mut().copy_from_slice(&data);
|
||||
packet_creator.user_data_mut().copy_from_slice(&data);
|
||||
let written_len = packet_creator.finish();
|
||||
assert_eq!(
|
||||
CcsdsPacketCreatorWithReservedData::packet_len_for_user_data_with_checksum(4).unwrap(),
|
||||
@@ -2310,7 +2370,7 @@ pub(crate) mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
let sph = *packet_creator.sp_header();
|
||||
packet_creator.packet_data_mut().copy_from_slice(&data);
|
||||
packet_creator.user_data_mut().copy_from_slice(&data);
|
||||
assert_eq!(
|
||||
packet_creator.ccsds_packet_id_and_psc(),
|
||||
CcsdsPacketIdAndPsc::from(sph)
|
||||
@@ -2415,7 +2475,7 @@ pub(crate) mod tests {
|
||||
.to_vec();
|
||||
let reader =
|
||||
CcsdsPacketReader::new(&packet_raw, Some(ChecksumType::WithCrc16ButIgnored)).unwrap();
|
||||
assert_eq!(reader.packet_data(), data);
|
||||
assert_eq!(reader.user_data(), data);
|
||||
}
|
||||
|
||||
fn generic_test_creator(packet_raw: &[u8], sp_header: &SpHeader, packet_type: PacketType) {
|
||||
@@ -2482,7 +2542,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
fn generic_ccsds_reader_test(
|
||||
packet_data: &[u8],
|
||||
user_data: &[u8],
|
||||
packet_raw: &[u8],
|
||||
packet_type: PacketType,
|
||||
sp_header: SpHeader,
|
||||
@@ -2493,7 +2553,11 @@ pub(crate) mod tests {
|
||||
);
|
||||
let reader = CcsdsPacketReader::new_with_checksum(packet_raw).unwrap();
|
||||
assert_eq!(*reader.sp_header(), sp_header);
|
||||
assert_eq!(reader.packet_data(), packet_data);
|
||||
assert_eq!(
|
||||
&reader.packet_data()[0..reader.packet_data().len() - 2],
|
||||
user_data
|
||||
);
|
||||
assert_eq!(reader.user_data(), user_data);
|
||||
assert_eq!(reader.apid(), u11::new(0x1));
|
||||
assert_eq!(
|
||||
reader.packet_id(),
|
||||
@@ -2506,6 +2570,7 @@ pub(crate) mod tests {
|
||||
assert_eq!(reader.packet_len(), packet_raw.len());
|
||||
assert_eq!(reader.packet_type(), packet_type);
|
||||
assert_eq!(reader.data_len() as usize, packet_raw.len() - 7);
|
||||
assert!(reader.checksum().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2704,7 +2769,8 @@ pub(crate) mod tests {
|
||||
*packet_raw.last_mut().unwrap() = 0;
|
||||
let reader =
|
||||
CcsdsPacketReader::new(&packet_raw, Some(ChecksumType::WithCrc16ButIgnored)).unwrap();
|
||||
assert_eq!(reader.packet_data(), data);
|
||||
assert_eq!(reader.raw_data(), &packet_raw);
|
||||
assert_eq!(reader.user_data(), data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
407
src/seq_count.rs
407
src/seq_count.rs
@@ -18,7 +18,7 @@ pub trait SequenceCounter {
|
||||
type Raw: Into<u64>;
|
||||
|
||||
/// Bit width of the counter.
|
||||
const MAX_BIT_WIDTH: usize;
|
||||
fn max_bit_width(&self) -> usize;
|
||||
|
||||
/// Get the current sequence count value.
|
||||
fn get(&self) -> Self::Raw;
|
||||
@@ -26,11 +26,6 @@ pub trait SequenceCounter {
|
||||
/// Increment the sequence count by one.
|
||||
fn increment(&self);
|
||||
|
||||
/// Increment the sequence count by one, mutable API.
|
||||
fn increment_mut(&mut self) {
|
||||
self.increment();
|
||||
}
|
||||
|
||||
/// Get the current sequence count value and increment the counter by one.
|
||||
fn get_and_increment(&self) -> Self::Raw {
|
||||
let val = self.get();
|
||||
@@ -38,10 +33,11 @@ pub trait SequenceCounter {
|
||||
val
|
||||
}
|
||||
|
||||
/// Get the current sequence count value and increment the counter by one, mutable API.
|
||||
fn get_and_increment_mut(&mut self) -> Self::Raw {
|
||||
self.get_and_increment()
|
||||
}
|
||||
/// Set the sequence counter.
|
||||
///
|
||||
/// This should not be required by default but can be used to reset the counter
|
||||
/// or initialize it with a custom value.
|
||||
fn set(&self, value: Self::Raw);
|
||||
}
|
||||
|
||||
/// Simple sequence counter which wraps at ´T::MAX´.
|
||||
@@ -58,7 +54,7 @@ macro_rules! impl_for_primitives {
|
||||
paste! {
|
||||
impl SequenceCounterSimple<$ty> {
|
||||
/// Constructor with a custom maximum value.
|
||||
pub fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self {
|
||||
pub const fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self {
|
||||
Self {
|
||||
seq_count: Cell::new(0),
|
||||
max_val,
|
||||
@@ -66,7 +62,7 @@ macro_rules! impl_for_primitives {
|
||||
}
|
||||
|
||||
/// Generic constructor.
|
||||
pub fn [<new_ $ty>]() -> Self {
|
||||
pub const fn [<new_ $ty>]() -> Self {
|
||||
Self {
|
||||
seq_count: Cell::new(0),
|
||||
max_val: $ty::MAX
|
||||
@@ -82,16 +78,23 @@ macro_rules! impl_for_primitives {
|
||||
|
||||
impl SequenceCounter for SequenceCounterSimple<$ty> {
|
||||
type Raw = $ty;
|
||||
const MAX_BIT_WIDTH: usize = core::mem::size_of::<Self::Raw>() * 8;
|
||||
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
core::mem::size_of::<Self::Raw>() * 8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get(&self) -> Self::Raw {
|
||||
self.seq_count.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment(&self) {
|
||||
self.get_and_increment();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_and_increment(&self) -> Self::Raw {
|
||||
let curr_count = self.seq_count.get();
|
||||
|
||||
@@ -102,6 +105,11 @@ macro_rules! impl_for_primitives {
|
||||
}
|
||||
curr_count
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set(&self, value: Self::Raw) {
|
||||
self.seq_count.set(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
@@ -117,6 +125,7 @@ pub struct SequenceCounterCcsdsSimple {
|
||||
}
|
||||
|
||||
impl Default for SequenceCounterCcsdsSimple {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
provider: SequenceCounterSimple::new_custom_max_val_u16(MAX_SEQ_COUNT.as_u16()),
|
||||
@@ -126,7 +135,6 @@ impl Default for SequenceCounterCcsdsSimple {
|
||||
|
||||
impl SequenceCounter for SequenceCounterCcsdsSimple {
|
||||
type Raw = u16;
|
||||
const MAX_BIT_WIDTH: usize = core::mem::size_of::<Self::Raw>() * 8;
|
||||
delegate::delegate! {
|
||||
to self.provider {
|
||||
fn get(&self) -> u16;
|
||||
@@ -134,73 +142,143 @@ impl SequenceCounter for SequenceCounterCcsdsSimple {
|
||||
fn get_and_increment(&self) -> u16;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set(&self, value: u16) {
|
||||
if value > MAX_SEQ_COUNT.as_u16() {
|
||||
return;
|
||||
}
|
||||
self.provider.set(value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
Self::MAX_BIT_WIDTH
|
||||
}
|
||||
}
|
||||
|
||||
impl SequenceCounterCcsdsSimple {
|
||||
/// Maximum bit width for CCSDS packet sequence counter is 14 bits.
|
||||
pub const MAX_BIT_WIDTH: usize = 14;
|
||||
|
||||
/// Create a new sequence counter specifically for the sequence count of CCSDS packets.
|
||||
///
|
||||
/// It has a [Self::MAX_BIT_WIDTH] of 14.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
provider: SequenceCounterSimple::new_custom_max_val_u16(MAX_SEQ_COUNT.value()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_has_atomic = "8")]
|
||||
impl SequenceCounter for core::sync::atomic::AtomicU8 {
|
||||
type Raw = u8;
|
||||
|
||||
const MAX_BIT_WIDTH: usize = 8;
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get(&self) -> Self::Raw {
|
||||
self.load(core::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment(&self) {
|
||||
self.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set(&self, value: u8) {
|
||||
self.store(value, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_has_atomic = "16")]
|
||||
impl SequenceCounter for core::sync::atomic::AtomicU16 {
|
||||
type Raw = u16;
|
||||
|
||||
const MAX_BIT_WIDTH: usize = 16;
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get(&self) -> Self::Raw {
|
||||
self.load(core::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment(&self) {
|
||||
self.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set(&self, value: u16) {
|
||||
self.store(value, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_has_atomic = "32")]
|
||||
impl SequenceCounter for core::sync::atomic::AtomicU32 {
|
||||
type Raw = u32;
|
||||
|
||||
const MAX_BIT_WIDTH: usize = 32;
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get(&self) -> Self::Raw {
|
||||
self.load(core::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment(&self) {
|
||||
self.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set(&self, value: u32) {
|
||||
self.store(value, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_has_atomic = "64")]
|
||||
impl SequenceCounter for core::sync::atomic::AtomicU64 {
|
||||
type Raw = u64;
|
||||
|
||||
const MAX_BIT_WIDTH: usize = 64;
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get(&self) -> Self::Raw {
|
||||
self.load(core::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment(&self) {
|
||||
self.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set(&self, value: u64) {
|
||||
self.store(value, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "portable-atomic")]
|
||||
impl SequenceCounter for portable_atomic::AtomicU8 {
|
||||
type Raw = u8;
|
||||
|
||||
const MAX_BIT_WIDTH: usize = 8;
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
8
|
||||
}
|
||||
|
||||
fn get(&self) -> Self::Raw {
|
||||
self.load(portable_atomic::Ordering::Relaxed)
|
||||
@@ -209,13 +287,20 @@ impl SequenceCounter for portable_atomic::AtomicU8 {
|
||||
fn increment(&self) {
|
||||
self.fetch_add(1, portable_atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn set(&self, value: Self::Raw) {
|
||||
self.store(value, portable_atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "portable-atomic")]
|
||||
impl SequenceCounter for portable_atomic::AtomicU16 {
|
||||
type Raw = u16;
|
||||
|
||||
const MAX_BIT_WIDTH: usize = 16;
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
16
|
||||
}
|
||||
|
||||
fn get(&self) -> Self::Raw {
|
||||
self.load(portable_atomic::Ordering::Relaxed)
|
||||
@@ -224,13 +309,20 @@ impl SequenceCounter for portable_atomic::AtomicU16 {
|
||||
fn increment(&self) {
|
||||
self.fetch_add(1, portable_atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn set(&self, value: Self::Raw) {
|
||||
self.store(value, portable_atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "portable-atomic")]
|
||||
impl SequenceCounter for portable_atomic::AtomicU32 {
|
||||
type Raw = u32;
|
||||
|
||||
const MAX_BIT_WIDTH: usize = 32;
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
fn get(&self) -> Self::Raw {
|
||||
self.load(portable_atomic::Ordering::Relaxed)
|
||||
@@ -239,13 +331,20 @@ impl SequenceCounter for portable_atomic::AtomicU32 {
|
||||
fn increment(&self) {
|
||||
self.fetch_add(1, portable_atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn set(&self, value: Self::Raw) {
|
||||
self.store(value, portable_atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "portable-atomic")]
|
||||
impl SequenceCounter for portable_atomic::AtomicU64 {
|
||||
type Raw = u64;
|
||||
|
||||
const MAX_BIT_WIDTH: usize = 64;
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
fn get(&self) -> Self::Raw {
|
||||
self.load(portable_atomic::Ordering::Relaxed)
|
||||
@@ -254,19 +353,33 @@ impl SequenceCounter for portable_atomic::AtomicU64 {
|
||||
fn increment(&self) {
|
||||
self.fetch_add(1, portable_atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn set(&self, value: Self::Raw) {
|
||||
self.store(value, portable_atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SequenceCounter + ?Sized> SequenceCounter for &T {
|
||||
type Raw = T::Raw;
|
||||
const MAX_BIT_WIDTH: usize = T::MAX_BIT_WIDTH;
|
||||
|
||||
#[inline]
|
||||
fn max_bit_width(&self) -> usize {
|
||||
(**self).max_bit_width()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get(&self) -> Self::Raw {
|
||||
(**self).get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment(&self) {
|
||||
(**self).increment()
|
||||
}
|
||||
|
||||
fn set(&self, value: Self::Raw) {
|
||||
(**self).set(value);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
@@ -298,7 +411,10 @@ macro_rules! sync_clonable_seq_counter_impl {
|
||||
|
||||
impl SequenceCounter for [<SequenceCounterSyncCustomWrap $ty:upper>] {
|
||||
type Raw = $ty;
|
||||
const MAX_BIT_WIDTH: usize = core::mem::size_of::<Self::Raw>() * 8;
|
||||
|
||||
fn max_bit_width(&self) -> usize {
|
||||
core::mem::size_of::<Self::Raw>() * 8
|
||||
}
|
||||
|
||||
fn get(&self) -> $ty {
|
||||
self.seq_count.load(core::sync::atomic::Ordering::Relaxed)
|
||||
@@ -319,6 +435,10 @@ macro_rules! sync_clonable_seq_counter_impl {
|
||||
},
|
||||
).unwrap()
|
||||
}
|
||||
|
||||
fn set(&self, value: $ty) {
|
||||
self.seq_count.store(value, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -333,9 +453,187 @@ sync_clonable_seq_counter_impl!(u32);
|
||||
#[cfg(target_has_atomic = "64")]
|
||||
sync_clonable_seq_counter_impl!(u64);
|
||||
|
||||
/// Modules relying on [std] support.
|
||||
#[cfg(feature = "std")]
|
||||
pub mod std_mod {
|
||||
use super::*;
|
||||
|
||||
use core::str::FromStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::string::ToString as _;
|
||||
use std::{fs, io};
|
||||
|
||||
/// A persistent file-backed sequence counter that can wrap any other [SequenceCounter]
|
||||
/// implementation which is non-persistent.
|
||||
///
|
||||
/// In the default configuration, the underlying [SequenceCounter] is initialized from the file
|
||||
/// content, and the file content will only be updated on a manual [Self::save] or on drop.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SequenceCounterOnFile<
|
||||
Inner: SequenceCounter<Raw = RawTy>,
|
||||
RawTy: core::fmt::Debug
|
||||
+ Copy
|
||||
+ Clone
|
||||
+ Into<u64>
|
||||
+ TryFrom<u64>
|
||||
+ FromStr
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq,
|
||||
> {
|
||||
path: PathBuf,
|
||||
inner: Inner,
|
||||
/// Configures whether the counter value is saved to disk when the object is dropped.
|
||||
///
|
||||
/// If this is set to [true] which is the default, the sequence counter will only be stored
|
||||
/// to disk if the [Self::save] method is used or the object is dropped. Otherwise, the
|
||||
/// counter will be saved to disk on every [Self::increment] or [Self::set].
|
||||
pub save_on_drop: bool,
|
||||
}
|
||||
|
||||
impl<
|
||||
Inner: SequenceCounter<Raw = RawTy>,
|
||||
RawTy: core::fmt::Debug
|
||||
+ Copy
|
||||
+ Clone
|
||||
+ Into<u64>
|
||||
+ TryFrom<u64>
|
||||
+ FromStr
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq,
|
||||
> SequenceCounterOnFile<Inner, RawTy>
|
||||
{
|
||||
/// Initialize a new persistent sequence counter using a file at the given path and
|
||||
/// any non persistent inner [SequenceCounter] implementation.
|
||||
pub fn new<P: AsRef<Path>>(path: P, inner: Inner) -> io::Result<Self> {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let value = Self::load_from_path(&path);
|
||||
inner.set(value);
|
||||
Ok(Self {
|
||||
path,
|
||||
inner,
|
||||
save_on_drop: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_from_path(path: &Path) -> RawTy {
|
||||
let bytes = match fs::read(path) {
|
||||
Ok(b) => b,
|
||||
Err(_) => return Default::default(),
|
||||
};
|
||||
|
||||
// Trim optional single trailing newline (Unix/Windows)
|
||||
let trimmed = match bytes.last() {
|
||||
Some(&b'\n') => &bytes[..bytes.len() - 1],
|
||||
_ => &bytes,
|
||||
};
|
||||
|
||||
// Reject non-ASCII
|
||||
if !trimmed.is_ascii() {
|
||||
return Default::default();
|
||||
}
|
||||
|
||||
// Parse
|
||||
std::str::from_utf8(trimmed)
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Persist the current value to disk (best-effort).
|
||||
pub fn save(&self) -> io::Result<()> {
|
||||
let value = self.inner.get();
|
||||
std::fs::write(&self.path, value.into().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Inner: SequenceCounter<Raw = RawTy>,
|
||||
RawTy: core::fmt::Debug
|
||||
+ Copy
|
||||
+ Clone
|
||||
+ Into<u64>
|
||||
+ TryFrom<u64, Error: core::fmt::Debug>
|
||||
+ FromStr
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq,
|
||||
> SequenceCounter for SequenceCounterOnFile<Inner, RawTy>
|
||||
{
|
||||
type Raw = RawTy;
|
||||
|
||||
fn max_bit_width(&self) -> usize {
|
||||
self.inner.max_bit_width()
|
||||
}
|
||||
|
||||
fn get(&self) -> RawTy {
|
||||
self.inner.get()
|
||||
}
|
||||
|
||||
fn increment(&self) {
|
||||
self.inner.increment();
|
||||
|
||||
if !self.save_on_drop {
|
||||
// persist (ignore I/O errors here; caller can call `save` explicitly)
|
||||
let _ = self.save();
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&self, value: RawTy) {
|
||||
self.inner.set(value);
|
||||
if !self.save_on_drop {
|
||||
// persist (ignore I/O errors here; caller can call `save` explicitly)
|
||||
let _ = self.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Inner: SequenceCounter<Raw = RawTy>,
|
||||
RawTy: core::fmt::Debug
|
||||
+ Copy
|
||||
+ Clone
|
||||
+ Into<u64>
|
||||
+ TryFrom<u64>
|
||||
+ FromStr
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq,
|
||||
> Drop for SequenceCounterOnFile<Inner, RawTy>
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if self.save_on_drop {
|
||||
let _ = self.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alisas for a CCSDS sequence counter stored on file.
|
||||
pub type SequenceCounterCcsdsOnFile = SequenceCounterOnFile<SequenceCounterCcsdsSimple, u16>;
|
||||
|
||||
impl SequenceCounterCcsdsOnFile {
|
||||
/// Open or create the counter file at `path`.
|
||||
pub fn new_ccsds_counter<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
SequenceCounterOnFile::new(path, SequenceCounterCcsdsSimple::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alisas for a [u16] sequence counter stored on file.
|
||||
pub type SequenceCounterU16OnFile = SequenceCounterOnFile<SequenceCounterSimple<u16>, u16>;
|
||||
|
||||
impl SequenceCounterU16OnFile {
|
||||
/// Open or create the counter file at `path`.
|
||||
pub fn new_u16_counter<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
SequenceCounterOnFile::new(path, SequenceCounterSimple::<u16>::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8};
|
||||
use std::boxed::Box;
|
||||
|
||||
use crate::seq_count::{
|
||||
SequenceCounter, SequenceCounterCcsdsSimple, SequenceCounterSimple,
|
||||
@@ -347,6 +645,7 @@ mod tests {
|
||||
fn test_u8_counter() {
|
||||
let u8_counter = SequenceCounterSimple::<u8>::default();
|
||||
assert_eq!(u8_counter.get(), 0);
|
||||
assert_eq!(u8_counter.max_bit_width(), 8);
|
||||
assert_eq!(u8_counter.get_and_increment(), 0);
|
||||
assert_eq!(u8_counter.get_and_increment(), 1);
|
||||
assert_eq!(u8_counter.get(), 2);
|
||||
@@ -384,33 +683,37 @@ mod tests {
|
||||
assert_eq!(seq_counter.get_and_increment().into(), 0);
|
||||
assert_eq!(seq_counter.get_and_increment().into(), 1);
|
||||
assert_eq!(seq_counter.get().into(), 2);
|
||||
seq_counter.increment_mut();
|
||||
seq_counter.increment();
|
||||
assert_eq!(seq_counter.get().into(), 3);
|
||||
assert_eq!(seq_counter.get_and_increment_mut().into(), 3);
|
||||
assert_eq!(seq_counter.get_and_increment().into(), 3);
|
||||
assert_eq!(seq_counter.get().into(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_atomic_counter_u8() {
|
||||
let mut sync_u8_counter = AtomicU8::new(0);
|
||||
assert_eq!(sync_u8_counter.max_bit_width(), 8);
|
||||
common_counter_test(&mut sync_u8_counter);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_atomic_counter_u16() {
|
||||
let mut sync_u16_counter = AtomicU16::new(0);
|
||||
assert_eq!(sync_u16_counter.max_bit_width(), 16);
|
||||
common_counter_test(&mut sync_u16_counter);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_atomic_counter_u32() {
|
||||
let mut sync_u32_counter = AtomicU32::new(0);
|
||||
assert_eq!(sync_u32_counter.max_bit_width(), 32);
|
||||
common_counter_test(&mut sync_u32_counter);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_atomic_counter_u64() {
|
||||
let mut sync_u64_counter = AtomicU64::new(0);
|
||||
assert_eq!(sync_u64_counter.max_bit_width(), 64);
|
||||
common_counter_test(&mut sync_u64_counter);
|
||||
}
|
||||
|
||||
@@ -470,4 +773,56 @@ mod tests {
|
||||
}
|
||||
assert_eq!(sync_u8_counter.get(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dyn_compatible() {
|
||||
let counter: Box<dyn SequenceCounter<Raw = u16>> =
|
||||
Box::new(SequenceCounterCcsdsSimple::default());
|
||||
assert_eq!(counter.get(), 0);
|
||||
assert_eq!(counter.max_bit_width(), 14);
|
||||
counter.increment();
|
||||
assert_eq!(counter.get(), 1);
|
||||
assert_eq!(counter.get_and_increment(), 1);
|
||||
assert_eq!(counter.get(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_persistent_counter() {
|
||||
let tempdir = tempfile::tempdir().expect("failed to create temp dir");
|
||||
let path = tempdir.path().join("seq_count.txt");
|
||||
let mut persistent_counter =
|
||||
crate::seq_count::std_mod::SequenceCounterCcsdsOnFile::new_ccsds_counter(&path)
|
||||
.unwrap();
|
||||
assert_eq!(persistent_counter.get(), 0);
|
||||
assert_eq!(persistent_counter.get_and_increment(), 0);
|
||||
drop(persistent_counter);
|
||||
assert!(path.exists());
|
||||
|
||||
persistent_counter =
|
||||
crate::seq_count::std_mod::SequenceCounterCcsdsOnFile::new_ccsds_counter(
|
||||
tempdir.path().join("seq_count.txt"),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(persistent_counter.get(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_persistent_couter_manual_save() {
|
||||
let tempdir = tempfile::tempdir().expect("failed to create temp dir");
|
||||
let path = tempdir.path().join("seq_count.txt");
|
||||
let mut persistent_counter =
|
||||
crate::seq_count::std_mod::SequenceCounterCcsdsOnFile::new_ccsds_counter(&path)
|
||||
.unwrap();
|
||||
assert_eq!(persistent_counter.get(), 0);
|
||||
assert_eq!(persistent_counter.get_and_increment(), 0);
|
||||
persistent_counter.save().unwrap();
|
||||
assert!(path.exists());
|
||||
std::mem::forget(persistent_counter);
|
||||
persistent_counter =
|
||||
crate::seq_count::std_mod::SequenceCounterCcsdsOnFile::new_ccsds_counter(
|
||||
tempdir.path().join("seq_count.txt"),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(persistent_counter.get(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
436
src/uslp/mod.rs
436
src/uslp/mod.rs
@@ -1,5 +1,7 @@
|
||||
//! # Support of the CCSDS Unified Space Data Link Protocol (USLP)
|
||||
#![warn(missing_docs)]
|
||||
use arbitrary_int::{prelude::*, u4, u6};
|
||||
|
||||
use crate::{crc::CRC_CCITT_FALSE, ByteConversionError};
|
||||
|
||||
/// Only this version is supported by the library
|
||||
@@ -64,17 +66,17 @@ pub enum UslpError {
|
||||
/// Invalid version number.
|
||||
#[error("invalid version number: {0}")]
|
||||
InvalidVersionNumber(u8),
|
||||
/// Invalid virtual channel ID.
|
||||
#[error("invalid virtual channel ID: {0}")]
|
||||
InvalidVcId(u8),
|
||||
/// Invalid MAP ID.
|
||||
#[error("invalid MAP ID: {0}")]
|
||||
InvalidMapId(u8),
|
||||
/// Checksum failure.
|
||||
#[error("checksum failure")]
|
||||
ChecksumFailure(u16),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[error("FHP or LVO field invalid for given construction rule")]
|
||||
pub struct FhpLvoError(pub ConstructionRule);
|
||||
|
||||
/// Invalid value for length.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@@ -82,7 +84,7 @@ pub enum UslpError {
|
||||
#[error("invalid value for length of the field")]
|
||||
pub struct InvalidValueForLenError {
|
||||
value: u64,
|
||||
len: u8,
|
||||
len: u3,
|
||||
}
|
||||
|
||||
/// Primary header of a USLP transfer frame.
|
||||
@@ -95,9 +97,9 @@ pub struct PrimaryHeader {
|
||||
/// Source or destination identifier.
|
||||
pub source_or_dest_field: SourceOrDestField,
|
||||
/// Virtual channel ID.
|
||||
pub vc_id: u8,
|
||||
pub vc_id: u6,
|
||||
/// MAP ID.
|
||||
pub map_id: u8,
|
||||
pub map_id: u4,
|
||||
frame_len_field: u16,
|
||||
/// Bypass sequence control flag.
|
||||
pub sequence_control_flag: BypassSequenceControlFlag,
|
||||
@@ -105,7 +107,7 @@ pub struct PrimaryHeader {
|
||||
pub protocol_control_command_flag: ProtocolControlCommandFlag,
|
||||
/// Operational control field flag.
|
||||
pub ocf_flag: bool,
|
||||
vc_frame_count_len: u8,
|
||||
vc_frame_count_len: u3,
|
||||
vc_frame_count: u64,
|
||||
}
|
||||
|
||||
@@ -114,16 +116,10 @@ impl PrimaryHeader {
|
||||
pub fn new(
|
||||
spacecraft_id: u16,
|
||||
source_or_dest_field: SourceOrDestField,
|
||||
vc_id: u8,
|
||||
map_id: u8,
|
||||
vc_id: u6,
|
||||
map_id: u4,
|
||||
frame_len: u16,
|
||||
) -> Result<Self, UslpError> {
|
||||
if vc_id > 0b111111 {
|
||||
return Err(UslpError::InvalidVcId(vc_id));
|
||||
}
|
||||
if map_id > 0b1111 {
|
||||
return Err(UslpError::InvalidMapId(map_id));
|
||||
}
|
||||
Ok(Self {
|
||||
spacecraft_id,
|
||||
source_or_dest_field,
|
||||
@@ -133,7 +129,7 @@ impl PrimaryHeader {
|
||||
sequence_control_flag: BypassSequenceControlFlag::SequenceControlledQoS,
|
||||
protocol_control_command_flag: ProtocolControlCommandFlag::TfdfContainsUserData,
|
||||
ocf_flag: false,
|
||||
vc_frame_count_len: 0,
|
||||
vc_frame_count_len: u3::ZERO,
|
||||
vc_frame_count: 0,
|
||||
})
|
||||
}
|
||||
@@ -141,10 +137,10 @@ impl PrimaryHeader {
|
||||
/// Set the virtual channel frame count.
|
||||
pub fn set_vc_frame_count(
|
||||
&mut self,
|
||||
count_len: u8,
|
||||
count_len: u3,
|
||||
count: u64,
|
||||
) -> Result<(), InvalidValueForLenError> {
|
||||
if count > 2_u64.pow(count_len as u32 * 8) - 1 {
|
||||
if count > 2_u64.pow(count_len.as_u32() * 8) - 1 {
|
||||
return Err(InvalidValueForLenError {
|
||||
value: count,
|
||||
len: count_len,
|
||||
@@ -163,7 +159,7 @@ impl PrimaryHeader {
|
||||
|
||||
/// Length of the virtual channel frame count field.
|
||||
#[inline]
|
||||
pub fn vc_frame_count_len(&self) -> u8 {
|
||||
pub fn vc_frame_count_len(&self) -> u3 {
|
||||
self.vc_frame_count_len
|
||||
}
|
||||
|
||||
@@ -198,15 +194,15 @@ impl PrimaryHeader {
|
||||
1 => SourceOrDestField::Dest,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let vc_frame_count_len = buf[6] & 0b111;
|
||||
if buf.len() < 7 + vc_frame_count_len as usize {
|
||||
let vc_frame_count_len = u3::new(buf[6] & 0b111);
|
||||
if buf.len() < 7 + vc_frame_count_len.as_usize() {
|
||||
return Err(ByteConversionError::FromSliceTooSmall {
|
||||
found: buf.len(),
|
||||
expected: 7 + vc_frame_count_len as usize,
|
||||
expected: 7 + vc_frame_count_len.as_usize(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let vc_frame_count = match vc_frame_count_len {
|
||||
let vc_frame_count = match vc_frame_count_len.value() {
|
||||
1 => buf[7] as u64,
|
||||
2 => u16::from_be_bytes(buf[7..9].try_into().unwrap()) as u64,
|
||||
4 => u32::from_be_bytes(buf[7..11].try_into().unwrap()) as u64,
|
||||
@@ -225,8 +221,8 @@ impl PrimaryHeader {
|
||||
| ((buf[1] as u16) << 4)
|
||||
| ((buf[2] as u16) >> 4) & 0b1111,
|
||||
source_or_dest_field,
|
||||
vc_id: ((buf[2] & 0b111) << 3) | (buf[3] >> 5) & 0b111,
|
||||
map_id: (buf[3] >> 1) & 0b1111,
|
||||
vc_id: u6::new(((buf[2] & 0b111) << 3) | (buf[3] >> 5) & 0b111),
|
||||
map_id: u4::new((buf[3] >> 1) & 0b1111),
|
||||
frame_len_field: ((buf[4] as u16) << 8) | buf[5] as u16,
|
||||
sequence_control_flag: ((buf[6] >> 7) & 0b1).try_into().unwrap(),
|
||||
protocol_control_command_flag: ((buf[6] >> 6) & 0b1).try_into().unwrap(),
|
||||
@@ -237,7 +233,7 @@ impl PrimaryHeader {
|
||||
}
|
||||
|
||||
/// Write primary header to bytes.
|
||||
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
if buf.len() < self.len_header() {
|
||||
return Err(ByteConversionError::ToSliceTooSmall {
|
||||
found: buf.len(),
|
||||
@@ -248,15 +244,15 @@ impl PrimaryHeader {
|
||||
buf[1] = (self.spacecraft_id >> 4) as u8;
|
||||
buf[2] = (((self.spacecraft_id & 0b1111) as u8) << 4)
|
||||
| ((self.source_or_dest_field as u8) << 3)
|
||||
| (self.vc_id >> 3) & 0b111;
|
||||
buf[3] = ((self.vc_id & 0b111) << 5) | (self.map_id << 1);
|
||||
| (self.vc_id.as_u8() >> 3) & 0b111;
|
||||
buf[3] = ((self.vc_id.as_u8() & 0b111) << 5) | (self.map_id.as_u8() << 1);
|
||||
buf[4..6].copy_from_slice(&self.frame_len_field.to_be_bytes());
|
||||
buf[6] = ((self.sequence_control_flag as u8) << 7)
|
||||
| ((self.protocol_control_command_flag as u8) << 6)
|
||||
| ((self.ocf_flag as u8) << 3)
|
||||
| self.vc_frame_count_len;
|
||||
| self.vc_frame_count_len.as_u8();
|
||||
let mut packet_idx = 7;
|
||||
for idx in (0..self.vc_frame_count_len).rev() {
|
||||
for idx in (0..self.vc_frame_count_len.value()).rev() {
|
||||
buf[packet_idx] = ((self.vc_frame_count >> (idx * 8)) & 0xff) as u8;
|
||||
packet_idx += 1;
|
||||
}
|
||||
@@ -275,7 +271,7 @@ impl PrimaryHeader {
|
||||
/// Length of primary header when written to bytes.
|
||||
#[inline(always)]
|
||||
pub fn len_header(&self) -> usize {
|
||||
7 + self.vc_frame_count_len as usize
|
||||
7 + self.vc_frame_count_len.as_usize()
|
||||
}
|
||||
|
||||
/// Length of the entire frame.
|
||||
@@ -384,6 +380,27 @@ pub struct TransferFrameDataFieldHeader {
|
||||
}
|
||||
|
||||
impl TransferFrameDataFieldHeader {
|
||||
/// Constructor for the transfer frame data field header.
|
||||
///
|
||||
/// This constructor also checks whether the passed first header pointer or last valid octet
|
||||
/// field is compatible to the construction rule.
|
||||
pub const fn new(
|
||||
construction_rule: ConstructionRule,
|
||||
uslp_protocol_id: UslpProtocolId,
|
||||
fhp_or_lvo: Option<u16>,
|
||||
) -> Result<Self, FhpLvoError> {
|
||||
if (construction_rule.applicable_to_fixed_len_tfdz() && fhp_or_lvo.is_none())
|
||||
|| (!construction_rule.applicable_to_fixed_len_tfdz() && fhp_or_lvo.is_some())
|
||||
{
|
||||
return Err(FhpLvoError(construction_rule));
|
||||
}
|
||||
Ok(Self {
|
||||
construction_rule,
|
||||
uslp_protocol_id,
|
||||
fhp_or_lvo,
|
||||
})
|
||||
}
|
||||
|
||||
/// Length of the header when written to bytes.
|
||||
#[inline]
|
||||
pub const fn len_header(&self) -> usize {
|
||||
@@ -441,6 +458,103 @@ impl TransferFrameDataFieldHeader {
|
||||
fhp_or_lvo,
|
||||
})
|
||||
}
|
||||
|
||||
/// Write [self] to the provided byte buffer.
|
||||
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
let full_len = self.len_header();
|
||||
if buf.len() < full_len {
|
||||
return Err(ByteConversionError::ToSliceTooSmall {
|
||||
found: buf.len(),
|
||||
expected: full_len,
|
||||
});
|
||||
}
|
||||
buf[0] = ((self.construction_rule as u8) << 5) | (self.uslp_protocol_id as u8 & 0b11111);
|
||||
if let Some(fhp_or_lvo) = self.fhp_or_lvo {
|
||||
buf[1..3].copy_from_slice(&fhp_or_lvo.to_be_bytes());
|
||||
}
|
||||
Ok(full_len)
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple USLP transfer frame creator.
|
||||
pub struct TransferFrameCreator<'data> {
|
||||
primary_header: PrimaryHeader,
|
||||
data_field_header: TransferFrameDataFieldHeader,
|
||||
data: &'data [u8],
|
||||
operational_control_field: Option<u32>,
|
||||
has_fecf: bool,
|
||||
}
|
||||
|
||||
impl<'data> TransferFrameCreator<'data> {
|
||||
/// Constructor.
|
||||
///
|
||||
/// If the operational control field is present, the OCF flag in the primary header will
|
||||
/// be set accordingly. The frame length field of the [PrimaryHeader] is also updated according
|
||||
/// to the provided arguments.
|
||||
pub fn new(
|
||||
mut primary_header: PrimaryHeader,
|
||||
data_field_header: TransferFrameDataFieldHeader,
|
||||
data: &'data [u8],
|
||||
op_control_field: Option<u32>,
|
||||
has_fecf: bool,
|
||||
) -> Self {
|
||||
if op_control_field.is_some() {
|
||||
primary_header.ocf_flag = true;
|
||||
}
|
||||
Self {
|
||||
primary_header,
|
||||
data_field_header,
|
||||
data,
|
||||
operational_control_field: op_control_field,
|
||||
has_fecf,
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of the frame when written to bytes.
|
||||
pub fn len_written(&self) -> usize {
|
||||
self.primary_header.len_header()
|
||||
+ self.data_field_header.len_header()
|
||||
+ self.data.len()
|
||||
+ if self.operational_control_field.is_some() {
|
||||
4
|
||||
} else {
|
||||
0
|
||||
}
|
||||
+ if self.has_fecf { 2 } else { 0 }
|
||||
}
|
||||
|
||||
/// Write [self] to the provided byte buffer.
|
||||
pub fn write_to_bytes(&mut self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
let full_len = self.len_written();
|
||||
if full_len > buf.len() {
|
||||
return Err(ByteConversionError::ToSliceTooSmall {
|
||||
found: buf.len(),
|
||||
expected: full_len,
|
||||
});
|
||||
}
|
||||
let mut current_index = 0;
|
||||
self.primary_header.set_frame_len(full_len);
|
||||
current_index += self.primary_header.write_to_bytes(buf)?;
|
||||
|
||||
current_index += self
|
||||
.data_field_header
|
||||
.write_to_bytes(&mut buf[self.primary_header.len_header()..])?;
|
||||
buf[current_index..current_index + self.data.len()].copy_from_slice(self.data);
|
||||
current_index += self.data.len();
|
||||
|
||||
if let Some(ocf) = self.operational_control_field {
|
||||
buf[current_index..current_index + 4].copy_from_slice(&ocf.to_be_bytes());
|
||||
current_index += 4;
|
||||
}
|
||||
if self.has_fecf {
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
digest.update(&buf[0..current_index]);
|
||||
let crc = digest.finalize();
|
||||
buf[current_index..current_index + 2].copy_from_slice(&crc.to_be_bytes());
|
||||
current_index += 2;
|
||||
}
|
||||
Ok(current_index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple USLP transfer frame reader.
|
||||
@@ -485,7 +599,6 @@ impl<'buf> TransferFrameReader<'buf> {
|
||||
.unwrap(),
|
||||
));
|
||||
}
|
||||
let data_end = data_idx + data_len;
|
||||
if has_fecf {
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
digest.update(&buf[0..frame_len]);
|
||||
@@ -498,7 +611,7 @@ impl<'buf> TransferFrameReader<'buf> {
|
||||
Ok(Self {
|
||||
primary_header,
|
||||
data_field_header,
|
||||
data: buf[data_idx..data_end].try_into().unwrap(),
|
||||
data: buf[data_idx..data_idx + data_len].try_into().unwrap(),
|
||||
operational_control_field,
|
||||
})
|
||||
}
|
||||
@@ -566,15 +679,15 @@ mod tests {
|
||||
let primary_header = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b1010,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0x2345,
|
||||
)
|
||||
.unwrap();
|
||||
// Virtual channel count 0.
|
||||
assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 7);
|
||||
assert_eq!(primary_header.write_to_bytes(&mut buf).unwrap(), 7);
|
||||
common_basic_check(&buf);
|
||||
assert_eq!(primary_header.vc_frame_count_len(), 0);
|
||||
assert_eq!(primary_header.vc_frame_count_len().value(), 0);
|
||||
assert_eq!(primary_header.vc_frame_count(), 0);
|
||||
// Bypass / Sequence Control Flag.
|
||||
assert_eq!(
|
||||
@@ -600,8 +713,8 @@ mod tests {
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b1010,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0x2345,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -609,10 +722,12 @@ mod tests {
|
||||
primary_header.protocol_control_command_flag =
|
||||
ProtocolControlCommandFlag::TfdfContainsProtocolInfo;
|
||||
primary_header.ocf_flag = true;
|
||||
primary_header.set_vc_frame_count(4, 0x12345678).unwrap();
|
||||
primary_header
|
||||
.set_vc_frame_count(u3::new(4), 0x12345678)
|
||||
.unwrap();
|
||||
// Virtual channel count 4.
|
||||
assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 11);
|
||||
assert_eq!(primary_header.vc_frame_count_len(), 4);
|
||||
assert_eq!(primary_header.write_to_bytes(&mut buf).unwrap(), 11);
|
||||
assert_eq!(primary_header.vc_frame_count_len().value(), 4);
|
||||
assert_eq!(primary_header.vc_frame_count(), 0x12345678);
|
||||
common_basic_check(&buf);
|
||||
// Bypass / Sequence Control Flag.
|
||||
@@ -643,20 +758,20 @@ mod tests {
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b1010,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0x2345,
|
||||
)
|
||||
.unwrap();
|
||||
primary_header.set_vc_frame_count(2, 5).unwrap();
|
||||
assert_eq!(primary_header.vc_frame_count_len(), 2);
|
||||
primary_header.set_vc_frame_count(u3::new(2), 5).unwrap();
|
||||
assert_eq!(primary_header.vc_frame_count_len().value(), 2);
|
||||
assert_eq!(primary_header.vc_frame_count(), 5);
|
||||
assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 9);
|
||||
assert_eq!(primary_header.write_to_bytes(&mut buf).unwrap(), 9);
|
||||
assert_eq!(buf[6] & 0b111, 2);
|
||||
assert_eq!(u16::from_be_bytes(buf[7..9].try_into().unwrap()), 5);
|
||||
|
||||
let primary_header = PrimaryHeader::from_bytes(&buf).unwrap();
|
||||
assert_eq!(primary_header.vc_frame_count_len(), 2);
|
||||
assert_eq!(primary_header.vc_frame_count_len().value(), 2);
|
||||
assert_eq!(primary_header.vc_frame_count(), 5);
|
||||
}
|
||||
|
||||
@@ -668,20 +783,20 @@ mod tests {
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b1010,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0x2345,
|
||||
)
|
||||
.unwrap();
|
||||
primary_header.set_vc_frame_count(1, 255).unwrap();
|
||||
assert_eq!(primary_header.vc_frame_count_len(), 1);
|
||||
primary_header.set_vc_frame_count(u3::new(1), 255).unwrap();
|
||||
assert_eq!(primary_header.vc_frame_count_len().value(), 1);
|
||||
assert_eq!(primary_header.vc_frame_count(), 255);
|
||||
assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 8);
|
||||
assert_eq!(primary_header.write_to_bytes(&mut buf).unwrap(), 8);
|
||||
assert_eq!(buf[6] & 0b111, 1);
|
||||
assert_eq!(buf[7], 255);
|
||||
|
||||
let primary_header = PrimaryHeader::from_bytes(&buf).unwrap();
|
||||
assert_eq!(primary_header.vc_frame_count_len(), 1);
|
||||
assert_eq!(primary_header.vc_frame_count_len().value(), 1);
|
||||
assert_eq!(primary_header.vc_frame_count(), 255);
|
||||
}
|
||||
|
||||
@@ -691,12 +806,12 @@ mod tests {
|
||||
let primary_header = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b1010,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0x2345,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 7);
|
||||
assert_eq!(primary_header.write_to_bytes(&mut buf).unwrap(), 7);
|
||||
let parsed_header = PrimaryHeader::from_bytes(&buf).unwrap();
|
||||
assert_eq!(parsed_header, primary_header);
|
||||
}
|
||||
@@ -707,8 +822,8 @@ mod tests {
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b1010,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0x2345,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -716,57 +831,37 @@ mod tests {
|
||||
primary_header.protocol_control_command_flag =
|
||||
ProtocolControlCommandFlag::TfdfContainsProtocolInfo;
|
||||
primary_header.ocf_flag = true;
|
||||
primary_header.set_vc_frame_count(4, 0x12345678).unwrap();
|
||||
assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 11);
|
||||
primary_header
|
||||
.set_vc_frame_count(u3::new(4), 0x12345678)
|
||||
.unwrap();
|
||||
assert_eq!(primary_header.write_to_bytes(&mut buf).unwrap(), 11);
|
||||
let parsed_header = PrimaryHeader::from_bytes(&buf).unwrap();
|
||||
assert_eq!(parsed_header, primary_header);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_vcid() {
|
||||
let error = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b1101011,
|
||||
0b1010,
|
||||
0x2345,
|
||||
);
|
||||
assert!(error.is_err());
|
||||
let error = error.unwrap_err();
|
||||
matches!(error, UslpError::InvalidVcId(0b1101011));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_mapid() {
|
||||
let error = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b10101,
|
||||
0x2345,
|
||||
);
|
||||
assert!(error.is_err());
|
||||
let error = error.unwrap_err();
|
||||
matches!(error, UslpError::InvalidMapId(0b10101));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_vc_count() {
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b1010,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0x2345,
|
||||
)
|
||||
.unwrap();
|
||||
matches!(
|
||||
primary_header.set_vc_frame_count(0, 1).unwrap_err(),
|
||||
InvalidValueForLenError { value: 1, len: 0 }
|
||||
primary_header.set_vc_frame_count(u3::ZERO, 1).unwrap_err(),
|
||||
InvalidValueForLenError {
|
||||
value: 1,
|
||||
len: u3::ZERO
|
||||
}
|
||||
);
|
||||
matches!(
|
||||
primary_header.set_vc_frame_count(1, 256).unwrap_err(),
|
||||
InvalidValueForLenError { value: 256, len: 1 }
|
||||
let len = u3::new(1);
|
||||
assert_eq!(
|
||||
primary_header
|
||||
.set_vc_frame_count(u3::new(1), 256)
|
||||
.unwrap_err(),
|
||||
InvalidValueForLenError { value: 256, len }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -774,15 +869,21 @@ mod tests {
|
||||
fn test_frame_parser() {
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
// Build a variable frame manually.
|
||||
let mut primary_header =
|
||||
PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0x01,
|
||||
SourceOrDestField::Dest,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
let header_len = primary_header.len_header();
|
||||
buf[header_len] = ((ConstructionRule::NoSegmentation as u8) << 5)
|
||||
| (UslpProtocolId::UserDefinedOctetStream as u8) & 0b11111;
|
||||
buf[header_len + 1] = 0x42;
|
||||
// 1 byte TFDH, 1 byte data, 2 bytes CRC.
|
||||
primary_header.set_frame_len(header_len + 4);
|
||||
primary_header.write_to_be_bytes(&mut buf).unwrap();
|
||||
primary_header.write_to_bytes(&mut buf).unwrap();
|
||||
// Calculate and write CRC16.
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
digest.update(&buf[0..header_len + 2]);
|
||||
@@ -809,15 +910,21 @@ mod tests {
|
||||
fn test_frame_parser_invalid_checksum() {
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
// Build a variable frame manually.
|
||||
let mut primary_header =
|
||||
PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0x01,
|
||||
SourceOrDestField::Dest,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
let header_len = primary_header.len_header();
|
||||
buf[header_len] = ((ConstructionRule::NoSegmentation as u8) << 5)
|
||||
| (UslpProtocolId::UserDefinedOctetStream as u8) & 0b11111;
|
||||
buf[header_len + 1] = 0x42;
|
||||
// 1 byte TFDH, 1 byte data, 2 bytes CRC.
|
||||
primary_header.set_frame_len(header_len + 4);
|
||||
primary_header.write_to_be_bytes(&mut buf).unwrap();
|
||||
primary_header.write_to_bytes(&mut buf).unwrap();
|
||||
// Now parse the frame without having calculated the checksum.
|
||||
match TransferFrameReader::from_bytes(&buf, true) {
|
||||
Ok(_) => panic!("transfer frame read call did not fail"),
|
||||
@@ -831,15 +938,21 @@ mod tests {
|
||||
fn test_frame_parser_buf_too_small() {
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
// Build a variable frame manually.
|
||||
let mut primary_header =
|
||||
PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0x01,
|
||||
SourceOrDestField::Dest,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
let header_len = primary_header.len_header();
|
||||
buf[header_len] = ((ConstructionRule::NoSegmentation as u8) << 5)
|
||||
| (UslpProtocolId::UserDefinedOctetStream as u8) & 0b11111;
|
||||
buf[header_len + 1] = 0x42;
|
||||
// 1 byte TFDH, 1 byte data, 2 bytes CRC.
|
||||
primary_header.set_frame_len(header_len + 4);
|
||||
primary_header.write_to_be_bytes(&mut buf).unwrap();
|
||||
primary_header.write_to_bytes(&mut buf).unwrap();
|
||||
// Now parse the frame.
|
||||
let error = TransferFrameReader::from_bytes(&buf[0..7], true).unwrap_err();
|
||||
assert_eq!(
|
||||
@@ -881,9 +994,15 @@ mod tests {
|
||||
#[test]
|
||||
fn test_from_bytes_truncated_not_supported() {
|
||||
let mut buf: [u8; 7] = [0; 7];
|
||||
let primary_header =
|
||||
PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
|
||||
primary_header.write_to_be_bytes(&mut buf).unwrap();
|
||||
let primary_header = PrimaryHeader::new(
|
||||
0x01,
|
||||
SourceOrDestField::Dest,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
primary_header.write_to_bytes(&mut buf).unwrap();
|
||||
// Set truncated header flag manually.
|
||||
buf[3] |= 0b1;
|
||||
assert_eq!(
|
||||
@@ -900,13 +1019,15 @@ mod tests {
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b1010,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0x2345,
|
||||
)
|
||||
.unwrap();
|
||||
primary_header.set_vc_frame_count(4, 0x12345678).unwrap();
|
||||
primary_header.write_to_be_bytes(&mut buf).unwrap();
|
||||
primary_header
|
||||
.set_vc_frame_count(u3::new(4), 0x12345678)
|
||||
.unwrap();
|
||||
primary_header.write_to_bytes(&mut buf).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
PrimaryHeader::from_bytes(&buf[0..8]).unwrap_err(),
|
||||
@@ -920,9 +1041,15 @@ mod tests {
|
||||
#[test]
|
||||
fn test_invalid_version_number() {
|
||||
let mut buf: [u8; 7] = [0; 7];
|
||||
let primary_header =
|
||||
PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
|
||||
primary_header.write_to_be_bytes(&mut buf).unwrap();
|
||||
let primary_header = PrimaryHeader::new(
|
||||
0x01,
|
||||
SourceOrDestField::Dest,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
primary_header.write_to_bytes(&mut buf).unwrap();
|
||||
buf[0] &= 0b00001111;
|
||||
assert_eq!(
|
||||
PrimaryHeader::from_bytes(&buf).unwrap_err(),
|
||||
@@ -935,13 +1062,13 @@ mod tests {
|
||||
let primary_header = PrimaryHeader::new(
|
||||
0b10100101_11000011,
|
||||
SourceOrDestField::Dest,
|
||||
0b110101,
|
||||
0b1010,
|
||||
u6::new(0b110101),
|
||||
u4::new(0b1010),
|
||||
0x2345,
|
||||
)
|
||||
.unwrap();
|
||||
if let Err(ByteConversionError::ToSliceTooSmall { found, expected }) =
|
||||
primary_header.write_to_be_bytes(&mut [0; 4])
|
||||
primary_header.write_to_bytes(&mut [0; 4])
|
||||
{
|
||||
assert_eq!(found, 4);
|
||||
assert_eq!(expected, 7);
|
||||
@@ -1002,4 +1129,71 @@ mod tests {
|
||||
.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_frame_creator() {
|
||||
// Relying on the reader implementation for now.
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0x1234,
|
||||
SourceOrDestField::Source,
|
||||
u6::new(0b101010),
|
||||
u4::new(0b0101),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
let data_field_header = TransferFrameDataFieldHeader {
|
||||
construction_rule: ConstructionRule::NoSegmentation,
|
||||
uslp_protocol_id: UslpProtocolId::UserDefinedOctetStream,
|
||||
fhp_or_lvo: None,
|
||||
};
|
||||
let data = [1, 2, 3, 4];
|
||||
let mut frame_creator =
|
||||
TransferFrameCreator::new(primary_header, data_field_header, &data, None, true);
|
||||
let mut buf: [u8; 64] = [0; 64];
|
||||
assert_eq!(frame_creator.len_written(), 14);
|
||||
let written = frame_creator.write_to_bytes(&mut buf).unwrap();
|
||||
assert_eq!(written, 14);
|
||||
assert_eq!(written, frame_creator.len_written());
|
||||
let reader = TransferFrameReader::from_bytes(&buf, true).unwrap();
|
||||
primary_header.set_frame_len(written);
|
||||
assert_eq!(reader.primary_header(), &primary_header);
|
||||
assert_eq!(reader.data_field_header(), &data_field_header);
|
||||
assert_eq!(reader.data(), &data);
|
||||
assert!(reader.operational_control_field().is_none());
|
||||
assert_eq!(reader.len_frame(), 14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_frame_creator_with_op_ctrl() {
|
||||
// Relying on the reader implementation for now.
|
||||
let mut primary_header = PrimaryHeader::new(
|
||||
0x1234,
|
||||
SourceOrDestField::Source,
|
||||
u6::new(0b101010),
|
||||
u4::new(0b0101),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
let data_field_header = TransferFrameDataFieldHeader::new(
|
||||
ConstructionRule::NoSegmentation,
|
||||
UslpProtocolId::UserDefinedOctetStream,
|
||||
None,
|
||||
).unwrap();
|
||||
let data = [1, 2, 3, 4];
|
||||
let mut frame_creator =
|
||||
TransferFrameCreator::new(primary_header, data_field_header, &data, Some(4), true);
|
||||
let mut buf: [u8; 64] = [0; 64];
|
||||
assert_eq!(frame_creator.len_written(), 18);
|
||||
let written = frame_creator.write_to_bytes(&mut buf).unwrap();
|
||||
assert_eq!(written, 18);
|
||||
assert_eq!(written, frame_creator.len_written());
|
||||
let reader = TransferFrameReader::from_bytes(&buf, true).unwrap();
|
||||
primary_header.set_frame_len(written);
|
||||
primary_header.ocf_flag = true;
|
||||
assert_eq!(reader.primary_header(), &primary_header);
|
||||
assert_eq!(reader.data_field_header(), &data_field_header);
|
||||
assert_eq!(reader.data(), &data);
|
||||
assert_eq!(reader.operational_control_field().unwrap(), 4);
|
||||
assert_eq!(reader.len_frame(), 18);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user