use super::{Compression, Encryption, Key, HMAC};
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::cmp;
#[derive(PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Hash, Debug)]
pub struct ChunkID {
id: [u8; 32],
}
impl ChunkID {
pub fn new(input_id: &[u8]) -> ChunkID {
let mut id: [u8; 32] = [0; 32];
id[..cmp::min(32, input_id.len())]
.clone_from_slice(&input_id[..cmp::min(32, input_id.len())]);
ChunkID { id }
}
#[cfg_attr(tarpaulin, skip)]
pub fn get_id(&self) -> &[u8] {
&self.id
}
pub fn verify(&self, slice: &[u8]) -> bool {
if slice.len() < self.id.len() {
false
} else {
let mut equal = true;
for (i, val) in self.id.iter().enumerate() {
if *val != slice[i] {
equal = false;
}
}
equal
}
}
pub fn manifest_id() -> ChunkID {
ChunkID { id: [0_u8; 32] }
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq)]
pub struct ChunkSettings {
pub compression: Compression,
pub encryption: Encryption,
pub hmac: HMAC,
}
impl ChunkSettings {
pub fn lightweight() -> ChunkSettings {
ChunkSettings {
compression: Compression::NoCompression,
encryption: Encryption::NoEncryption,
hmac: HMAC::Blake2b,
}
}
}
pub struct UnpackedChunk {
data: Vec<u8>,
id: ChunkID,
}
impl UnpackedChunk {
pub fn new(data: Vec<u8>, settings: ChunkSettings, key: &Key) -> UnpackedChunk {
let id = settings.hmac.id(data.as_slice(), &key);
let cid = ChunkID::new(&id);
UnpackedChunk { data, id: cid }
}
pub fn id(&self) -> ChunkID {
self.id
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn consuming_data(self) -> Vec<u8> {
self.data
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Chunk {
#[serde(with = "serde_bytes")]
data: Vec<u8>,
compression: Compression,
encryption: Encryption,
hmac: HMAC,
#[serde(with = "serde_bytes")]
mac: Vec<u8>,
id: ChunkID,
}
impl Chunk {
#[cfg_attr(feature = "profile", flame)]
pub fn pack(
data: Vec<u8>,
compression: Compression,
encryption: Encryption,
hmac: HMAC,
key: &Key,
) -> Chunk {
let id_mac = hmac.id(&data, key);
let compressed_data = compression.compress(data);
let data = encryption.encrypt(&compressed_data, key);
let id = ChunkID::new(&id_mac);
let mac = hmac.mac(&data, key);
Chunk {
data,
compression,
encryption,
hmac,
mac,
id,
}
}
pub fn from_parts(
data: Vec<u8>,
compression: Compression,
encryption: Encryption,
hmac: HMAC,
mac: Vec<u8>,
id: ChunkID,
) -> Chunk {
Chunk {
data,
compression,
encryption,
hmac,
mac,
id,
}
}
#[cfg_attr(feature = "profile", flame)]
pub fn pack_with_id(
data: Vec<u8>,
compression: Compression,
encryption: Encryption,
hmac: HMAC,
key: &Key,
id: ChunkID,
) -> Chunk {
let compressed_data = compression.compress(data);
let data = encryption.encrypt(&compressed_data, key);
let mac = hmac.mac(&data, key);
Chunk {
data,
compression,
encryption,
hmac,
mac,
id,
}
}
pub async fn pack_with_id_async(
data: Vec<u8>,
compression: Compression,
encryption: Encryption,
hmac: HMAC,
key: &Key,
id: ChunkID,
) -> Chunk {
let compressed_data = compression.compress(data);
let data = encryption.encrypt(&compressed_data, key);
let mac = hmac.mac(&data, key);
Chunk {
data,
compression,
encryption,
hmac,
mac,
id,
}
}
#[cfg_attr(feature = "profile", flame)]
pub fn unpack(&self, key: &Key) -> Result<Vec<u8>> {
if self.hmac.verify_hmac(&self.mac, &self.data, key) {
let decrypted_data = self.encryption.decrypt(&self.data, key)?;
let decompressed_data = self.compression.decompress(decrypted_data)?;
Ok(decompressed_data)
} else {
Err(anyhow!("hmac verification failed"))
}
}
#[cfg_attr(tarpaulin, skip)]
pub fn len(&self) -> usize {
self.data.len()
}
#[cfg_attr(tarpaulin, skip)]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
#[cfg_attr(tarpaulin, skip)]
pub fn get_bytes(&self) -> &[u8] {
&self.data
}
pub fn get_id(&self) -> ChunkID {
self.id
}
#[cfg(test)]
#[cfg_attr(tarpaulin, skip)]
pub fn break_data(&mut self, index: usize) {
let val = self.data[index];
if val == 0 {
self.data[index] = 1;
} else {
self.data[index] = 0;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn chunk_with_settings(compression: Compression, encryption: Encryption, hmac: HMAC) {
let data_string =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
let data_bytes = data_string.as_bytes().to_vec();
println!("Data: \n:{:X?}", data_bytes);
println!("{:?} {:?} {:?}", compression, hmac, encryption);
let key = Key::random(32);
let packed = Chunk::pack(data_bytes, compression, encryption, hmac, &key);
let output_bytes = packed.unpack(&key).unwrap();
assert_eq!(data_string.as_bytes().to_vec(), output_bytes);
}
#[test]
fn all_combos() {
let compressions = [Compression::NoCompression, Compression::ZStd { level: 1 }];
let encryptions = [
Encryption::NoEncryption,
Encryption::new_aes256cbc(),
Encryption::new_aes256ctr(),
];
let hmacs = [HMAC::SHA256, HMAC::Blake2b, HMAC::Blake2bp];
for c in compressions.iter() {
for e in encryptions.iter() {
for h in hmacs.iter() {
chunk_with_settings(*c, *e, *h);
}
}
}
}
#[test]
fn detect_bad_data() {
let data_string = "I am but a humble test string";
let data_bytes = data_string.as_bytes().to_vec();
let compression = Compression::NoCompression;
let encryption = Encryption::NoEncryption;
let hmac = HMAC::SHA256;
let key = Key::random(32);
let mut packed = Chunk::pack(data_bytes, compression, encryption, hmac, &key);
packed.break_data(5);
let result = packed.unpack(&key);
assert!(result.is_err());
}
#[test]
fn chunk_id_equality() {
let data1 = [1_u8; 64];
let data2 = [2_u8; 64];
let id = ChunkID::new(&data1);
assert!(id.verify(&data1));
assert!(!id.verify(&data2));
}
}