#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashVersion {
Legacy = 0,
HalfMd4 = 1,
Tea = 2,
LegacyUnsigned = 3,
HalfMd4Unsigned = 4,
TeaUnsigned = 5,
}
impl HashVersion {
pub fn from_u8(v: u8) -> Option<Self> {
Some(match v {
0 => Self::Legacy,
1 => Self::HalfMd4,
2 => Self::Tea,
3 => Self::LegacyUnsigned,
4 => Self::HalfMd4Unsigned,
5 => Self::TeaUnsigned,
_ => return None,
})
}
pub fn is_unsigned(self) -> bool {
matches!(
self,
Self::LegacyUnsigned | Self::HalfMd4Unsigned | Self::TeaUnsigned
)
}
fn byte_widening(self) -> ByteWidening {
if self.is_unsigned() {
ByteWidening::ZeroExtend
} else {
ByteWidening::SignExtend
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NameHash {
pub major: u32,
pub minor: u32,
}
pub fn name_hash(name: &[u8], version: HashVersion, seed: &[u32; 4]) -> NameHash {
let widening = version.byte_widening();
let (major, minor) = match version {
HashVersion::Legacy | HashVersion::LegacyUnsigned => {
(LegacyRecurrence::digest(name, widening), 0)
}
HashVersion::HalfMd4 | HashVersion::HalfMd4Unsigned => {
chain_blocks::<8, Md4HalfRounds>(name, widening, seed)
}
HashVersion::Tea | HashVersion::TeaUnsigned => {
chain_blocks::<4, TeaCipher>(name, widening, seed)
}
};
let mut major = major & !1;
if major == END_OF_DIRECTORY_MAJOR {
major = END_OF_DIRECTORY_MAJOR - 2;
}
NameHash { major, minor }
}
const END_OF_DIRECTORY_MAJOR: u32 = 0xFFFF_FFFE;
pub fn effective_version(root_version: HashVersion, unsigned_hash: bool) -> HashVersion {
if !unsigned_hash {
return root_version;
}
match root_version {
HashVersion::Legacy => HashVersion::LegacyUnsigned,
HashVersion::HalfMd4 => HashVersion::HalfMd4Unsigned,
HashVersion::Tea => HashVersion::TeaUnsigned,
already_unsigned => already_unsigned,
}
}
#[derive(Debug, Clone, Copy)]
enum ByteWidening {
SignExtend,
ZeroExtend,
}
impl ByteWidening {
fn widen(self, byte: u8) -> u32 {
match self {
ByteWidening::SignExtend => byte as i8 as i32 as u32,
ByteWidening::ZeroExtend => u32::from(byte),
}
}
}
struct WordFeeder<'a, const N: usize> {
rest: &'a [u8],
widening: ByteWidening,
}
impl<'a, const N: usize> WordFeeder<'a, N> {
const BLOCK_BYTES: usize = 4 * N;
fn new(name: &'a [u8], widening: ByteWidening) -> Self {
Self {
rest: name,
widening,
}
}
fn fill_word(remaining: usize) -> u32 {
let r = remaining as u32;
r | (r << 8) | (r << 16) | (r << 24)
}
fn pack(&self, bytes: &[u8], fill: u32) -> u32 {
bytes.iter().fold(fill, |word, &b| {
(word << 8).wrapping_add(self.widening.widen(b))
})
}
}
impl<const N: usize> Iterator for WordFeeder<'_, N> {
type Item = [u32; N];
fn next(&mut self) -> Option<[u32; N]> {
if self.rest.is_empty() {
return None;
}
let fill = Self::fill_word(self.rest.len());
let take = self.rest.len().min(Self::BLOCK_BYTES);
let (this_block, later) = self.rest.split_at(take);
let mut block = [fill; N];
for (word, bytes) in block.iter_mut().zip(this_block.chunks(4)) {
*word = self.pack(bytes, fill);
}
self.rest = later;
Some(block)
}
}
trait BlockMixer<const N: usize> {
fn absorb(state: &mut [u32; 4], block: &[u32; N]);
fn report(state: &[u32; 4]) -> (u32, u32);
}
const RFC1320_INITIAL_STATE: [u32; 4] = [0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476];
fn starting_state(seed: &[u32; 4]) -> [u32; 4] {
if seed.iter().all(|&w| w == 0) {
RFC1320_INITIAL_STATE
} else {
*seed
}
}
fn chain_blocks<const N: usize, M: BlockMixer<N>>(
name: &[u8],
widening: ByteWidening,
seed: &[u32; 4],
) -> (u32, u32) {
let mut state = starting_state(seed);
for block in WordFeeder::<N>::new(name, widening) {
M::absorb(&mut state, &block);
}
M::report(&state)
}
struct Md4HalfRounds;
fn rfc1320_f(x: u32, y: u32, z: u32) -> u32 {
(x & y) | (!x & z)
}
fn rfc1320_g(x: u32, y: u32, z: u32) -> u32 {
(x & y) | (x & z) | (y & z)
}
fn rfc1320_h(x: u32, y: u32, z: u32) -> u32 {
x ^ y ^ z
}
const RFC1320_ROUND2_CONSTANT: u32 = 0x5A82_7999;
const RFC1320_ROUND3_CONSTANT: u32 = 0x6ED9_EBA1;
struct Md4Round {
mix: fn(u32, u32, u32) -> u32,
constant: u32,
words: [usize; 8],
rotations: [u32; 4],
}
const HALF_MD4_ROUNDS: [Md4Round; 3] = [
Md4Round {
mix: rfc1320_f,
constant: 0,
words: [0, 1, 2, 3, 4, 5, 6, 7],
rotations: [3, 7, 11, 19],
},
Md4Round {
mix: rfc1320_g,
constant: RFC1320_ROUND2_CONSTANT,
words: [1, 3, 5, 7, 0, 2, 4, 6],
rotations: [3, 5, 9, 13],
},
Md4Round {
mix: rfc1320_h,
constant: RFC1320_ROUND3_CONSTANT,
words: [3, 7, 2, 6, 1, 5, 0, 4],
rotations: [3, 9, 11, 15],
},
];
impl BlockMixer<8> for Md4HalfRounds {
fn absorb(state: &mut [u32; 4], block: &[u32; 8]) {
let mut reg = *state;
for round in &HALF_MD4_ROUNDS {
for (step, &word) in round.words.iter().enumerate() {
let target = (4 - step % 4) % 4;
let x = reg[(target + 1) % 4];
let y = reg[(target + 2) % 4];
let z = reg[(target + 3) % 4];
reg[target] = reg[target]
.wrapping_add((round.mix)(x, y, z))
.wrapping_add(block[word])
.wrapping_add(round.constant)
.rotate_left(round.rotations[step % 4]);
}
}
for (s, r) in state.iter_mut().zip(reg) {
*s = s.wrapping_add(r);
}
}
fn report(state: &[u32; 4]) -> (u32, u32) {
(state[1], state[2])
}
}
struct TeaCipher;
const TEA_DELTA: u32 = 0x9E37_79B9;
const TEA_CYCLES: u32 = 16;
fn tea_encipher(plain: [u32; 2], key: &[u32; 4], cycles: u32) -> [u32; 2] {
let [mut y, mut z] = plain;
let mut sum: u32 = 0;
for _ in 0..cycles {
sum = sum.wrapping_add(TEA_DELTA);
y = y.wrapping_add(
(z << 4).wrapping_add(key[0]) ^ z.wrapping_add(sum) ^ (z >> 5).wrapping_add(key[1]),
);
z = z.wrapping_add(
(y << 4).wrapping_add(key[2]) ^ y.wrapping_add(sum) ^ (y >> 5).wrapping_add(key[3]),
);
}
[y, z]
}
impl BlockMixer<4> for TeaCipher {
fn absorb(state: &mut [u32; 4], block: &[u32; 4]) {
let [y, z] = tea_encipher([state[0], state[1]], block, TEA_CYCLES);
state[0] = state[0].wrapping_add(y);
state[1] = state[1].wrapping_add(z);
}
fn report(state: &[u32; 4]) -> (u32, u32) {
(state[0], state[1])
}
}
struct LegacyRecurrence;
impl LegacyRecurrence {
const MULTIPLIER: u32 = 0x006D_22F5;
const FIRST_TERMS: (u32, u32) = (0x37AB_E8F9, 0x12A3_FE2D);
const REDUCTION: u32 = 0x7FFF_FFFF;
fn digest(name: &[u8], widening: ByteWidening) -> u32 {
let (_older, newest) = name
.iter()
.fold(Self::FIRST_TERMS, |(older, newer), &byte| {
let scaled = widening.widen(byte).wrapping_mul(Self::MULTIPLIER);
let mut next = older.wrapping_add(newer ^ scaled);
if next & 0x8000_0000 != 0 {
next = next.wrapping_sub(Self::REDUCTION);
}
(newer, next)
});
newest << 1
}
}