Newer
Older
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>,
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 }
}
}
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();
features: HashSet::from_iter(features.iter().cloned()),
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)?);
}
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)
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> {
}
#[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 zippy = Zippy::decode(&encoded, None).unwrap();
assert!(zippy.has_feature(ZippyFeatures::Crc32));
assert_eq!(zippy.crc32(), 0x53518fab);
#[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(),
Some(ZippyOptions::new(false)),
)
.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(), 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);
}