From 81582a2023f9106e7a06f58bc091d232d0051a84 Mon Sep 17 00:00:00 2001 From: huntc Date: Tue, 4 Apr 2023 16:51:55 +1000 Subject: [PATCH] Introduces CRCs A `crc` feature has been added that brings in the `crc` crate and a new `CrcModifier` flavour. This modifier flavour is similar to the COBS implementation in how it wraps another flavour. --- .gitignore | 3 +- Cargo.toml | 5 ++ src/de/flavors.rs | 187 +++++++++++++++++++++------------------------ src/ser/flavors.rs | 75 +++++++++++++++++- tests/crc.rs | 29 +++++++ 5 files changed, 196 insertions(+), 103 deletions(-) create mode 100644 tests/crc.rs diff --git a/.gitignore b/.gitignore index 2f88dba..5cb9f6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk -Cargo.lock \ No newline at end of file +Cargo.lock +.vscode \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6498e55..313fa77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,10 @@ path = "./postcard-derive" version = "0.1.1" optional = true +[dependencies.crc] +version = "3.0.1" +optional = true + [features] default = ["heapless-cas"] @@ -59,6 +63,7 @@ use-std = ["serde/std", "alloc"] heapless-cas = ["heapless", "heapless/cas"] alloc = ["serde/alloc"] use-defmt = ["defmt"] +use-crc = ["crc"] # Experimental features! # diff --git a/src/de/flavors.rs b/src/de/flavors.rs index a81fdb2..5237749 100644 --- a/src/de/flavors.rs +++ b/src/de/flavors.rs @@ -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 = (>::Remainder, u8); - type Source = F; - - fn pop(&mut self) -> Result { - 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 { - 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 { + 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 { + 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]; } diff --git a/src/ser/flavors.rs b/src/ser/flavors.rs index f82c8c0..aec695f 100644 --- a/src/ser/flavors.rs +++ b/src/ser/flavors.rs @@ -236,7 +236,7 @@ mod heapless_vec { } } - impl<'a, const B: usize> Flavor for HVec { + impl Flavor for HVec { type Output = Vec; #[inline(always)] @@ -380,7 +380,7 @@ where } } -impl<'a, B> Flavor for Cobs +impl Flavor for Cobs where B: Flavor + IndexMut, { @@ -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 = ::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 { + 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. /// diff --git a/tests/crc.rs b/tests/crc.rs new file mode 100644 index 0000000..27e6acc --- /dev/null +++ b/tests/crc.rs @@ -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::::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]); +}