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"] }
|
bytesize = { version = "2.0.1", features = ["serde"] }
|
||||||
bytes = { version = "1.10.1", features = ["serde"] }
|
bytes = { version = "1.10.1", features = ["serde"] }
|
||||||
url = { version = "2.5.4", 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),
|
bounds: syn::parse_quote!(#eva::str::FixedUtf8),
|
||||||
};
|
};
|
||||||
let predicate = syn::WherePredicate::Type(predicate);
|
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.predicates.push(predicate);
|
||||||
|
clause
|
||||||
} else {
|
} else {
|
||||||
generics.where_clause = Some(syn::parse_quote! {
|
generics.where_clause = Some(syn::parse_quote! {
|
||||||
where #predicate
|
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();
|
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;
|
use std::hash::BuildHasherDefault;
|
||||||
|
|
||||||
pub type HashMap<K, V> = hashbrown::HashMap<K, 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::Hasher>>;
|
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::{
|
pub use compact_str::{
|
||||||
CompactString, CompactStringExt, ToCompactString, ToCompactStringError, format_compact,
|
CompactString, CompactStringExt, ToCompactString, ToCompactStringError, format_compact,
|
||||||
};
|
};
|
||||||
|
use perfect_derive::perfect_derive;
|
||||||
|
|
||||||
use crate::data;
|
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.
|
/// Simple parse error.
|
||||||
#[data(copy, error, display(doc), crate = crate)]
|
#[data(copy, error, display(doc), crate = crate)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue