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

Introduces CRCs #98

Merged
merged 4 commits into from
Apr 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
Cargo.lock
.vscode
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,18 @@ path = "./postcard-derive"
version = "0.1.1"
optional = true

[dependencies.crc]
version = "3.0.1"
optional = true

[features]
default = ["heapless-cas"]

use-std = ["serde/std", "alloc"]
heapless-cas = ["heapless", "heapless/cas"]
alloc = ["serde/alloc"]
use-defmt = ["defmt"]
use-crc = ["crc"]

# Experimental features!
#
Expand Down
187 changes: 87 additions & 100 deletions src/de/flavors.rs
jamesmunns marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -163,120 +163,107 @@ impl<'de> Flavor<'de> for Slice<'de> {
}
}

// This is a terrible checksum implementation to make sure that we can effectively
// use the deserialization flavor. This is kept as a test (and not published)
// because an 8-bit checksum is not ACTUALLY useful for almost anything.
//
// You could certainly do something similar with a CRC32, cryptographic sig,
// or something else
#[cfg(test)]
mod test {
use super::*;
use serde::{Deserialize, Serialize};
////////////////////////////////////////
// CRC
////////////////////////////////////////

struct Checksum<'de, F>
where
F: Flavor<'de> + 'de,
{
flav: F,
checksum: u8,
_plt: PhantomData<&'de ()>,
}
/// This Cyclic Redundancy Check flavor applies [the CRC crate's `Algorithm`](https://docs.rs/crc/latest/crc/struct.Algorithm.html) struct on
/// the serialized data. The flavor will check the CRC assuming that it has been appended to the bytes.
///
/// CRCs are used for error detection when reading data back.
///
/// The `crc` feature requires enabling to use this module.
///
/// More on CRCs: https://en.wikipedia.org/wiki/Cyclic_redundancy_check.
#[cfg(feature = "crc")]
pub mod crc {
use core::convert::TryInto;

use crc::Digest;
use crc::Width;

impl<'de, F> Checksum<'de, F>
use super::Flavor;
use crate::Error;
use crate::Result;

/// Manages CRC modifications as a flavor.
pub struct CrcModifier<'de, B, W>
where
F: Flavor<'de> + 'de,
B: Flavor<'de>,
W: Width,
{
pub fn from_flav(flav: F) -> Self {
Self {
flav,
checksum: 0,
_plt: PhantomData,
}
}
flav: B,
digest: Digest<'de, W>,
}

impl<'de, F> Flavor<'de> for Checksum<'de, F>
impl<'de, B, W> CrcModifier<'de, B, W>
where
F: Flavor<'de> + 'de,
B: Flavor<'de>,
W: Width,
{
type Remainder = (<F as Flavor<'de>>::Remainder, u8);
type Source = F;

fn pop(&mut self) -> Result<u8> {
match self.flav.pop() {
Ok(u) => {
self.checksum = self.checksum.wrapping_add(u);
Ok(u)
}
Err(e) => Err(e),
}
}
fn try_take_n(&mut self, ct: usize) -> Result<&'de [u8]> {
match self.flav.try_take_n(ct) {
Ok(u) => {
u.iter().for_each(|u| {
self.checksum = self.checksum.wrapping_add(*u);
});
Ok(u)
}
Err(e) => Err(e),
}
}
fn finalize(self) -> Result<Self::Remainder> {
Ok((self.flav.finalize()?, self.checksum))
/// Create a new Crc modifier Flavor.
pub fn new(bee: B, digest: Digest<'de, W>) -> Self {
Self { flav: bee, digest }
}
}

#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
pub struct SomeData<'a> {
#[serde(borrow)]
sli: &'a [u8],
sts: &'a str,
foo: u64,
bar: u128,
}
macro_rules! impl_flavor {
($( $int:ty ),*) => {
$(
impl<'de, B> Flavor<'de> for CrcModifier<'de, B, $int>
where
B: Flavor<'de>,
{
type Remainder = B::Remainder;

#[test]
fn smoke() {
const EXPECTED: &[u8] = &[
4, 255, 1, 34, 51, 19, 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 103, 111, 111,
100, 32, 116, 101, 115, 116, 170, 213, 170, 213, 170, 213, 170, 213, 170, 1, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127,
];

// Calculate simple 8-bit checksum
let mut check: u8 = 0;
EXPECTED.iter().for_each(|u| check = check.wrapping_add(*u));

let mut buf = [0u8; 256];
let data = SomeData {
sli: &[0xFF, 0x01, 0x22, 0x33],
sts: "this is a good test",
foo: (u64::MAX / 3) * 2,
bar: u128::MAX / 4,
};
let used = crate::to_slice(&data, &mut buf).unwrap();
assert_eq!(used, EXPECTED);
let used = used.len();
type Source = B::Source;

// Put the checksum at the end
buf[used] = check;
#[inline]
fn pop(&mut self) -> Result<u8> {
match self.flav.pop() {
Ok(byte) => {
self.digest.update(&[byte]);
Ok(byte)
}
e @ Err(_) => e,
}
}

let mut deser = crate::de::Deserializer::from_flavor(Checksum::from_flav(Slice::new(&buf)));
#[inline]
fn try_take_n(&mut self, ct: usize) -> Result<&'de [u8]> {
match self.flav.try_take_n(ct) {
Ok(bytes) => {
self.digest.update(bytes);
Ok(bytes)
}
e @ Err(_) => e,
}
}

let t = SomeData::<'_>::deserialize(&mut deser).unwrap();
assert_eq!(t, data);

// Normally, you'd probably expect the check
let (rem, cksm) = deser.finalize().unwrap();

// The pre-calculated checksum we stuffed at the end is the
// first "unused" byte
assert_eq!(rem[0], check);

// the one we calculated during serialization matches the
// pre-calculated one
assert_eq!(cksm, check);
fn finalize(mut self) -> Result<Self::Remainder> {
match self.flav.try_take_n(core::mem::size_of::<$int>()) {
Ok(prev_crc_bytes) => match self.flav.finalize() {
Ok(remainder) => {
let crc = self.digest.finalize();
let le_bytes = prev_crc_bytes
.try_into()
.map_err(|_| Error::DeserializeBadEncoding)?;
let prev_crc = <$int>::from_le_bytes(le_bytes);
if crc == prev_crc {
Ok(remainder)
} else {
Err(Error::DeserializeBadEncoding)
}
}
e @ Err(_) => e,
},
Err(e) => Err(e),
}
}
}
)*
};
}

impl_flavor![u8, u16, u32, u64, u128];
}
75 changes: 73 additions & 2 deletions src/ser/flavors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ mod heapless_vec {
}
}

