Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ CRATES=("embedded-command-macros" "serac" "dispatch-bundle")
for TARGET in "${TARGETS[@]}"; do
rustup target add "$TARGET"
for CRATE in "${CRATES[@]}"; do
cargo build -p "$CRATE" --target "$TARGET"
cargo build -p "$CRATE" --all-features --target "$TARGET"
done
done

# tests

for CRATE in "${CRATES[@]}"; do
cargo test -p "$CRATE"
cargo test -p "$CRATE" --all-features
done

# miri
Expand Down
7 changes: 6 additions & 1 deletion serac/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "serac"
version = "0.4.7"
version = "0.4.8"
edition = "2024"
description = "A static, modular, and light-weight serialization framework."
license = "CC-BY-NC-SA-4.0"
Expand All @@ -15,10 +15,15 @@ panic-halt = { version = "1.0.0", optional = true }
cortex-m = { version = "0.7.7", optional = true }
cortex-m-rt = { version = "0.7.3", optional = true }
defmt = { version = "0.3.10", optional = true }
heapless = { version = "0.9.3", optional = true }

[dev-dependencies]
itertools = { version = "0.15.0", default-features = false }

[features]
binary = ["dep:panic-halt", "dep:cortex-m", "dep:cortex-m-rt"]
defmt = ["dep:defmt"]
heapless = ["dep:heapless"]

[[bin]]
name = "asm"
Expand Down
3 changes: 3 additions & 0 deletions serac/src/encoding/vanilla.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[cfg(feature = "heapless")]
pub mod heapless;

use core::{marker::PhantomData, mem::MaybeUninit};

use fill_array::fill;
Expand Down
136 changes: 136 additions & 0 deletions serac/src/encoding/vanilla/heapless.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! Vanilla encoding implementations for types from the [`heapless`] crate.

use heapless::Vec;

use crate::{SerializeIter, Size, error};

pub type VecU8<T, const N: usize> = Vec<T, N, u8>;
pub type VecU16<T, const N: usize> = Vec<T, N, u16>;
pub type VecU32<T, const N: usize> = Vec<T, N, u32>;
pub type VecU64<T, const N: usize> = Vec<T, N, u64>;

impl<T: SerializeIter, LenT: heapless::LenType + SerializeIter, const N: usize> SerializeIter
for Vec<T, N, LenT>
{
fn ser<'a>(
&self,
dst: &mut crate::Buf<
impl Iterator<Item = &'a mut <super::Vanilla as crate::Encoding>::Word>,
>,
) -> Result<(), crate::error::EndOfInput>
where
<super::Vanilla as crate::Encoding>::Word: 'a,
{
LenT::from_usize(self.len()).ser(dst)?;

for e in self {
e.ser(dst)?;
}

Ok(())
}

fn de<'a>(
src: &mut crate::Buf<impl Iterator<Item = &'a <super::Vanilla as crate::Encoding>::Word>>,
) -> Result<Self, crate::error::Error>
where
<super::Vanilla as crate::Encoding>::Word: 'a,
{
let mut vec = Vec::new();

let len = LenT::de(src)?;

// invariant 0: encoded length must not exceed vec capacity
if len.into_usize() > N {
Err(error::Invalid)?
}

for _ in 0..len.into_usize() {
// SAFETY: room is ensured by invariant 0
unsafe { vec.push_unchecked(T::de(src)?) };
}

Ok(vec)
}
}

// SAFETY: size of len + size of element * number of elements
unsafe impl<T: Size, LenT: heapless::LenType + Size, const N: usize> Size for Vec<T, N, LenT> {
const SIZE: usize = LenT::SIZE + T::SIZE * N;
}

#[cfg(test)]
mod tests {
mod vec {

use crate::{
self as serac, SerializeIter, Size, buf,
encoding::vanilla::heapless::{VecU8, VecU32},
};

#[test]
fn simple() {
let v = const { VecU8::<u8, 255>::from_array([5, 4, 3, 2, 1, 0]) };

let mut buf = buf!(VecU8<u8, 255>);

v.serialize_iter(&mut buf).expect("vec should fit in buf");

let readback = VecU8::<u8, 255>::deserialize_iter(&buf)
.expect("vec should deserialize successfully");

itertools::assert_equal(v, readback);
}

#[test]
fn shorter() {
let v = const { VecU8::<u8, 255>::from_array([5, 4, 3, 2, 1, 0]) };

let mut buf = buf!(VecU8<u8, 255>);

v.serialize_iter(&mut buf).expect("vec should fit in buf");

let readback = VecU8::<u8, 6>::deserialize_iter(&buf)
.expect("vec should deserialize successfully");

itertools::assert_equal(v, readback);
}

#[test]
fn too_short() {
let v = const { VecU8::<u8, 255>::from_array([5, 4, 3, 2, 1, 0]) };

let mut buf = buf!(VecU8<u8, 255>);

v.serialize_iter(&mut buf).expect("vec should fit in buf");

let readback = VecU8::<u8, 5>::deserialize_iter(&buf);

assert!(
readback.is_err(),
"expected deserialization to fail since the vec has an insufficient capacity",
);
}

#[test]
fn buf_too_small() {
let v = const { VecU8::<u8, 255>::from_array([5, 4, 3, 2, 1, 0]) };

let mut buf = buf!(VecU8<u8, 5>);

assert!(
v.serialize_iter(&mut buf).is_err(),
"expected serialization to fail since the buffer has an insufficient capacity",
);
}

#[test]
fn len_type() {
assert_eq!(
VecU32::<(), 0>::SIZE,
<u32 as Size>::SIZE,
"expected empty vec with u32 length type to be the same size as u32",
);
}
}
}
Loading