Skip to main content

boytacean_encoding/
zippy.rs

1use std::{
2    collections::HashSet,
3    convert::TryInto,
4    default,
5    hash::Hash,
6    io::{Cursor, Read, Write},
7    iter::FromIterator,
8    mem::size_of,
9};
10
11use boytacean_common::{
12    data::{read_bytes, read_string, read_u32, write_bytes, write_string, write_u32},
13    error::Error,
14};
15use boytacean_hashing::crc32c::crc32c;
16
17use crate::{
18    codec::Codec,
19    huffman::{decode_huffman, encode_huffman},
20    rc4::{decrypt_rc4, encrypt_rc4},
21    rle::{decode_rle, encode_rle},
22};
23
24pub const ZIPPY_MAGIC: &str = "ZIPY";
25
26pub const ZIPPY_MAGIC_UINT: u32 = 0x5a495059;
27
28pub const ZIPPY_CIPHER_TEST: &[u8; 22] = b"ZIPPY_CIPHER_SIGNATURE";
29
30#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
31pub enum ZippyFeatures {
32    Crc32,
33    EncryptedRc4,
34    Other,
35}
36
37impl From<ZippyFeatures> for &str {
38    fn from(value: ZippyFeatures) -> Self {
39        match value {
40            ZippyFeatures::Crc32 => "crc32",
41            ZippyFeatures::EncryptedRc4 => "encrypted_rc4",
42            ZippyFeatures::Other => "other",
43        }
44    }
45}
46
47impl From<&ZippyFeatures> for &str {
48    fn from(value: &ZippyFeatures) -> Self {
49        match value {
50            ZippyFeatures::Crc32 => "crc32",
51            ZippyFeatures::EncryptedRc4 => "encrypted_rc4",
52            ZippyFeatures::Other => "other",
53        }
54    }
55}
56
57impl From<u32> for ZippyFeatures {
58    fn from(value: u32) -> Self {
59        match value {
60            0 => Self::Crc32,
61            1 => Self::EncryptedRc4,
62            _ => Self::Other,
63        }
64    }
65}
66
67impl From<&str> for ZippyFeatures {
68    fn from(value: &str) -> Self {
69        match value {
70            "crc32" => Self::Crc32,
71            "encrypted_rc4" => Self::EncryptedRc4,
72            _ => Self::Other,
73        }
74    }
75}
76
77#[derive(Default)]
78pub struct Zippy {
79    name: String,
80    description: String,
81    features: HashSet<ZippyFeatures>,
82    options: ZippyOptions,
83    crc32: u32,
84    data: Vec<u8>,
85}
86
87#[derive(Clone, Debug, Eq, Hash, PartialEq)]
88pub struct ZippyOptions {
89    crc32: bool,
90    key: Option<String>,
91}
92
93impl ZippyOptions {
94    pub fn new(crc32: bool, key: Option<String>) -> Self {
95        Self { crc32, key }
96    }
97}
98
99impl default::Default for ZippyOptions {
100    fn default() -> Self {
101        Self {
102            crc32: true,
103            key: None,
104        }
105    }
106}
107
108pub struct ZippyEncodeOptions {
109    name: Option<String>,
110    description: Option<String>,
111    features: Option<Vec<ZippyFeatures>>,
112    options: Option<ZippyOptions>,
113}
114
115pub struct ZippyDecodeOptions {
116    options: Option<ZippyOptions>,
117}
118
119impl Zippy {
120    pub fn build(
121        data: &[u8],
122        name: String,
123        description: String,
124        features: Option<Vec<ZippyFeatures>>,
125        options: Option<ZippyOptions>,
126    ) -> Result<Self, Error> {
127        let features = features.unwrap_or(vec![ZippyFeatures::Crc32]);
128        let options = options.unwrap_or_default();
129        let is_crc32 = options.crc32;
130        Ok(Self {
131            name,
132            description,
133            features: HashSet::from_iter(features.iter().cloned()),
134            options,
135            crc32: if is_crc32 { crc32c(data) } else { 0xffffffff },
136            data: data.to_vec(),
137        })
138    }
139
140    pub fn encode_data(&self) -> Result<Vec<u8>, Error> {
141        let mut buffer = Cursor::new(vec![]);
142        let mut encoded = encode_huffman(&encode_rle(&self.data)?)?;
143
144        if self.has_feature(ZippyFeatures::EncryptedRc4) {
145            encrypt_rc4(&mut encoded, self.key()?)?;
146        }
147
148        write_u32(&mut buffer, ZIPPY_MAGIC_UINT)?;
149
150        Self::write_string(&mut buffer, &self.name)?;
151        Self::write_string(&mut buffer, &self.description)?;
152
153        self.write_features(&mut buffer)?;
154
155        Self::write_buffer(&mut buffer, &encoded)?;
156
157        Ok(buffer.into_inner())
158    }
159
160    pub fn decode_data(data: &[u8], options: Option<ZippyOptions>) -> Result<Zippy, Error> {
161        let options = options.unwrap_or_default();
162
163        let mut data = Cursor::new(data);
164
165        let magic = read_u32(&mut data)?;
166        if magic != ZIPPY_MAGIC_UINT {
167            return Err(Error::InvalidData);
168        }
169
170        let name = Self::read_string(&mut data)?;
171        let description = Self::read_string(&mut data)?;
172
173        let mut instance = Self {
174            name,
175            description,
176            features: HashSet::new(),
177            options,
178            crc32: 0xffffffff,
179            data: vec![],
180        };
181
182        instance.read_features(&mut data)?;
183
184        let mut buffer = Self::read_buffer(&mut data)?;
185        if instance.has_feature(ZippyFeatures::EncryptedRc4) {
186            decrypt_rc4(&mut buffer, instance.key()?)?;
187        }
188
189        let decoded = decode_rle(&decode_huffman(&buffer)?)?;
190        instance.data = decoded;
191
192        Ok(instance)
193    }
194
195    pub fn is_zippy(data: &[u8]) -> Result<bool, Error> {
196        let mut data = Cursor::new(data);
197
198        let mut buffer = [0x00; size_of::<u32>()];
199        data.read_exact(&mut buffer)?;
200        let magic = u32::from_le_bytes(buffer);
201
202        Ok(magic == ZIPPY_MAGIC_UINT)
203    }
204
205    pub fn check_crc32(&self) -> bool {
206        self.crc32 == crc32c(&self.data)
207    }
208
209    pub fn crc32(&self) -> u32 {
210        self.crc32
211    }
212
213    pub fn data(&self) -> &[u8] {
214        &self.data
215    }
216
217    pub fn has_feature(&self, feature: ZippyFeatures) -> bool {
218        self.features.contains(&feature)
219    }
220
221    #[inline(always)]
222    fn read_string<R: Read>(reader: &mut R) -> Result<String, Error> {
223        let count = read_u32(reader)?;
224        read_string(reader, count as usize)
225    }
226
227    #[inline(always)]
228    fn read_buffer<R: Read>(reader: &mut R) -> Result<Vec<u8>, Error> {
229        let count = read_u32(reader)?;
230        read_bytes(reader, count as usize)
231    }
232
233    #[inline(always)]
234    fn read_features<R: Read>(&mut self, reader: &mut R) -> Result<(), Error> {
235        let num_features = read_u32(reader)?;
236        for _ in 0..num_features {
237            let feature_str = Self::read_string(reader)?;
238            let feature = ZippyFeatures::from(feature_str.as_str());
239            match feature {
240                ZippyFeatures::Crc32 => self.read_crc32_feature(reader)?,
241                ZippyFeatures::EncryptedRc4 => self.read_rc4_feature(reader)?,
242                _ => self.read_empty_feature(reader)?,
243            };
244            self.features.insert(feature);
245        }
246        Ok(())
247    }
248
249    #[inline(always)]
250    fn read_crc32_feature<R: Read>(&mut self, reader: &mut R) -> Result<(), Error> {
251        let payload = Self::read_buffer(reader)?;
252        if payload.len() != size_of::<u32>() {
253            return Err(Error::InvalidData);
254        }
255        let payload: [u8; 4] = payload.try_into().unwrap();
256        self.crc32 = u32::from_le_bytes(payload);
257        Ok(())
258    }
259
260    #[inline(always)]
261    fn read_rc4_feature<R: Read>(&mut self, reader: &mut R) -> Result<(), Error> {
262        let mut test_data = Self::read_buffer(reader)?;
263        decrypt_rc4(&mut test_data, self.key()?)?;
264        if test_data != ZIPPY_CIPHER_TEST {
265            return Err(Error::InvalidKey);
266        }
267        Ok(())
268    }
269
270    #[inline(always)]
271    fn read_empty_feature<R: Read>(&mut self, reader: &mut R) -> Result<(), Error> {
272        Self::read_buffer(reader)?;
273        Ok(())
274    }
275
276    #[inline(always)]
277    fn write_string<W: Write>(writer: &mut W, value: &str) -> Result<(), Error> {
278        write_u32(writer, value.len() as u32)?;
279        write_string(writer, value)?;
280        Ok(())
281    }
282
283    #[inline(always)]
284    fn write_buffer<W: Write>(writer: &mut W, value: &[u8]) -> Result<(), Error> {
285        write_u32(writer, value.len() as u32)?;
286        write_bytes(writer, value)?;
287        Ok(())
288    }
289
290    #[inline(always)]
291    fn write_features<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
292        write_u32(writer, self.features.len() as u32)?;
293        for feature in &self.features {
294            match feature {
295                ZippyFeatures::Crc32 => self.write_crc32_feature(writer)?,
296                ZippyFeatures::EncryptedRc4 => self.write_rc4_feature(writer)?,
297                _ => self.write_empty_feature(writer, feature.into())?,
298            }
299        }
300        Ok(())
301    }
302
303    #[inline(always)]
304    fn write_crc32_feature<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
305        Self::write_string(writer, ZippyFeatures::Crc32.into())?;
306        write_u32(writer, size_of::<u32>() as u32)?;
307        write_u32(writer, self.crc32)?;
308        Ok(())
309    }
310
311    #[inline(always)]
312    fn write_rc4_feature<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
313        let mut test_data = ZIPPY_CIPHER_TEST.to_vec();
314        encrypt_rc4(&mut test_data, self.key()?)?;
315        Self::write_string(writer, ZippyFeatures::EncryptedRc4.into())?;
316        Self::write_buffer(writer, &test_data)?;
317        Ok(())
318    }
319
320    #[inline(always)]
321    fn write_empty_feature<W: Write>(&self, writer: &mut W, name: &str) -> Result<(), Error> {
322        Self::write_string(writer, name)?;
323        write_u32(writer, 0)?;
324        Ok(())
325    }
326
327    fn key(&self) -> Result<&[u8], Error> {
328        Ok(self
329            .options
330            .key
331            .as_ref()
332            .ok_or(Error::MissingOption(String::from("key")))?
333            .as_bytes())
334    }
335}
336
337impl Codec for Zippy {
338    type EncodeOptions = ZippyEncodeOptions;
339    type DecodeOptions = ZippyDecodeOptions;
340
341    fn encode(data: &[u8], options: &Self::EncodeOptions) -> Result<Vec<u8>, Error> {
342        Self::build(
343            data,
344            options.name.clone().unwrap_or_default(),
345            options.description.clone().unwrap_or_default(),
346            options.features.clone(),
347            options.options.clone(),
348        )?
349        .encode_data()
350    }
351
352    fn decode(data: &[u8], options: &Self::DecodeOptions) -> Result<Vec<u8>, Error> {
353        Ok(Zippy::decode_data(data, options.options.clone())?
354            .data()
355            .to_vec())
356    }
357}
358
359pub fn encode_zippy(
360    data: &[u8],
361    features: Option<Vec<ZippyFeatures>>,
362    options: Option<ZippyOptions>,
363) -> Result<Vec<u8>, Error> {
364    Zippy::encode(
365        data,
366        &ZippyEncodeOptions {
367            name: None,
368            description: None,
369            features,
370            options,
371        },
372    )
373}
374
375pub fn decode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8>, Error> {
376    Zippy::decode(data, &ZippyDecodeOptions { options })
377}
378
379#[cfg(test)]
380mod tests {
381    use boytacean_common::error::Error;
382
383    use super::{decode_zippy, encode_zippy, Zippy, ZippyFeatures, ZippyOptions};
384
385    #[test]
386    fn test_zippy_build_and_encode() {
387        let data = vec![1, 2, 3, 4, 5];
388        let name = String::from("Test");
389        let description = String::from("Test description");
390
391        let zippy = Zippy::build(&data, name.clone(), description.clone(), None, None).unwrap();
392        let encoded = zippy.encode_data().unwrap();
393
394        let decoded = Zippy::decode_data(&encoded, None).unwrap();
395        assert_eq!(decoded.name, name);
396        assert_eq!(decoded.description, description);
397        assert_eq!(decoded.data, data);
398    }
399
400    #[test]
401    fn test_zippy_decode() {
402        let data = vec![1, 2, 3, 4, 5];
403        let name = String::from("Test");
404        let description = String::from("Test description");
405
406        let zippy = Zippy::build(&data, name.clone(), description.clone(), None, None).unwrap();
407        let encoded = zippy.encode_data().unwrap();
408
409        let decoded_data = decode_zippy(&encoded, None).unwrap();
410        assert_eq!(decoded_data, data);
411    }
412
413    #[test]
414    fn test_zippy_crc32() {
415        let data = vec![1, 2, 3, 4, 5];
416        let name = String::from("Test");
417        let description = String::from("Test description");
418
419        let zippy = Zippy::build(&data, name.clone(), description.clone(), None, None).unwrap();
420        let encoded = zippy.encode_data().unwrap();
421
422        let zippy = Zippy::decode_data(&encoded, None).unwrap();
423        assert!(zippy.has_feature(ZippyFeatures::Crc32));
424        assert!(zippy.check_crc32());
425        assert_eq!(zippy.crc32(), 0x53518fab);
426    }
427
428    #[test]
429    fn test_zippy_no_crc32() {
430        let data = vec![1, 2, 3, 4, 5];
431        let name = String::from("Test");
432        let description = String::from("Test description");
433
434        let zippy = Zippy::build(
435            &data,
436            name.clone(),
437            description.clone(),
438            None,
439            Some(ZippyOptions::new(false, None)),
440        )
441        .unwrap();
442        let encoded = zippy.encode_data().unwrap();
443
444        let zippy = Zippy::decode_data(&encoded, None).unwrap();
445        assert!(zippy.has_feature(ZippyFeatures::Crc32));
446        assert!(!zippy.check_crc32());
447        assert_eq!(zippy.crc32(), 0xffffffff);
448    }
449
450    #[test]
451    fn test_zippy_decode_invalid() {
452        let decoded_data = decode_zippy(b"invalid", None);
453        assert!(decoded_data.is_err());
454        assert_eq!(decoded_data.unwrap_err(), Error::InvalidData);
455    }
456
457    #[test]
458    fn test_zippy_dummy_feature() {
459        let data = vec![1, 2, 3, 4, 5];
460        let name = String::from("Test");
461        let description = String::from("Test description");
462
463        let zippy = Zippy::build(
464            &data,
465            name.clone(),
466            description.clone(),
467            Some(vec![ZippyFeatures::Other]),
468            Some(ZippyOptions::new(false, None)),
469        )
470        .unwrap();
471        let encoded = zippy.encode_data().unwrap();
472
473        let zippy = Zippy::decode_data(&encoded, None).unwrap();
474        assert!(zippy.has_feature(ZippyFeatures::Other));
475        assert!(!zippy.has_feature(ZippyFeatures::Crc32));
476        assert!(!zippy.check_crc32());
477        assert_eq!(zippy.crc32(), 0xffffffff);
478    }
479
480    #[test]
481    fn test_zippy_encrypted() {
482        let encoded = encode_zippy(
483            b"test",
484            Some(vec![ZippyFeatures::EncryptedRc4]),
485            Some(ZippyOptions::new(false, Some(String::from("key")))),
486        )
487        .unwrap();
488        let decoded = decode_zippy(
489            &encoded,
490            Some(ZippyOptions::new(false, Some(String::from("key")))),
491        )
492        .unwrap();
493        assert_eq!(decoded, b"test");
494    }
495
496    #[test]
497    fn test_zippy_wrong_key() {
498        let encoded = encode_zippy(
499            b"test",
500            Some(vec![ZippyFeatures::EncryptedRc4]),
501            Some(ZippyOptions::new(false, Some(String::from("key")))),
502        )
503        .unwrap();
504        let decoded = decode_zippy(
505            &encoded,
506            Some(ZippyOptions::new(false, Some(String::from("wrong_key")))),
507        );
508        assert!(decoded.is_err());
509        assert_eq!(decoded.unwrap_err(), Error::InvalidKey);
510    }
511
512    #[test]
513    fn test_zippy_no_key() {
514        let encoded = encode_zippy(
515            b"test",
516            Some(vec![ZippyFeatures::EncryptedRc4]),
517            Some(ZippyOptions::new(false, Some(String::from("key")))),
518        )
519        .unwrap();
520        let decoded = decode_zippy(&encoded, Some(ZippyOptions::new(false, None)));
521        assert!(decoded.is_err());
522        assert_eq!(
523            decoded.unwrap_err(),
524            Error::MissingOption(String::from("key"))
525        );
526    }
527}