use std::{backtrace::Backtrace, fmt::Display, num::ParseIntError, str::FromStr};
use bytemuck::{Pod, PodCastError, Zeroable};
use serde::{Deserialize, Deserializer, Serialize};
use snafu::Snafu;
use crate::crypto::hmac_sha1::HmacSha1;
#[repr(C)]
#[derive(Clone, Copy, Zeroable, Pod, PartialEq, Eq)]
pub struct HmacSha1Signature {
pub hash: [u8; 20],
}
#[derive(Debug, Snafu)]
pub enum HmacSha1SignatureError {
#[snafu(display("the HMAC-SHA1 signature table must be a multiple of {} bytes:\n{backtrace}", size_of::<HmacSha1Signature>()))]
InvalidSize {
backtrace: Backtrace,
},
#[snafu(display("expected {expected}-alignment for HMAC-SHA1 signature table but got {actual}-alignment:\n{backtrace}"))]
Misaligned {
expected: usize,
actual: usize,
backtrace: Backtrace,
},
}
impl HmacSha1Signature {
pub fn from_hmac_sha1(hmac_sha1: &HmacSha1, data: &[u8]) -> Self {
let hash = hmac_sha1.compute(data);
Self { hash }
}
pub fn set(&mut self, hash: [u8; 20]) {
self.hash = hash;
}
fn check_size(data: &[u8]) -> Result<(), HmacSha1SignatureError> {
let size = size_of::<Self>();
if !data.len().is_multiple_of(size) {
InvalidSizeSnafu {}.fail()
} else {
Ok(())
}
}
fn handle_pod_cast<T>(result: Result<T, PodCastError>, addr: usize) -> Result<T, HmacSha1SignatureError> {
match result {
Ok(signatures) => Ok(signatures),
Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => {
MisalignedSnafu { expected: size_of::<Self>(), actual: addr }.fail()
}
Err(PodCastError::AlignmentMismatch) => panic!(),
Err(PodCastError::OutputSliceWouldHaveSlop) => panic!(),
Err(PodCastError::SizeMismatch) => unreachable!(),
}
}
pub fn borrow_from_slice(data: &'_ [u8]) -> Result<&'_ [Self], HmacSha1SignatureError> {
Self::check_size(data)?;
let addr = data as *const [u8] as *const () as usize;
Self::handle_pod_cast(bytemuck::try_cast_slice(data), addr)
}
pub fn borrow_from_slice_mut(data: &'_ mut [u8]) -> Result<&'_ mut [Self], HmacSha1SignatureError> {
Self::check_size(data)?;
let addr = data as *const [u8] as *const () as usize;
Self::handle_pod_cast(bytemuck::try_cast_slice_mut(data), addr)
}
}
impl Display for HmacSha1Signature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for byte in &self.hash {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}
#[derive(Debug, Snafu)]
pub enum HmacSha1SignatureParseError {
#[snafu(display("invalid length: {length}:\n{backtrace}"))]
InvalidLength {
length: usize,
backtrace: Backtrace,
},
#[snafu(display("invalid hex string '{string}':{error}\n{backtrace}"))]
ParseInt {
error: ParseIntError,
string: String,
backtrace: Backtrace,
},
}
impl FromStr for HmacSha1Signature {
type Err = HmacSha1SignatureParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 40 {
return InvalidLengthSnafu { length: s.len() }.fail();
}
let mut hash = [0u8; 20];
for i in 0..20 {
let byte_str = &s[i * 2..i * 2 + 2];
hash[i] = u8::from_str_radix(byte_str, 16)
.map_err(|error| ParseIntSnafu { error, string: byte_str.to_string() }.build())?;
}
Ok(Self { hash })
}
}
impl Serialize for HmacSha1Signature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
impl<'de> Deserialize<'de> for HmacSha1Signature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(serde::de::Error::custom)
}
}