impl<'a, const B: usize> Flavor for HVec<B> {
jamesmunns marked this conversation as resolved.
Show resolved Hide resolved
impl<const B: usize> Flavor for HVec<B> {
type Output = Vec<u8, B>;

#[inline(always)]
Expand Down Expand Up @@ -380,7 +380,7 @@ where
}
}

impl<'a, B> Flavor for Cobs<B>
jamesmunns marked this conversation as resolved.
Show resolved Hide resolved
impl<B> Flavor for Cobs<B>
where
B: Flavor + IndexMut<usize, Output = u8>,
{
Expand Down Expand Up @@ -411,6 +411,77 @@ where
}
}

////////////////////////////////////////
// CRC
////////////////////////////////////////

/// This Cyclic Redundancy Check flavor applies [the CRC crate's `Algorithm`](https://docs.rs/crc/latest/crc/struct.Algorithm.html) struct on
/// the serialized data. The output of this flavor receives the CRC appended to the bytes.
///
/// CRCs are used for error detection when reading data back.
///
/// The `crc` feature requires enabling to use this module.
///
/// More on CRCs: https://en.wikipedia.org/wiki/Cyclic_redundancy_check.
#[cfg(feature = "crc")]
pub mod crc {
use crc::Digest;
use crc::Width;

use super::Flavor;
use crate::Result;

/// Manages CRC modifications as a flavor.
pub struct CrcModifier<'a, B, W>
where
B: Flavor,
W: Width,
{
flav: B,
digest: Digest<'a, W>,
}

impl<'a, B, W> CrcModifier<'a, B, W>
where
B: Flavor,
W: Width,
{
/// Create a new CRC modifier Flavor.
pub fn new(bee: B, digest: Digest<'a, W>) -> Self {
Self { flav: bee, digest }
}
}

macro_rules! impl_flavor {
($( $int:ty ),*) => {
$(
impl<'a, B> Flavor for CrcModifier<'a, B, $int>
where
B: Flavor,
{
type Output = <B as Flavor>::Output;

#[inline(always)]
fn try_push(&mut self, data: u8) -> Result<()> {
self.digest.update(&[data]);
self.flav.try_push(data)
}

fn finalize(mut self) -> Result<Self::Output> {
let crc = self.digest.finalize();
for byte in crc.to_le_bytes() {
self.flav.try_push(byte)?;
}
self.flav.finalize()
}
}
)*
};
}

impl_flavor![u8, u16, u32, u64, u128];
}

/// The `Size` flavor is a measurement flavor, which accumulates the number of bytes needed to
/// serialize the data.
///
Expand Down
29 changes: 29 additions & 0 deletions tests/crc.rs
jamesmunns marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#[test]
#[cfg(feature = "crc")]
fn test_crc() {
use crc::{Crc, CRC_32_ISCSI};
use postcard::{
de_flavors::{crc::CrcModifier as DeCrcModifier, Slice as DeSlice},
ser_flavors::{crc::CrcModifier as SerCrcModifier, Slice as SerSlice},
serialize_with_flavor, Deserializer,
};
use serde::de::Deserialize;

let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
let buffer = &mut [0u8; 32];
let crc = Crc::<u32>::new(&CRC_32_ISCSI);
let digest = crc.digest();
let res =
serialize_with_flavor(data, SerCrcModifier::new(SerSlice::new(buffer), digest)).unwrap();

assert_eq!(res, &[0x04, 0x01, 0x00, 0x20, 0x30, 0x8E, 0xC8, 0x1A, 0x37]);

let digest = crc.digest();
let flav = DeCrcModifier::new(DeSlice::new(&res), digest);
let mut deserializer = Deserializer::from_flavor(flav);
let res = <[u8; 5]>::deserialize(&mut deserializer).unwrap();

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

assert_eq!(deserializer.finalize().unwrap(), &[0u8; 0]);
}