#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::fmt::Write;
use std::str::FromStr;
use std::sync::Arc;
use sha2::Digest;
#[cfg(feature = "serde")]
mod serde;
macro_rules! define_hash_algorithms {
($($variant:ident, $name:literal, [$($alias:literal),*], $size:literal, $hasher:ty;)*) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum HashAlgorithm {
$(
$variant,
)*
}
impl HashAlgorithm {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
$(
Self::$variant => $name,
)*
}
}
#[must_use]
pub const fn names(self) -> &'static [&'static str] {
match self {
$(
Self::$variant => &[$name $(, $alias)*],
)*
}
}
pub fn hasher(self) -> Hasher {
match self {
$(
Self::$variant => Hasher {
algorithm: self,
inner: HasherInner::$variant(<$hasher>::new())
},
)*
}
}
#[must_use]
pub const fn hash_size(self) -> usize {
match self {
$(
Self::$variant => $size,
)*
}
}
}
impl FromStr for HashAlgorithm {
type Err = InvalidAlgorithmError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
$(
$name $(| $alias)* => Ok(Self::$variant),
)*
_ => Err(InvalidAlgorithmError),
}
}
}
#[derive(Debug, Clone)]
enum HasherInner {
$(
$variant($hasher),
)*
}
impl HasherInner {
fn update(&mut self, bytes: &[u8]) {
match self {
$(
HasherInner::$variant(hasher) => hasher.update(bytes),
)*
}
}
#[must_use]
fn finalize<D>(self) -> D
where
D: for<'slice> From<&'slice [u8]>
{
match self {
$(
HasherInner::$variant(hasher) => hasher.finalize().as_slice().into(),
)*
}
}
}
};
}
define_hash_algorithms! {
Sha256, "sha256", [], 32, sha2::Sha256;
Sha512_256, "sha512_256", ["sha512-256"], 32, sha2::Sha512_256;
Sha512, "sha512", [], 64, sha2::Sha512;
}
impl HashAlgorithm {
#[must_use]
pub fn hash<D>(self, bytes: &[u8]) -> HashDigest<D>
where
D: for<'slice> From<&'slice [u8]>,
{
let mut hasher = self.hasher();
hasher.update(bytes);
hasher.finalize()
}
}
impl std::fmt::Display for HashAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
f.write_str(self.names().last().expect("algorithm has names"))
} else {
match self {
HashAlgorithm::Sha512_256 if cfg!(feature = "legacy") => f.write_str("sha512-256"),
_ => f.write_str(self.name()),
}
}
}
}
#[derive(Debug)]
pub struct InvalidAlgorithmError;
impl std::fmt::Display for InvalidAlgorithmError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("invalid hash algorithm")
}
}
impl std::error::Error for InvalidAlgorithmError {}
#[derive(Debug, Clone)]
#[must_use]
pub struct Hasher {
algorithm: HashAlgorithm,
inner: HasherInner,
}
impl Hasher {
#[must_use]
pub const fn algorithm(&self) -> HashAlgorithm {
self.algorithm
}
pub fn update(&mut self, bytes: &[u8]) {
self.inner.update(bytes);
}
#[must_use]
pub fn finalize<D>(self) -> HashDigest<D>
where
D: for<'slice> From<&'slice [u8]>,
{
HashDigest {
algorithm: self.algorithm,
raw: self.inner.finalize(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HashDigest<D = Arc<[u8]>> {
algorithm: HashAlgorithm,
raw: D,
}
impl<D> HashDigest<D>
where
D: AsRef<[u8]>,
{
pub fn new(algorithm: HashAlgorithm, raw: D) -> Result<Self, InvalidDigestError> {
if raw.as_ref().len() != algorithm.hash_size() {
return Err(InvalidDigestError("invalid digest size"));
}
Ok(Self::new_unchecked(algorithm, raw))
}
pub const fn new_unchecked(algorithm: HashAlgorithm, raw: D) -> Self {
Self { algorithm, raw }
}
pub const fn algorithm(&self) -> HashAlgorithm {
self.algorithm
}
pub fn raw(&self) -> &[u8] {
self.raw.as_ref()
}
pub fn raw_hex_string(&self) -> String {
hex::encode(&self.raw)
}
pub fn into_inner(self) -> D {
self.raw
}
}
impl<D> std::fmt::Display for HashDigest<D>
where
D: AsRef<[u8]>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.algorithm.fmt(f)?;
if cfg!(feature = "legacy") {
f.write_char(':')?;
} else {
f.write_char('_')?;
}
f.write_str(&self.raw_hex_string())?;
Ok(())
}
}
impl<D> AsRef<[u8]> for HashDigest<D>
where
D: AsRef<[u8]>,
{
fn as_ref(&self) -> &[u8] {
self.raw.as_ref()
}
}
impl<D: From<Vec<u8>>> FromStr for HashDigest<D> {
type Err = InvalidDigestError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((algorithm, digest)) = s.rsplit_once([':', '_']) else {
return Err(InvalidDigestError("missing delimiter, expected ':' or '_'"));
};
let algorithm = HashAlgorithm::from_str(algorithm)?;
let Ok(raw) = hex::decode(digest) else {
return Err(InvalidDigestError("digest is not a hex string"));
};
if raw.len() != algorithm.hash_size() {
return Err(InvalidDigestError("invalid digest size"));
}
Ok(Self {
algorithm,
raw: raw.into(),
})
}
}
#[derive(Debug)]
pub struct InvalidDigestError(pub(crate) &'static str);
impl std::fmt::Display for InvalidDigestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
impl std::error::Error for InvalidDigestError {}
impl From<InvalidAlgorithmError> for InvalidDigestError {
fn from(_: InvalidAlgorithmError) -> Self {
InvalidDigestError("invalid hash algorithm")
}
}