Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

COBS with footer PoC #17

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
//!
//! let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
//! let buffer = &mut [0u8; 32];
//! let res = serialize_with_flavor::<[u8], Cobs<Slice>, &mut [u8]>(
//! let res = serialize_with_flavor(
//! data,
//! Cobs::try_new(Slice::new(buffer)).unwrap(),
//! ).unwrap();
Expand Down Expand Up @@ -167,9 +167,7 @@ mod varint;
pub use de::deserializer::Deserializer;
pub use de::{from_bytes, from_bytes_cobs, take_from_bytes, take_from_bytes_cobs};
pub use error::{Error, Result};
pub use ser::{
flavors, serialize_with_flavor, serializer::Serializer, to_slice, to_slice_cobs,
};
pub use ser::{flavors, serialize_with_flavor, serializer::Serializer, to_slice, to_slice_cobs};

#[cfg(feature = "heapless")]
pub use ser::{to_vec, to_vec_cobs};
Expand Down
80 changes: 70 additions & 10 deletions src/ser/flavors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
//!
//! let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
//! let buffer = &mut [0u8; 32];
//! let res = serialize_with_flavor::<[u8], Cobs<Slice>, &mut [u8]>(
//! let res = serialize_with_flavor(
//! data,
//! Cobs::try_new(Slice::new(buffer)).unwrap(),
//! ).unwrap();
Expand Down Expand Up @@ -183,10 +183,10 @@ impl<'a> IndexMut<usize> for Slice<'a> {

#[cfg(feature = "heapless")]
mod heapless_vec {
use heapless::{ArrayLength, Vec};
use super::SerFlavor;
use super::Index;
use super::IndexMut;
use super::SerFlavor;
use heapless::{ArrayLength, Vec};

////////////////////////////////////////
// HVec
Expand Down Expand Up @@ -241,10 +241,10 @@ mod heapless_vec {
#[cfg(feature = "use-std")]
mod std_vec {
extern crate std;
use std::vec::Vec;
use super::SerFlavor;
use super::Index;
use super::IndexMut;
use super::SerFlavor;
use std::vec::Vec;

/// The `StdVec` flavor is a wrapper type around a `std::vec::Vec`.
///
Expand Down Expand Up @@ -289,10 +289,10 @@ mod std_vec {
#[cfg(feature = "alloc")]
mod alloc_vec {
extern crate alloc;
use alloc::vec::Vec;
use super::SerFlavor;
use super::Index;
use super::IndexMut;
use super::SerFlavor;
use alloc::vec::Vec;

/// The `AllocVec` flavor is a wrapper type around an `alloc::vec::Vec`.
///
Expand Down Expand Up @@ -338,6 +338,35 @@ mod alloc_vec {
// Modification Flavors
////////////////////////////////////////////////////////////////////////////////

use core::convert::AsRef;

/// The SerFlavor trait acts as a layer to generate footers in modification flavors, this can
/// commonly be a hash or CRC in packets.
pub trait FooterGenerator<T: AsRef<[u8]>> {
/// This feeds the generator with data to generate a footer
fn feed(&mut self, data: u8);
/// This returns the final footer
fn finalize(&mut self) -> T;
}

#[doc(hidden)]
pub struct NoFooter;

impl NoFooter {
fn new() -> Self {
NoFooter {}
}
}

impl FooterGenerator<[u8; 0]> for NoFooter {
#[inline(always)]
fn feed(&mut self, _data: u8) {}

fn finalize(&mut self) -> [u8; 0] {
[0; 0]
}
}

////////////////////////////////////////
// COBS
////////////////////////////////////////
Expand All @@ -349,15 +378,19 @@ mod alloc_vec {
/// This protocol is useful when sending data over a serial interface without framing such as a UART
///
/// [Consistent Overhead Byte Stuffing]: https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
pub struct Cobs<B>
pub struct Cobs<B, F, T>
where
B: SerFlavor + IndexMut<usize, Output = u8>,
F: FooterGenerator<T>,
T: AsRef<[u8]>,
{
flav: B,
footer: F,
cobs: EncoderState,
_t: core::marker::PhantomData<T>,
}

impl<B> Cobs<B>
impl<B> Cobs<B, NoFooter, [u8; 0]>
where
B: SerFlavor + IndexMut<usize, Output = u8>,
{
Expand All @@ -367,20 +400,44 @@ where
bee.try_push(0).map_err(|_| Error::SerializeBufferFull)?;
Ok(Self {
flav: bee,
footer: NoFooter::new(),
cobs: EncoderState::default(),
_t: core::marker::PhantomData,
})
}
}

impl<'a, B> SerFlavor for Cobs<B>
impl<B, F, T> Cobs<B, F, T>
where
B: SerFlavor + IndexMut<usize, Output = u8>,
F: FooterGenerator<T>,
T: AsRef<[u8]>,
{
/// Create a new Cobs modifier Flavor. If there is insufficient space
/// to push the leading header byte, the method will return an Error
pub fn try_new_with_footer(mut bee: B, footer: F) -> Result<Self> {
bee.try_push(0).map_err(|_| Error::SerializeBufferFull)?;
Ok(Self {
flav: bee,
footer,
cobs: EncoderState::default(),
_t: core::marker::PhantomData,
})
}
}

impl<'a, B, F, T> SerFlavor for Cobs<B, F, T>
where
B: SerFlavor + IndexMut<usize, Output = u8>,
F: FooterGenerator<T>,
T: AsRef<[u8]>,
{
type Output = <B as SerFlavor>::Output;

#[inline(always)]
fn try_push(&mut self, data: u8) -> core::result::Result<(), ()> {
use PushResult::*;
self.footer.feed(data);
match self.cobs.push(data) {
AddSingle(n) => self.flav.try_push(n),
ModifyFromStartAndSkip((idx, mval)) => {
Expand All @@ -396,6 +453,9 @@ where
}

fn release(mut self) -> core::result::Result<Self::Output, ()> {
for b in self.footer.finalize().as_ref() {
self.try_push(*b)?;
}
let (idx, mval) = self.cobs.finalize();
self.flav[idx] = mval;
self.flav.try_push(0)?;
Expand Down
49 changes: 41 additions & 8 deletions src/ser/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::Serialize;
use crate::error::{Error, Result};
use crate::ser::flavors::{Cobs, SerFlavor, Slice};
use serde::Serialize;

#[cfg(feature = "heapless")]
use crate::ser::flavors::HVec;
Expand Down Expand Up @@ -54,10 +54,7 @@ pub fn to_slice_cobs<'a, 'b, T>(value: &'b T, buf: &'a mut [u8]) -> Result<&'a m
where
T: Serialize + ?Sized,
{
serialize_with_flavor::<T, Cobs<Slice<'a>>, &'a mut [u8]>(
value,
Cobs::try_new(Slice::new(buf))?,
)
serialize_with_flavor(value, Cobs::try_new(Slice::new(buf))?)
}

/// Serialize a `T` to the given slice, with the resulting slice containing
Expand Down Expand Up @@ -128,7 +125,7 @@ where
T: Serialize + ?Sized,
B: ArrayLength<u8>,
{
serialize_with_flavor::<T, Cobs<HVec<_>>, Vec<u8, B>>(value, Cobs::try_new(HVec::default())?)
serialize_with_flavor(value, Cobs::try_new(HVec::default())?)
}

/// Serialize a `T` to a `heapless::Vec<u8>`, with the `Vec` containing
Expand Down Expand Up @@ -230,7 +227,10 @@ pub fn to_allocvec<T>(value: &T) -> Result<alloc::vec::Vec<u8>>
where
T: Serialize + ?Sized,
{
serialize_with_flavor::<T, AllocVec, alloc::vec::Vec<u8>>(value, AllocVec(alloc::vec::Vec::new()))
serialize_with_flavor::<T, AllocVec, alloc::vec::Vec<u8>>(
value,
AllocVec(alloc::vec::Vec::new()),
)
}

/// Serialize and COBS encode a `T` to an `alloc::vec::Vec<u8>`. Requires the `alloc` feature.
Expand Down Expand Up @@ -278,7 +278,7 @@ where
///
/// let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
/// let buffer = &mut [0u8; 32];
/// let res = serialize_with_flavor::<[u8], Cobs<Slice>, &mut [u8]>(
/// let res = serialize_with_flavor(
/// data,
/// Cobs::try_new(Slice::new(buffer)).unwrap(),
/// ).unwrap();
Expand All @@ -302,6 +302,7 @@ where
#[cfg(test)]
mod test {
use super::*;
use crate::from_bytes_cobs;
use crate::varint::VarintUsize;
use core::fmt::Write;
use core::ops::{Deref, DerefMut};
Expand Down Expand Up @@ -587,4 +588,36 @@ mod test {

assert_eq!(input, x);
}

struct MyFooter {
state: u16,
}

impl crate::flavors::FooterGenerator<[u8; 2]> for MyFooter {
fn feed(&mut self, data: u8) {
self.state += data as u16;
}

fn finalize(&mut self) -> [u8; 2] {
self.state.to_le_bytes()
}
}

#[test]
fn cobs_footer_test() {
let data: [u8; 4] = [0x01, 0x00, 0x20, 0x30];
let buffer = &mut [0; 32];
let res = serialize_with_flavor(
&data,
Cobs::try_new_with_footer(Slice::new(buffer), MyFooter { state: 0 }).unwrap(),
)
.unwrap();

assert_eq!(res, &[0x02, 0x01, 0x04, 0x20, 0x30, 0x51, 0x01, 0x00]);

let res: ([u8; 4], [u8; 2]) = from_bytes_cobs(res).unwrap();

assert_eq!(res.0, data);
assert_eq!(res.1, (data.iter().sum::<u8>() as u16).to_le_bytes());
}
}