use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use arrayref::array_ref;
use crate::consts::{BLAKE2_MAGIC, MD4_MAGIC};
use crate::crc::Crc;
use crate::hasher::BuildCrcHasher;
use crate::hashmap_variant::SecondLayerMap;
use crate::md4::{md4, md4_many, MD4_SIZE};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Signature {
signature_type: SignatureType,
block_size: u32,
crypto_hash_size: u32,
signature: Vec<u8>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IndexedSignature<'a> {
pub(crate) signature_type: SignatureType,
pub(crate) block_size: u32,
pub(crate) crypto_hash_size: u32,
pub(crate) blocks: HashMap<Crc, SecondLayerMap<&'a [u8], u32>, BuildCrcHasher>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum SignatureType {
Md4,
Blake2,
}
impl SignatureType {
const SIZE: usize = 4;
fn from_magic(bytes: [u8; Self::SIZE]) -> Option<Self> {
match u32::from_be_bytes(bytes) {
BLAKE2_MAGIC => Some(SignatureType::Blake2),
MD4_MAGIC => Some(SignatureType::Md4),
_ => None,
}
}
fn to_magic(self) -> [u8; Self::SIZE] {
match self {
SignatureType::Md4 => MD4_MAGIC,
SignatureType::Blake2 => BLAKE2_MAGIC,
}
.to_be_bytes()
}
}
#[derive(Debug)]
pub struct SignatureParseError(());
impl fmt::Display for SignatureParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid or unsupported signature")
}
}
impl Error for SignatureParseError {}
#[derive(Copy, Clone, Debug)]
pub struct SignatureOptions {
pub block_size: u32,
pub crypto_hash_size: u32,
}
impl Signature {
const HEADER_SIZE: usize = SignatureType::SIZE + 2 * 4;
pub fn calculate(buf: &[u8], options: SignatureOptions) -> Signature {
assert!(options.block_size > 0);
assert!(options.crypto_hash_size <= MD4_SIZE as u32);
let num_blocks = buf.chunks(options.block_size as usize).len();
let signature_type = SignatureType::Md4;
let mut signature = Vec::with_capacity(
Self::HEADER_SIZE + num_blocks * (Crc::SIZE + options.crypto_hash_size as usize),
);
signature.extend_from_slice(&signature_type.to_magic());
signature.extend_from_slice(&options.block_size.to_be_bytes());
signature.extend_from_slice(&options.crypto_hash_size.to_be_bytes());
let chunks = buf.chunks_exact(options.block_size as usize);
let remainder = chunks.remainder();
for (block, md4_hash) in md4_many(chunks).chain(if remainder.is_empty() {
None
} else {
Some((remainder, md4(remainder)))
}) {
let crc = Crc::new().update(block);
let crypto_hash = &md4_hash[..options.crypto_hash_size as usize];
signature.extend_from_slice(&crc.to_bytes());
signature.extend_from_slice(crypto_hash);
}
Signature {
signature_type: SignatureType::Md4,
block_size: options.block_size,
crypto_hash_size: options.crypto_hash_size,
signature,
}
}
pub fn deserialize(signature: Vec<u8>) -> Result<Signature, SignatureParseError> {
if signature.len() < Self::HEADER_SIZE {
return Err(SignatureParseError(()));
}
let signature_type = SignatureType::from_magic(*array_ref![signature, 0, 4])
.ok_or(SignatureParseError(()))?;
let block_size = u32::from_be_bytes(*array_ref![signature, 4, 4]);
let crypto_hash_size = u32::from_be_bytes(*array_ref![signature, 8, 4]);
let block_signature_size = Crc::SIZE + crypto_hash_size as usize;
if (signature.len() - Self::HEADER_SIZE) % block_signature_size != 0 {
return Err(SignatureParseError(()));
}
Ok(Signature {
signature_type,
block_size,
crypto_hash_size,
signature,
})
}
pub fn serialized(&self) -> &[u8] {
&self.signature
}
pub fn into_serialized(self) -> Vec<u8> {
self.signature
}
fn blocks(&self) -> impl ExactSizeIterator<Item = (Crc, &[u8])> {
self.signature[Self::HEADER_SIZE..]
.chunks(Crc::SIZE + self.crypto_hash_size as usize)
.map(|b| {
(
Crc::from_bytes(*array_ref!(b, 0, Crc::SIZE)),
&b[Crc::SIZE..],
)
})
}
pub fn index(&self) -> IndexedSignature<'_> {
let blocks = self.blocks();
let mut block_index: HashMap<Crc, SecondLayerMap<&[u8], u32>, BuildCrcHasher> =
HashMap::with_capacity_and_hasher(blocks.len(), BuildCrcHasher::default());
for (idx, (crc, crypto_hash)) in blocks.enumerate() {
block_index
.entry(crc)
.or_default()
.insert(crypto_hash, idx as u32);
}
block_index.shrink_to_fit();
IndexedSignature {
signature_type: self.signature_type,
block_size: self.block_size,
crypto_hash_size: self.crypto_hash_size,
blocks: block_index,
}
}
}