add blake3 hashing
This commit is contained in:
parent
75a589f235
commit
0ceb55546b
7 changed files with 266 additions and 6 deletions
|
@ -40,3 +40,4 @@ seq-macro = "0.3.6"
|
|||
bytesize = { version = "2.0.1", features = ["serde"] }
|
||||
bytes = { version = "1.10.1", features = ["serde"] }
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
blake3 = "1.8.2"
|
||||
|
|
|
@ -52,13 +52,22 @@ pub fn transform(args: TokenStream, body: TokenStream) -> TokenStream {
|
|||
bounds: syn::parse_quote!(#eva::str::FixedUtf8),
|
||||
};
|
||||
let predicate = syn::WherePredicate::Type(predicate);
|
||||
if let Some(ref mut clause) = generics.where_clause {
|
||||
let clause = if let Some(ref mut clause) = generics.where_clause {
|
||||
clause.predicates.push(predicate);
|
||||
clause
|
||||
} else {
|
||||
generics.where_clause = Some(syn::parse_quote! {
|
||||
where #predicate
|
||||
});
|
||||
}
|
||||
generics.where_clause.as_mut().unwrap()
|
||||
};
|
||||
|
||||
clause.predicates.push(syn::WherePredicate::Type(syn::PredicateType {
|
||||
lifetimes: None,
|
||||
bounded_ty: error_ty.clone(),
|
||||
colon_token: Token),
|
||||
bounds: syn::parse_quote!(::std::convert::From<<#ty as ::std::str::FromStr>::Err>),
|
||||
}));
|
||||
}
|
||||
|
||||
let (ig, tyg, where_clause) = generics.split_for_impl();
|
||||
|
|
|
@ -2,5 +2,5 @@ pub use hashbrown::{Equivalent, HashTable, hash_map, hash_table};
|
|||
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
pub type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<crate::hash::Hasher>>;
|
||||
pub type HashSet<V> = hashbrown::HashSet<V, BuildHasherDefault<crate::hash::Hasher>>;
|
||||
pub type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<crate::hash::U64Hasher>>;
|
||||
pub type HashSet<V> = hashbrown::HashSet<V, BuildHasherDefault<crate::hash::U64Hasher>>;
|
||||
|
|
184
src/hash.rs
184
src/hash.rs
|
@ -1 +1,183 @@
|
|||
pub type Hasher = ahash::AHasher;
|
||||
use std::{borrow::Cow, fmt, marker::PhantomData, mem, str::FromStr};
|
||||
|
||||
use perfect_derive::perfect_derive;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::shit_happens,
|
||||
int, single_ascii_char, str,
|
||||
str::{HasPattern, ParseError, PhantomDataStr, Seq},
|
||||
};
|
||||
|
||||
pub mod blake3;
|
||||
|
||||
pub type U64Hasher = ahash::AHasher;
|
||||
|
||||
#[int(u8, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z', crate = crate)]
|
||||
pub enum HexChar {}
|
||||
|
||||
single_ascii_char!(HexChar);
|
||||
unsafe impl str::FixedUtf8 for HexChar {}
|
||||
|
||||
/// Hexadecimal representation of the hash.
|
||||
#[str(fixed(error = ParseError), crate = crate)]
|
||||
pub struct Hex<IH: IsHash, const SIZE: usize> {
|
||||
buf: Seq<SIZE, Seq<2, HexChar>>,
|
||||
_marker: PhantomDataStr<IH>,
|
||||
}
|
||||
|
||||
impl<IH: IsHash, const SIZE: usize> Hex<IH, SIZE> {
|
||||
pub const fn parse(self) -> Hash<IH, SIZE> {
|
||||
const fn hexc(c: u8) -> u8 {
|
||||
match c.to_ascii_uppercase() {
|
||||
b'0'..=b'9' => c - b'0',
|
||||
b'A'..=b'F' => 10 + c - b'A',
|
||||
_ => shit_happens(),
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = [0_u8; SIZE];
|
||||
let mut offset = 0_usize;
|
||||
|
||||
while offset < SIZE {
|
||||
let [upper, lower] = self.buf.0[offset].0;
|
||||
let upper = hexc(upper.into_inner());
|
||||
let lower = hexc(lower.into_inner());
|
||||
|
||||
out[offset] = (upper << 4) | lower;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
Hash::new(out)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait State {
|
||||
fn update(&mut self, buf: &[u8]);
|
||||
}
|
||||
|
||||
#[perfect_derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Hash<IH: IsHash, const SIZE: usize> {
|
||||
buf: [u8; SIZE],
|
||||
_marker: PhantomData<[IH; 0]>,
|
||||
}
|
||||
|
||||
impl<IH: IsHash, const SIZE: usize> fmt::Display for Hash<IH, SIZE> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(self.hex().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<IH: IsHash, const SIZE: usize> JsonSchema for Hash<IH, SIZE> {
|
||||
fn schema_id() -> Cow<'static, str> {
|
||||
format!("{}::{}", module_path!(), Self::schema_name()).into()
|
||||
}
|
||||
|
||||
fn schema_name() -> Cow<'static, str> {
|
||||
format!("Hash_{}__{}", IH::schema_name(), SIZE).into()
|
||||
}
|
||||
|
||||
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
_ = generator;
|
||||
|
||||
schemars::json_schema!({"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": format!("Hexadecimal representation of {} hash, only in human-readable formats", IH::schema_name()),
|
||||
"minLength": SIZE * 2,
|
||||
"maxLength": SIZE * 2,
|
||||
"pattern": <Hex<IH, SIZE>>::regex_pat_fullmatch(),
|
||||
},
|
||||
{"type": "array", "minItems": SIZE, "maxItems": SIZE, "items": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 20
|
||||
}}
|
||||
]})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, IH: IsHash, const SIZE: usize> Deserialize<'de> for Hash<IH, SIZE>
|
||||
where
|
||||
[u8; SIZE]: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
if deserializer.is_human_readable() {
|
||||
let hex = Hex::deserialize(deserializer)?;
|
||||
Ok(hex.parse())
|
||||
} else {
|
||||
let buf = <[u8; SIZE] as Deserialize<'de>>::deserialize(deserializer)?;
|
||||
Ok(Self {
|
||||
buf,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<IH: IsHash, const SIZE: usize> Serialize for Hash<IH, SIZE> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
if serializer.is_human_readable() {
|
||||
self.hex().serialize(serializer)
|
||||
} else {
|
||||
self.buf.serialize(serializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<IH: IsHash, const SIZE: usize> FromStr for Hash<IH, SIZE> {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Hex::from_str(s).map(|h| h.parse())
|
||||
}
|
||||
}
|
||||
|
||||
impl<IH: IsHash, const SIZE: usize> Hash<IH, SIZE> {
|
||||
pub const fn new(buf: [u8; SIZE]) -> Self {
|
||||
Self {
|
||||
buf,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn hex(&self) -> Hex<IH, SIZE> {
|
||||
const fn hexc(c: u8) -> u8 {
|
||||
match c {
|
||||
0..=9 => b'0' + c,
|
||||
10..=15 => b'a' + (c - 10),
|
||||
_ => shit_happens(),
|
||||
}
|
||||
}
|
||||
let mut out: [[u8; 2]; SIZE] = [[0, 0]; SIZE];
|
||||
let mut offset = 0_usize;
|
||||
|
||||
while offset < SIZE {
|
||||
let num = self.buf[offset];
|
||||
let [upper, lower] = &mut out[offset];
|
||||
*upper = hexc(num >> 4);
|
||||
*lower = hexc(num & 0b1111);
|
||||
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
Hex {
|
||||
buf: unsafe { mem::transmute_copy(&out) },
|
||||
_marker: PhantomDataStr(PhantomData),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IsHash: JsonSchema + Copy + 'static + Send + Sync + Ord {
|
||||
const FULL_SIZE: usize;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
32
src/hash/blake3.rs
Normal file
32
src/hash/blake3.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use crate::hash::{self, IsHash, State};
|
||||
|
||||
pub const FULL_SIZE: usize = 32;
|
||||
pub type Hash = hash::Hash<Blake3, FULL_SIZE>;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Hasher(blake3::Hasher);
|
||||
|
||||
impl Hasher {
|
||||
pub fn finish(&self) -> Hash {
|
||||
let h = self.0.finalize();
|
||||
Hash::new(*h.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl State for Hasher {
|
||||
fn update(&mut self, buf: &[u8]) {
|
||||
self.0.update(buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::data(copy, ord, crate = crate)]
|
||||
pub enum Blake3 {}
|
||||
|
||||
impl IsHash for Blake3 {
|
||||
const FULL_SIZE: usize = FULL_SIZE;
|
||||
}
|
||||
|
||||
pub fn hash(bytes: &[u8]) -> Hash {
|
||||
let h = blake3::hash(bytes);
|
||||
Hash::new(*h.as_bytes())
|
||||
}
|
11
src/hash/tests.rs
Normal file
11
src/hash/tests.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use crate::hash::blake3;
|
||||
|
||||
#[test]
|
||||
fn blake3_from_str_and_back_identical() {
|
||||
const STR: &str = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
|
||||
|
||||
let h: blake3::Hash = STR.parse().unwrap();
|
||||
let hex = h.hex();
|
||||
|
||||
assert_eq!(hex.as_str(), STR);
|
||||
}
|
27
src/str.rs
27
src/str.rs
|
@ -1,8 +1,9 @@
|
|||
use std::{fmt, mem::MaybeUninit, slice, str::FromStr};
|
||||
use std::{fmt, marker::PhantomData, mem::MaybeUninit, slice, str::FromStr};
|
||||
|
||||
pub use compact_str::{
|
||||
CompactString, CompactStringExt, ToCompactString, ToCompactStringError, format_compact,
|
||||
};
|
||||
use perfect_derive::perfect_derive;
|
||||
|
||||
use crate::data;
|
||||
|
||||
|
@ -35,6 +36,30 @@ macro_rules! single_ascii_char {
|
|||
};
|
||||
}
|
||||
|
||||
#[perfect_derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct PhantomDataStr<C>(pub PhantomData<C>);
|
||||
|
||||
impl<C> HasPattern for PhantomDataStr<C> {
|
||||
#[inline]
|
||||
fn pat_into(buf: &mut String) {
|
||||
_ = buf;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<C> FixedUtf8 for PhantomDataStr<C> {}
|
||||
impl<C> FromStr for PhantomDataStr<C> {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.is_empty() {
|
||||
Ok(Self(PhantomData))
|
||||
} else {
|
||||
Err(ParseError::Length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple parse error.
|
||||
#[data(copy, error, display(doc), crate = crate)]
|
||||
pub enum ParseError {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue