serry 0.1.0

Serry - the unstructured Serde.
Documentation
use std::cmp::Ordering;
use std::fmt;
use std::fmt::{Debug, Formatter};
use crate::write::WriteResult;
use crate::{SerryError, SerryInput, SerryOutput, SerryRead, SerrySized, SerryWrite};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use crate::read::ReadResult;

pub use digest::Digest;

pub struct Checksummed<T, D> {
    pub inner: T,
    __digest: PhantomData<D>,
}

impl<T, D> Checksummed<T, D> {
    pub const fn new(value: T) -> Self {
        Self {
            inner: value,
            __digest: PhantomData
        }
    }
    fn checksum_size() -> usize where D: Digest {
        <D as Digest>::output_size()
    }
}

impl<T, D> SerryWrite for Checksummed<T, D> where T: SerryWrite, D: Digest {
    fn serry_write(&self, output: &mut impl SerryOutput) -> WriteResult<()> {
        let mut digest = D::new();
        let mut buf: Vec<u8> = Vec::new();
        buf.write_value(&self.inner)?;
        digest.update(&buf);
        let finalized = digest.finalize();
        output.write_value(&buf)?;
        output.write_value(finalized.as_slice())
    }
}

impl<T, D> SerryRead for Checksummed<T, D> where T: SerryRead, D: Digest {
    fn serry_read(input: &mut impl SerryInput) -> ReadResult<Self> {
        let value: Vec<u8> = input.read_value()?;
        let checksum: Vec<u8> = input.read_value()?;

        if checksum.len() != Self::checksum_size() {
            return Err(SerryError::custom("checksum size incorrect (possible algorithm mismatch)"));
        }

        let mut digest = D::new();
        digest.update(&value);
        let finalized = digest.finalize();
        if finalized.as_slice() != checksum {
            return Err(SerryError::custom("checksum incorrect"));
        }

        return Ok(Checksummed::new(T::serry_read(&mut value.as_slice())?));
    }
}

impl<T, D> SerrySized for Checksummed<T, D> where T: SerrySized, D: Digest {
    fn predict_size(&self) -> usize {
        self.inner.predict_size() + Self::checksum_size() + (u64::predict_constant_size_unchecked() * 2)
    }

    fn predict_constant_size() -> Option<usize> {
        T::predict_constant_size().map(|v| Self::checksum_size() + (u64::predict_constant_size_unchecked() * 2))
    }
}

impl<T, D> Deref for Checksummed<T, D> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<T, D> DerefMut for Checksummed<T, D>  {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

impl<T, D> PartialEq<Self> for Checksummed<T, D> where T: Eq {
    fn eq(&self, other: &Self) -> bool {
        self.inner.eq(&other.inner)
    }
}

impl<T, D> PartialEq<T> for Checksummed<T, D> where T: Eq {
    fn eq(&self, other: &T) -> bool {
        self.inner.eq(other)
    }
}

impl<T, D> Eq for Checksummed<T, D> where T: Eq {}

impl<T, D> PartialOrd for Checksummed<T, D> where Checksummed<T, D>: PartialEq, T: PartialOrd {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.inner.partial_cmp(&other.inner)
    }
}
impl<T, D> PartialOrd<T> for Checksummed<T, D> where Checksummed<T, D>: PartialEq<T>, T: PartialOrd {
    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
        self.inner.partial_cmp(other)
    }
}

impl<T, D> Ord for Checksummed<T, D> where Checksummed<T, D>: PartialOrd, T: Ord {
    fn cmp(&self, other: &Self) -> Ordering {
        self.inner.cmp(&other.inner)
    }
}

impl<T, D> Debug for Checksummed<T, D> where T: Debug {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.inner.fmt(f)
    }
}
impl<T, D> fmt::Display for Checksummed<T, D> where T: fmt::Display {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.inner.fmt(f)
    }
}


#[cfg(feature = "checksum-sha2")]
mod __sha2 {
    use sha2::Sha256;
    use sha2::Sha512;

    pub type ChecksummedSha256<T> = super::Checksummed<T, Sha256>;
    pub type ChecksummedSha512<T> = super::Checksummed<T, Sha512>;
}

#[cfg(feature = "checksum-sha2")]
pub use __sha2::*;

#[cfg(test)]
mod tests {
    use super::*;
    use rand::{Rng, RngCore};

    fn test_digest<D>() where D: Digest {
        let mut buf = Vec::new();
        macro_rules! value_test {
            ($type:ty, $value:expr) => {{
                let value: $type = $value;
                let checksummed = Checksummed::<$type, D>::new(value.clone());
                buf.write_value(&checksummed).expect("Failed to write value");
                if buf.len() != checksummed.predict_size() { panic!("Predicted size {} is different from written size {}", checksummed.predict_size(), buf.len()) }
                let read = Checksummed::<$type, D>::serry_read(&mut buf.as_slice()).expect("Failed to read value");
                assert_eq!(read, checksummed);
                assert_eq!(checksummed.inner, value);
                buf.clear();
            }};
        }


        let mut rng = rand::rngs::OsRng;

        value_test!(u8, rng.gen());
        value_test!(u8, rng.gen());
        value_test!(i8, rng.gen());
        value_test!(i8, rng.gen());

        value_test!(u16, rng.gen());
        value_test!(u16, rng.gen());
        value_test!(i16, rng.gen());
        value_test!(i16, rng.gen());

        value_test!(u32, rng.gen());
        value_test!(u32, rng.gen());
        value_test!(i32, rng.gen());
        value_test!(i32, rng.gen());

        value_test!(u64, rng.gen());
        value_test!(u64, rng.gen());
        value_test!(i64, rng.gen());
        value_test!(i64, rng.gen());

        value_test!(String, "Hello world!".into());
        value_test!(String, rng.next_u32().to_string());
        value_test!(String, rng.next_u64().to_string());
    }

    #[cfg(feature = "checksum-sha2")]
    mod sha2test {
        use sha2::{Sha224, Sha256, Sha512_224, Sha512_256, Sha384, Sha512};
        use super::test_digest;

        #[test]
        fn sha224() {
            test_digest::<Sha224>();
        }

        #[test]
        fn sha256() {
            test_digest::<Sha256>();
        }
        #[test]
        fn sha512_224() {
            test_digest::<Sha512_224>();
        }

        #[test]
        fn sha512_256() {
            test_digest::<Sha512_256>();
        }

        #[test]
        fn sha384() {
            test_digest::<Sha384>();
        }
        #[test]
        fn sha512() {
            test_digest::<Sha512>();
        }
    }
}