Skip to content
Snippets Groups Projects
zippy.rs 10.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • João Magalhães's avatar
    João Magalhães committed
    use std::{
    
        collections::HashSet,
    
        convert::TryInto,
    
        hash::Hash,
    
    João Magalhães's avatar
    João Magalhães committed
        io::{Cursor, Read, Write},
    
        iter::FromIterator,
    
    João Magalhães's avatar
    João Magalhães committed
        mem::size_of,
    };
    
    use boytacean_common::error::Error;
    
    use boytacean_hashing::crc32c::crc32c;
    
    
    use crate::{
        huffman::{decode_huffman, encode_huffman},
        rle::{decode_rle, encode_rle},
    };
    
    
    pub const ZIPPY_MAGIC: &str = "ZIPY";
    
    pub const ZIPPY_MAGIC_UINT: u32 = 0x5a495059;
    
    
    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
    pub enum ZippyFeatures {
        Crc32,
        Encrypted,
        Other,
    }
    
    
    impl From<&ZippyFeatures> for &str {
        fn from(value: &ZippyFeatures) -> Self {
            match value {
                ZippyFeatures::Crc32 => "crc32",
                ZippyFeatures::Encrypted => "encrypted",
                ZippyFeatures::Other => "other",
            }
        }
    }
    
    
    impl From<u32> for ZippyFeatures {
        fn from(value: u32) -> Self {
            match value {
                0 => Self::Crc32,
                1 => Self::Encrypted,
                _ => Self::Other,
            }
        }
    }
    
    impl From<&str> for ZippyFeatures {
        fn from(value: &str) -> Self {
            match value {
                "crc32" => Self::Crc32,
                "encrypted" => Self::Encrypted,
                _ => Self::Other,
            }
        }
    }
    
    
    #[derive(Default)]
    pub struct Zippy {
    
        name: String,
        description: String,
    
        features: HashSet<ZippyFeatures>,
    
        crc32: u32,
    
        data: Vec<u8>,
    
    pub struct ZippyOptions {
        crc32: bool,
    }
    
    impl ZippyOptions {
        pub fn new(crc32: bool) -> Self {
            Self { crc32 }
        }
    }
    
    impl default::Default for ZippyOptions {
        fn default() -> Self {
            Self { crc32: true }
        }
    }
    
    
    impl Zippy {
    
        pub fn build(
            data: &[u8],
            name: String,
            description: String,
    
            features: Option<Vec<ZippyFeatures>>,
    
            options: Option<ZippyOptions>,
        ) -> Result<Self, Error> {
    
            let features = features.unwrap_or(vec![ZippyFeatures::Crc32]);
    
            let options = options.unwrap_or_default();
    
            Ok(Self {
                name,
                description,
    
                features: HashSet::from_iter(features.iter().cloned()),
    
    João Magalhães's avatar
    João Magalhães committed
                crc32: if options.crc32 {
    
    João Magalhães's avatar
    João Magalhães committed
                } else {
                    0xffffffff
                },
    
                data: data.to_vec(),
            })
        }
    
        pub fn is_zippy(data: &[u8]) -> Result<bool, Error> {
            let mut data = Cursor::new(data);
    
            let mut buffer = [0x00; size_of::<u32>()];
            data.read_exact(&mut buffer)?;
            let magic = u32::from_le_bytes(buffer);
    
            Ok(magic == ZIPPY_MAGIC_UINT)
        }
    
    
        pub fn decode(data: &[u8], _options: Option<ZippyOptions>) -> Result<Zippy, Error> {
    
            let mut data = Cursor::new(data);
    
    
            let magic = Self::read_u32(&mut data)?;
    
            if magic != ZIPPY_MAGIC_UINT {
                return Err(Error::InvalidData);
            }
    
    
            let name = Self::read_string(&mut data)?;
            let description = Self::read_string(&mut data)?;
    
            let mut instance = Self {
                name,
                description,
                features: HashSet::new(),
                crc32: 0xffffffff,
                data: vec![],
    
            instance.read_features(&mut data)?;
    
            let buffer = Self::read_payload(&mut data)?;
    
            let decoded = decode_rle(&decode_huffman(&buffer)?);
    
            instance.data = decoded;
    
        }
    
        pub fn encode(&self) -> Result<Vec<u8>, Error> {
            let mut buffer = Cursor::new(vec![]);
            let encoded = encode_huffman(&encode_rle(&self.data))?;
    
    
            Self::write_u32(&mut buffer, ZIPPY_MAGIC_UINT)?;
    
            Self::write_string(&mut buffer, &self.name)?;
            Self::write_string(&mut buffer, &self.description)?;
    
    
            self.write_features(&mut buffer)?;
    
            Self::write_payload(&mut buffer, &encoded)?;
    
    
            Ok(buffer.into_inner())
        }
    
        pub fn check_crc32(&self) -> bool {
    
            self.crc32 == crc32c(&self.data)
    
    João Magalhães's avatar
    João Magalhães committed
        pub fn crc32(&self) -> u32 {
            self.crc32
        }
    
    
        pub fn data(&self) -> &[u8] {
            &self.data
    
    
        pub fn has_feature(&self, feature: ZippyFeatures) -> bool {
            self.features.contains(&feature)
        }
    
        #[inline(always)]
        fn read_u32(data: &mut Cursor<&[u8]>) -> Result<u32, Error> {
            let mut buffer = [0x00; size_of::<u32>()];
            data.read_exact(&mut buffer)?;
            Ok(u32::from_le_bytes(buffer))
        }
    
        #[inline(always)]
        fn read_string(data: &mut Cursor<&[u8]>) -> Result<String, Error> {
            let length = Self::read_u32(data)?;
            let mut buffer = vec![0; length as usize];
            data.read_exact(&mut buffer)?;
            Ok(String::from_utf8(buffer)?)
        }
    
        #[inline(always)]
    
        fn read_payload(data: &mut Cursor<&[u8]>) -> Result<Vec<u8>, Error> {
            let size = Self::read_u32(data)?;
            let mut payload = vec![0; size as usize];
            data.read_exact(&mut payload)?;
            Ok(payload)
        }
    
        #[inline(always)]
        fn read_features(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
    
            let num_features = Self::read_u32(data)?;
            for _ in 0..num_features {
    
                let feature_str = Self::read_string(data)?;
                let feature = ZippyFeatures::from(feature_str.as_str());
                match feature {
                    ZippyFeatures::Crc32 => self.read_crc32_feature(data)?,
                    _ => self.read_empty_feature(data)?,
                };
                self.features.insert(feature);
    
            Ok(())
        }
    
        #[inline(always)]
        fn read_crc32_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
    
            let payload = Self::read_payload(data)?;
            if payload.len() != size_of::<u32>() {
                return Err(Error::InvalidData);
            }
            let payload: [u8; 4] = payload.try_into().unwrap();
    
            self.crc32 = u32::from_le_bytes(payload);
            Ok(())
        }
    
        #[inline(always)]
        fn read_empty_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
            Self::read_payload(data)?;
            Ok(())
    
        }
    
        #[inline(always)]
        fn write_u32(data: &mut Cursor<Vec<u8>>, value: u32) -> Result<(), Error> {
            data.write_all(&value.to_le_bytes())?;
            Ok(())
        }
    
        #[inline(always)]
        fn write_string(data: &mut Cursor<Vec<u8>>, value: &str) -> Result<(), Error> {
            Self::write_u32(data, value.len() as u32)?;
            data.write_all(value.as_bytes())?;
            Ok(())
        }
    
        #[inline(always)]
    
        fn write_payload(data: &mut Cursor<Vec<u8>>, value: &[u8]) -> Result<(), Error> {
            Self::write_u32(data, value.len() as u32)?;
            data.write_all(value)?;
            Ok(())
        }
    
        #[inline(always)]
        fn write_features(&self, data: &mut Cursor<Vec<u8>>) -> Result<(), Error> {
            Self::write_u32(data, self.features.len() as u32)?;
            for feature in &self.features {
                match feature {
                    ZippyFeatures::Crc32 => self.write_crc32_feature(data)?,
                    _ => self.write_empty_feature(data, feature.into())?,
                }
    
    
        #[inline(always)]
        fn write_crc32_feature(&self, data: &mut Cursor<Vec<u8>>) -> Result<(), Error> {
            Self::write_string(data, "crc32")?;
            Self::write_u32(data, size_of::<u32>() as u32)?;
            Self::write_u32(data, self.crc32)?;
            Ok(())
        }
    
        #[inline(always)]
        fn write_empty_feature(&self, data: &mut Cursor<Vec<u8>>, name: &str) -> Result<(), Error> {
            Self::write_string(data, name)?;
            Self::write_u32(data, 0)?;
            Ok(())
        }
    
    pub fn encode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8>, Error> {
    
        Zippy::build(data, String::from(""), String::from(""), None, options)?.encode()
    
    pub fn decode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8>, Error> {
    
    João Magalhães's avatar
    João Magalhães committed
        Ok(Zippy::decode(data, options)?.data().to_vec())
    
    }
    
    #[cfg(test)]
    mod tests {
        use boytacean_common::error::Error;
    
    
        use super::{decode_zippy, Zippy, ZippyFeatures, ZippyOptions};
    
    
        #[test]
        fn test_build_and_encode() {
            let data = vec![1, 2, 3, 4, 5];
            let name = String::from("Test");
            let description = String::from("Test description");
    
    
            let zippy = Zippy::build(&data, name.clone(), description.clone(), None, None).unwrap();
    
            let encoded = zippy.encode().unwrap();
    
    
            let decoded = Zippy::decode(&encoded, None).unwrap();
    
            assert_eq!(decoded.name, name);
            assert_eq!(decoded.description, description);
            assert_eq!(decoded.data, data);
        }
    
        #[test]
        fn test_decode_zippy() {
            let data = vec![1, 2, 3, 4, 5];
            let name = String::from("Test");
            let description = String::from("Test description");
    
    
            let zippy = Zippy::build(&data, name.clone(), description.clone(), None, None).unwrap();
    
            let encoded = zippy.encode().unwrap();
    
    
            let decoded_data = decode_zippy(&encoded, None).unwrap();
    
            assert_eq!(decoded_data, data);
        }
    
    
        #[test]
        fn test_crc32_zippy() {
            let data = vec![1, 2, 3, 4, 5];
            let name = String::from("Test");
            let description = String::from("Test description");
    
    
            let zippy = Zippy::build(&data, name.clone(), description.clone(), None, None).unwrap();
    
            let encoded = zippy.encode().unwrap();
    
    
            let zippy = Zippy::decode(&encoded, None).unwrap();
    
            assert!(zippy.has_feature(ZippyFeatures::Crc32));
    
            assert!(zippy.check_crc32());
    
            assert_eq!(zippy.crc32(), 0x53518fab);
    
    João Magalhães's avatar
    João Magalhães committed
        #[test]
        fn test_no_crc32_zippy() {
            let data = vec![1, 2, 3, 4, 5];
            let name = String::from("Test");
            let description = String::from("Test description");
    
            let zippy = Zippy::build(
                &data,
                name.clone(),
                description.clone(),
    
    João Magalhães's avatar
    João Magalhães committed
                Some(ZippyOptions::new(false)),
            )
            .unwrap();
            let encoded = zippy.encode().unwrap();
    
            let zippy = Zippy::decode(&encoded, None).unwrap();
    
            assert!(zippy.has_feature(ZippyFeatures::Crc32));
    
    João Magalhães's avatar
    João Magalhães committed
            assert!(!zippy.check_crc32());
            assert_eq!(zippy.crc32(), 0xffffffff);
        }
    
    
        #[test]
        fn test_decode_invalid() {
    
            let decoded_data = decode_zippy(b"invalid", None);
    
            assert!(decoded_data.is_err());
            assert_eq!(decoded_data.unwrap_err(), Error::InvalidData);
        }
    
    
        #[test]
        fn test_dummy_feature() {
            let data = vec![1, 2, 3, 4, 5];
            let name = String::from("Test");
            let description = String::from("Test description");
    
            let zippy = Zippy::build(
                &data,
                name.clone(),
                description.clone(),
                Some(vec![ZippyFeatures::Other]),
                Some(ZippyOptions::new(false)),
            )
            .unwrap();
            let encoded = zippy.encode().unwrap();
    
            let zippy = Zippy::decode(&encoded, None).unwrap();
            assert!(zippy.has_feature(ZippyFeatures::Other));
            assert!(!zippy.has_feature(ZippyFeatures::Crc32));
            assert!(!zippy.check_crc32());
            assert_eq!(zippy.crc32(), 0xffffffff);
        }