use ::std::fmt::Display;
use ::std::fmt::Error;
use ::std::fmt::Formatter;
use ::std::hash::Hasher;
use ::std::num::NonZeroU32;
use ::ring::pbkdf2::{derive, PBKDF2_HMAC_SHA512};
use ::twox_hash::XxHash64;
use crate::util::base64::base64str_to_u8s;
use crate::util::base64::u8s_to_base64str;
use crate::util::FedResult;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChecksumType {
#[allow(non_camel_case_types)]
Xxhash_Sha256_b64,
}
impl ChecksumType {
#[allow(dead_code)]
pub fn parse(input: &str) -> FedResult<Self> {
if "xx_sha256" == input {
Ok(ChecksumType::Xxhash_Sha256_b64)
} else {
Err(format!("unrecognized checksum type: '{}'", input))
}
}
}
impl Display for ChecksumType {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
match self {
ChecksumType::Xxhash_Sha256_b64 => f.write_str("xx_sha256")?,
};
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Checksum {
typ: ChecksumType,
value: Vec<u8>,
}
impl Checksum {
#[cfg(test)]
pub fn fixed_for_test(data: Vec<u8>) -> Self {
Checksum {
typ: ChecksumType::Xxhash_Sha256_b64,
value: data,
}
}
pub fn parse(input: &str) -> FedResult<Self> {
if &input[..9] == "xx_sha256" {
return Ok(Checksum {
typ: ChecksumType::Xxhash_Sha256_b64,
value: base64str_to_u8s(&input[10..])?,
});
}
Err(format!(
"failed to parse checksum format: {}",
input.split(' ').next().unwrap()
))
}
}
impl Display for Checksum {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.write_str(&self.typ.to_string())?;
f.write_str(" ")?;
f.write_str(&u8s_to_base64str(&self.value))?;
Ok(())
}
}
pub fn calculate_checksum(data: &[u8], start_progress: &mut impl FnMut()) -> Checksum {
start_progress();
let mut hasher = XxHash64::with_seed(5_771_919_056_451_745_621);
for b in data {
hasher.write_u8(*b);
}
let xxhash = hasher.finish().to_le_bytes();
let mut shahash = vec![0; 16];
derive(
PBKDF2_HMAC_SHA512,
NonZeroU32::new(1).unwrap(),
&[],
&xxhash,
&mut shahash,
);
Checksum {
typ: ChecksumType::Xxhash_Sha256_b64,
value: shahash,
}
}
#[cfg(test)]
mod tests {
use crate::files::mockfile::generate_test_file_content_for_test;
use super::*;
#[test]
fn parse() {
let input = "xx_sha256 AQIDBAAABQYHCP-qWg";
let parsed = Checksum::parse(input).unwrap();
let expected = Checksum::fixed_for_test(vec![1, 2, 3, 4, 0, 0, 5, 6, 7, 8, 255, 170, 90]);
assert_eq!(expected, parsed);
}
#[test]
fn calculate() {
let data = generate_test_file_content_for_test(15_001);
let checksum = calculate_checksum(&data, &mut || {});
assert_eq!(
vec![219, 36, 108, 103, 132, 201, 242, 88, 202, 217, 207, 138, 186, 93, 68, 203],
checksum.value,
);
}
}