11 Commits

6 changed files with 526 additions and 83 deletions

View File

@@ -2,7 +2,7 @@ name: ci
on: [push, pull_request]
jobs:
check:
build:
name: Check build
strategy:
matrix:
@@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo check --release
- run: cargo build
test:
name: Run Tests
@@ -30,7 +30,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.83
- run: cargo check --release
- run: cargo check
cross-check:
name: Check Cross-Compilation
@@ -46,7 +46,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf, thumbv6m-none-eabi"
- run: cargo check --release --target=${{matrix.target}} --no-default-features
- run: cargo check --target=${{matrix.target}} --no-default-features
fmt:
name: Check formatting
@@ -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

View File

@@ -8,6 +8,27 @@ 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.
## 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

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -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);
}
}