diff --git a/benches/encoding.rs b/benches/encoding.rs index 03bc40c819fce1e727216cfc0bdf175d324c0284..6e0fc43649f706d421b64d722f87d06642d4d55a 100644 --- a/benches/encoding.rs +++ b/benches/encoding.rs @@ -28,7 +28,7 @@ fn benchmark_encoding(c: &mut Criterion) { group.bench_function("encode_zippy", |b| { b.iter(|| { - let encoded = encode_zippy(black_box(&data), None).unwrap(); + let encoded = encode_zippy(black_box(&data), None, None).unwrap(); black_box(encoded); }) }); @@ -40,7 +40,7 @@ fn benchmark_decoding(c: &mut Criterion) { let data = generate_data(10_000_000_usize); let encoded_huffman = encode_huffman(black_box(&data)).unwrap(); let encoded_rle = encode_rle(black_box(&data)); - let encoded_zippy = encode_zippy(black_box(&data), None).unwrap(); + let encoded_zippy = encode_zippy(black_box(&data), None, None).unwrap(); let mut group = c.benchmark_group("decoding"); group.throughput(Throughput::Bytes(data.len() as u64)); diff --git a/crates/common/src/error.rs b/crates/common/src/error.rs index 52d9b050bc3a7aa6a9a6896c75478f8af9e56f64..a23b54d32593055f14f9f23c4feeb1e48cd403d2 100644 --- a/crates/common/src/error.rs +++ b/crates/common/src/error.rs @@ -18,8 +18,11 @@ use std::{ #[derive(Debug, Clone, PartialEq, Eq)] pub enum Error { InvalidData, + InvalidKey, RomSize, IncompatibleBootRom, + MissingOption(String), + IoError(String), InvalidParameter(String), CustomError(String), } @@ -28,8 +31,11 @@ impl Error { pub fn description(&self) -> String { match self { Error::InvalidData => String::from("Invalid data format"), + Error::InvalidKey => String::from("Invalid key"), Error::RomSize => String::from("Invalid ROM size"), Error::IncompatibleBootRom => String::from("Incompatible Boot ROM"), + Error::MissingOption(option) => format!("Missing option: {}", option), + Error::IoError(message) => format!("IO error: {}", message), Error::InvalidParameter(message) => format!("Invalid parameter: {}", message), Error::CustomError(message) => String::from(message), } @@ -43,8 +49,8 @@ impl Display for Error { } impl From<io::Error> for Error { - fn from(_error: io::Error) -> Self { - Error::CustomError(String::from("IO error")) + fn from(error: io::Error) -> Self { + Error::IoError(error.to_string()) } } diff --git a/crates/encoding/src/lib.rs b/crates/encoding/src/lib.rs index 0998974ceda3eb3742485787095e7edd4b6f34f9..5e53ad4a885b3a16f59062e9fb30bcfac99f65ac 100644 --- a/crates/encoding/src/lib.rs +++ b/crates/encoding/src/lib.rs @@ -1,3 +1,4 @@ pub mod huffman; +pub mod rc4; pub mod rle; pub mod zippy; diff --git a/crates/encoding/src/rc4.rs b/crates/encoding/src/rc4.rs new file mode 100644 index 0000000000000000000000000000000000000000..b98fe127b406a9627f48061e67ae2902b6f0b197 --- /dev/null +++ b/crates/encoding/src/rc4.rs @@ -0,0 +1,122 @@ +pub struct Rc4 { + s: [u8; 256], + i: u8, + j: u8, +} + +impl Rc4 { + pub fn new(key: &[u8]) -> Self { + let mut s: [u8; 256] = [0; 256]; + for (i, v) in s.iter_mut().enumerate() { + *v = i as u8; + } + + let key_len = key.len(); + if key_len > 0 { + let mut j = 0; + for i in 0..256 { + j = (j + s[i] as usize + key[i % key_len] as usize) % 256; + s.swap(i, j); + } + } + + Rc4 { s, i: 0, j: 0 } + } + + pub fn process(&mut self, data: &mut [u8]) { + for byte in data.iter_mut() { + self.i = self.i.wrapping_add(1); + self.j = self.j.wrapping_add(self.s[self.i as usize]); + self.s.swap(self.i as usize, self.j as usize); + let k = + self.s[(self.s[self.i as usize].wrapping_add(self.s[self.j as usize])) as usize]; + *byte ^= k; + } + } +} + +pub fn rc4_encrypt(key: &[u8], data: &mut [u8]) { + let mut rc4 = Rc4::new(key); + rc4.process(data); +} + +pub fn rc4_decrypt(key: &[u8], data: &mut [u8]) { + rc4_encrypt(key, data) +} + +#[cfg(test)] +mod tests { + use super::Rc4; + + #[test] + fn test_rc4_initialization() { + let key = b"key"; + let rc4 = Rc4::new(key); + assert_eq!(rc4.s.len(), 256); + } + + #[test] + fn test_rc4_encryption_decryption() { + let key = b"supersecretkey"; + let plaintext = b"hello world"; + let mut data = plaintext.to_vec(); + + let mut rc4 = Rc4::new(key); + rc4.process(&mut data); + assert_ne!(&data, plaintext); + + let mut rc4 = Rc4::new(key); + rc4.process(&mut data); + assert_eq!(&data, plaintext); + } + + #[test] + fn test_rc4_empty_key() { + let key = b""; + let mut data = b"hello world".to_vec(); + + let mut rc4 = Rc4::new(key); + rc4.process(&mut data); + + let mut rc4 = Rc4::new(key); + rc4.process(&mut data); + assert_eq!(data, b"hello world"); + } + + #[test] + fn test_rc4_empty_data() { + let key = b"supersecretkey"; + let mut data: Vec<u8> = vec![]; + + let mut rc4 = Rc4::new(key); + rc4.process(&mut data); + + let mut rc4 = Rc4::new(key); + rc4.process(&mut data); + assert!(data.is_empty()); + } + + #[test] + fn test_rc4_with_different_keys() { + let key1 = b"key1"; + let key2 = b"key2"; + let plaintext = b"hello world"; + let mut data1 = plaintext.to_vec(); + let mut data2 = plaintext.to_vec(); + + let mut rc4 = Rc4::new(key1); + rc4.process(&mut data1); + + let mut rc4 = Rc4::new(key2); + rc4.process(&mut data2); + assert_ne!(data1, data2); + + let mut rc4 = Rc4::new(key1); + rc4.process(&mut data1); + + let mut rc4 = Rc4::new(key2); + rc4.process(&mut data2); + assert_eq!(data1, plaintext); + assert_eq!(data2, plaintext); + } +} diff --git a/crates/encoding/src/zippy.rs b/crates/encoding/src/zippy.rs index 5de497487477c59bc55d8bef3d95ace03502b234..d109f75f58dfe8875c71884b7431a62d9b7f5ba6 100644 --- a/crates/encoding/src/zippy.rs +++ b/crates/encoding/src/zippy.rs @@ -13,6 +13,7 @@ use boytacean_hashing::crc32c::crc32c; use crate::{ huffman::{decode_huffman, encode_huffman}, + rc4::{rc4_decrypt, rc4_encrypt}, rle::{decode_rle, encode_rle}, }; @@ -20,18 +21,30 @@ pub const ZIPPY_MAGIC: &str = "ZIPY"; pub const ZIPPY_MAGIC_UINT: u32 = 0x5a495059; +pub const ZIPPY_CYPHER_TEST: &[u8; 22] = b"ZIPPY_CYPHER_SIGNATURE"; + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum ZippyFeatures { Crc32, - Encrypted, + EncryptedRc4, Other, } +impl From<ZippyFeatures> for &str { + fn from(value: ZippyFeatures) -> Self { + match value { + ZippyFeatures::Crc32 => "crc32", + ZippyFeatures::EncryptedRc4 => "encrypted_rc4", + ZippyFeatures::Other => "other", + } + } +} + impl From<&ZippyFeatures> for &str { fn from(value: &ZippyFeatures) -> Self { match value { ZippyFeatures::Crc32 => "crc32", - ZippyFeatures::Encrypted => "encrypted", + ZippyFeatures::EncryptedRc4 => "encrypted_rc4", ZippyFeatures::Other => "other", } } @@ -41,7 +54,7 @@ impl From<u32> for ZippyFeatures { fn from(value: u32) -> Self { match value { 0 => Self::Crc32, - 1 => Self::Encrypted, + 1 => Self::EncryptedRc4, _ => Self::Other, } } @@ -51,7 +64,7 @@ impl From<&str> for ZippyFeatures { fn from(value: &str) -> Self { match value { "crc32" => Self::Crc32, - "encrypted" => Self::Encrypted, + "encrypted_rc4" => Self::EncryptedRc4, _ => Self::Other, } } @@ -62,23 +75,28 @@ pub struct Zippy { name: String, description: String, features: HashSet<ZippyFeatures>, + options: ZippyOptions, crc32: u32, data: Vec<u8>, } pub struct ZippyOptions { crc32: bool, + key: Option<String>, } impl ZippyOptions { - pub fn new(crc32: bool) -> Self { - Self { crc32 } + pub fn new(crc32: bool, key: Option<String>) -> Self { + Self { crc32, key } } } impl default::Default for ZippyOptions { fn default() -> Self { - Self { crc32: true } + Self { + crc32: true, + key: None, + } } } @@ -92,15 +110,13 @@ impl Zippy { ) -> Result<Self, Error> { let features = features.unwrap_or(vec![ZippyFeatures::Crc32]); let options = options.unwrap_or_default(); + let is_crc32 = options.crc32; Ok(Self { name, description, features: HashSet::from_iter(features.iter().cloned()), - crc32: if options.crc32 { - crc32c(data) - } else { - 0xffffffff - }, + options, + crc32: if is_crc32 { crc32c(data) } else { 0xffffffff }, data: data.to_vec(), }) } @@ -115,7 +131,9 @@ impl Zippy { Ok(magic == ZIPPY_MAGIC_UINT) } - pub fn decode(data: &[u8], _options: Option<ZippyOptions>) -> Result<Zippy, Error> { + pub fn decode(data: &[u8], options: Option<ZippyOptions>) -> Result<Zippy, Error> { + let options = options.unwrap_or_default(); + let mut data = Cursor::new(data); let magic = Self::read_u32(&mut data)?; @@ -130,13 +148,18 @@ impl Zippy { name, description, features: HashSet::new(), + options, crc32: 0xffffffff, data: vec![], }; instance.read_features(&mut data)?; - let buffer = Self::read_payload(&mut data)?; + let mut buffer = Self::read_buffer(&mut data)?; + if instance.has_feature(ZippyFeatures::EncryptedRc4) { + rc4_decrypt(instance.key()?, &mut buffer) + } + let decoded = decode_rle(&decode_huffman(&buffer)?); instance.data = decoded; @@ -145,7 +168,11 @@ impl Zippy { pub fn encode(&self) -> Result<Vec<u8>, Error> { let mut buffer = Cursor::new(vec![]); - let encoded = encode_huffman(&encode_rle(&self.data))?; + let mut encoded = encode_huffman(&encode_rle(&self.data))?; + + if self.has_feature(ZippyFeatures::EncryptedRc4) { + rc4_encrypt(self.key()?, &mut encoded) + } Self::write_u32(&mut buffer, ZIPPY_MAGIC_UINT)?; @@ -154,7 +181,7 @@ impl Zippy { self.write_features(&mut buffer)?; - Self::write_payload(&mut buffer, &encoded)?; + Self::write_buffer(&mut buffer, &encoded)?; Ok(buffer.into_inner()) } @@ -191,7 +218,7 @@ impl Zippy { } #[inline(always)] - fn read_payload(data: &mut Cursor<&[u8]>) -> Result<Vec<u8>, Error> { + fn read_buffer(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)?; @@ -206,6 +233,7 @@ impl Zippy { let feature = ZippyFeatures::from(feature_str.as_str()); match feature { ZippyFeatures::Crc32 => self.read_crc32_feature(data)?, + ZippyFeatures::EncryptedRc4 => self.read_rc4_feature(data)?, _ => self.read_empty_feature(data)?, }; self.features.insert(feature); @@ -215,7 +243,7 @@ impl Zippy { #[inline(always)] fn read_crc32_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> { - let payload = Self::read_payload(data)?; + let payload = Self::read_buffer(data)?; if payload.len() != size_of::<u32>() { return Err(Error::InvalidData); } @@ -224,9 +252,19 @@ impl Zippy { Ok(()) } + #[inline(always)] + fn read_rc4_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> { + let mut test_data = Self::read_buffer(data)?; + rc4_decrypt(self.key()?, &mut test_data); + if test_data != ZIPPY_CYPHER_TEST { + return Err(Error::InvalidKey); + } + Ok(()) + } + #[inline(always)] fn read_empty_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> { - Self::read_payload(data)?; + Self::read_buffer(data)?; Ok(()) } @@ -244,7 +282,7 @@ impl Zippy { } #[inline(always)] - fn write_payload(data: &mut Cursor<Vec<u8>>, value: &[u8]) -> Result<(), Error> { + fn write_buffer(data: &mut Cursor<Vec<u8>>, value: &[u8]) -> Result<(), Error> { Self::write_u32(data, value.len() as u32)?; data.write_all(value)?; Ok(()) @@ -256,6 +294,7 @@ impl Zippy { for feature in &self.features { match feature { ZippyFeatures::Crc32 => self.write_crc32_feature(data)?, + ZippyFeatures::EncryptedRc4 => self.write_rc4_feature(data)?, _ => self.write_empty_feature(data, feature.into())?, } } @@ -264,22 +303,44 @@ impl Zippy { #[inline(always)] fn write_crc32_feature(&self, data: &mut Cursor<Vec<u8>>) -> Result<(), Error> { - Self::write_string(data, "crc32")?; + Self::write_string(data, ZippyFeatures::Crc32.into())?; Self::write_u32(data, size_of::<u32>() as u32)?; Self::write_u32(data, self.crc32)?; Ok(()) } + #[inline(always)] + fn write_rc4_feature(&self, data: &mut Cursor<Vec<u8>>) -> Result<(), Error> { + let mut test_data = ZIPPY_CYPHER_TEST.to_vec(); + rc4_encrypt(self.key()?, &mut test_data); + Self::write_string(data, ZippyFeatures::EncryptedRc4.into())?; + Self::write_buffer(data, &test_data)?; + 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(()) } + + fn key(&self) -> Result<&[u8], Error> { + Ok(self + .options + .key + .as_ref() + .ok_or(Error::MissingOption(String::from("key")))? + .as_bytes()) + } } -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 encode_zippy( + data: &[u8], + features: Option<Vec<ZippyFeatures>>, + options: Option<ZippyOptions>, +) -> Result<Vec<u8>, Error> { + Zippy::build(data, String::from(""), String::from(""), features, options)?.encode() } pub fn decode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8>, Error> { @@ -290,10 +351,10 @@ pub fn decode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8 mod tests { use boytacean_common::error::Error; - use super::{decode_zippy, Zippy, ZippyFeatures, ZippyOptions}; + use super::{decode_zippy, encode_zippy, Zippy, ZippyFeatures, ZippyOptions}; #[test] - fn test_build_and_encode() { + fn test_zippy_build_and_encode() { let data = vec![1, 2, 3, 4, 5]; let name = String::from("Test"); let description = String::from("Test description"); @@ -308,7 +369,7 @@ mod tests { } #[test] - fn test_decode_zippy() { + fn test_zippy_decode() { let data = vec![1, 2, 3, 4, 5]; let name = String::from("Test"); let description = String::from("Test description"); @@ -321,7 +382,7 @@ mod tests { } #[test] - fn test_crc32_zippy() { + fn test_zippy_crc32() { let data = vec![1, 2, 3, 4, 5]; let name = String::from("Test"); let description = String::from("Test description"); @@ -336,7 +397,7 @@ mod tests { } #[test] - fn test_no_crc32_zippy() { + fn test_zippy_no_crc32() { let data = vec![1, 2, 3, 4, 5]; let name = String::from("Test"); let description = String::from("Test description"); @@ -346,7 +407,7 @@ mod tests { name.clone(), description.clone(), None, - Some(ZippyOptions::new(false)), + Some(ZippyOptions::new(false, None)), ) .unwrap(); let encoded = zippy.encode().unwrap(); @@ -358,14 +419,14 @@ mod tests { } #[test] - fn test_decode_invalid() { + fn test_zippy_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() { + fn test_zippy_dummy_feature() { let data = vec![1, 2, 3, 4, 5]; let name = String::from("Test"); let description = String::from("Test description"); @@ -375,7 +436,7 @@ mod tests { name.clone(), description.clone(), Some(vec![ZippyFeatures::Other]), - Some(ZippyOptions::new(false)), + Some(ZippyOptions::new(false, None)), ) .unwrap(); let encoded = zippy.encode().unwrap(); @@ -386,4 +447,52 @@ mod tests { assert!(!zippy.check_crc32()); assert_eq!(zippy.crc32(), 0xffffffff); } + + #[test] + fn test_zippy_encrypted() { + let encoded = encode_zippy( + b"test", + Some(vec![ZippyFeatures::EncryptedRc4]), + Some(ZippyOptions::new(false, Some(String::from("key")))), + ) + .unwrap(); + let decoded = decode_zippy( + &encoded, + Some(ZippyOptions::new(false, Some(String::from("key")))), + ) + .unwrap(); + assert_eq!(decoded, b"test"); + } + + #[test] + fn test_zippy_wrong_key() { + let encoded = encode_zippy( + b"test", + Some(vec![ZippyFeatures::EncryptedRc4]), + Some(ZippyOptions::new(false, Some(String::from("key")))), + ) + .unwrap(); + let decoded = decode_zippy( + &encoded, + Some(ZippyOptions::new(false, Some(String::from("wrong_key")))), + ); + assert!(decoded.is_err()); + assert_eq!(decoded.unwrap_err(), Error::InvalidKey); + } + + #[test] + fn test_zippy_no_key() { + let encoded = encode_zippy( + b"test", + Some(vec![ZippyFeatures::EncryptedRc4]), + Some(ZippyOptions::new(false, Some(String::from("key")))), + ) + .unwrap(); + let decoded = decode_zippy(&encoded, Some(ZippyOptions::new(false, None))); + assert!(decoded.is_err()); + assert_eq!( + decoded.unwrap_err(), + Error::MissingOption(String::from("key")) + ); + } } diff --git a/src/state.rs b/src/state.rs index 044f0e455d749c1f6c3c4830feac46b990e4682f..89aa7d9a2970658b9ac44001fcd2c9bf06c535be 100644 --- a/src/state.rs +++ b/src/state.rs @@ -225,7 +225,7 @@ impl Serialize for BoscState { let mut cursor = Cursor::new(vec![]); self.bos.write(&mut cursor)?; - let bos_compressed = encode_zippy(&cursor.into_inner(), None)?; + let bos_compressed = encode_zippy(&cursor.into_inner(), None, None)?; buffer.write_all(&bos_compressed)?; Ok(()) @@ -2050,7 +2050,7 @@ mod tests { .unwrap(); gb.step_to(0x0100); let data = StateManager::save(&mut gb, Some(SaveStateFormat::Bess), None).unwrap(); - let encoded = encode_zippy(&data, None).unwrap(); + let encoded = encode_zippy(&data, None, None).unwrap(); let decoded = decode_zippy(&encoded, None).unwrap(); assert_eq!(data, decoded); assert_eq!(encoded.len(), 847);