ssdeep/generate/hashes/partial_fnv.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright Andrew Tridgell <tridge@samba.org> 2002
// SPDX-FileCopyrightText: Copyright (C) 2006 ManTech International Corporation
// SPDX-FileCopyrightText: Copyright (C) 2013 Helmut Grohne <helmut@subdivi.de>
// SPDX-FileCopyrightText: Copyright (C) 2017, 2023–2025 Tsukasa OI <floss_ssdeep@irq.a4lg.com>
//! Partial FNV-1 hash as used in the fuzzy hash generator.
use core::ops::AddAssign;
use crate::hash::block::block_hash;
#[cfg(not(feature = "opt-reduce-fnv-table"))]
use crate::macros::invariant;
/// Hasher which computes the lowest 6 bits of a 32-bit FNV-1 variant.
///
/// This variant of FNV-1 hash has the regular prime constant of `0x01000193`
/// but has a different initial state: `0x28021967` (in contrast to regular
/// FNV-1-32's `0x811c9dc5`).
///
/// Since ssdeep only uses the lowest 6 bits of the hash value, it ignores any
/// higher bits, enabling updating the state table-based (instead of multiply
/// and xor).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PartialFNVHash(u8);
impl PartialFNVHash {
/// The initial value of this variant of the FNV-1 hash (`0x28021967`).
///
/// This is different from the regular variant of the FNV-1 hash
/// (FNV-1-32: `0x811c9dc5`).
const OLD_HASH_INIT: u32 = 0x28021967;
/// The initial value of this variant of the partial FNV-1 hash.
///
/// This is generated by masking [`Self::OLD_HASH_INIT`].
const FNV_HASH_INIT: u8 = (Self::OLD_HASH_INIT % block_hash::ALPHABET_SIZE as u32) as u8;
/// The prime constant for the FNV-1 hash.
///
/// This is the same as the regular FNV-1.
const FNV_HASH_PRIME: u32 = 0x01000193;
/// The partial FNV-1 update table.
///
/// This is used by the [`update_by_byte()`](Self::update_by_byte()) method
/// to update the current state using a new byte, unless the feature
/// `opt-reduce-fnv-table` is enabled.
#[cfg(not(feature = "opt-reduce-fnv-table"))]
const FNV_TABLE: [[u8; block_hash::ALPHABET_SIZE]; block_hash::ALPHABET_SIZE] = {
let mut array = [[0u8; block_hash::ALPHABET_SIZE]; block_hash::ALPHABET_SIZE];
let mut state = 0u8;
while state < 64 {
let mut ch = 0u8;
while ch < 64 {
array[state as usize][ch as usize] =
(((state as u32).wrapping_mul(Self::FNV_HASH_PRIME) as u8) ^ ch)
% block_hash::ALPHABET_SIZE as u8;
ch += 1;
}
state += 1;
}
array
};
/// Creates a new [`PartialFNVHash`] with the initial value.
#[inline]
pub fn new() -> Self {
PartialFNVHash(Self::FNV_HASH_INIT)
}
/// Updates the hash value by processing a byte.
#[inline]
pub fn update_by_byte(&mut self, ch: u8) -> &mut Self {
cfg_if::cfg_if! {
if #[cfg(not(feature = "opt-reduce-fnv-table"))] {
invariant!((self.value() as usize) < block_hash::ALPHABET_SIZE);
self.0 = Self::FNV_TABLE
[self.value() as usize] // grcov-excl-br-line:ARRAY
[ch as usize % block_hash::ALPHABET_SIZE]; // grcov-excl-br-line:ARRAY
} else {
self.0 = ((self.0 as u32).wrapping_mul(Self::FNV_HASH_PRIME) ^ (ch as u32)) as u8;
}
}
self
}
/// Updates the hash value by processing an iterator of [`u8`].
pub fn update_by_iter(&mut self, iter: impl Iterator<Item = u8>) -> &mut Self {
for ch in iter {
self.update_by_byte(ch);
}
self
}
/// Updates the hash value by processing a slice of [`u8`].
pub fn update(&mut self, buf: &[u8]) -> &mut Self {
for &ch in buf.iter() {
self.update_by_byte(ch);
}
self
}
/// Returns the current hash value.
///
/// Note that there's no "finalization" on the FNV hash.
/// You can even continue updating after reading the hash value.
#[inline]
pub fn value(&self) -> u8 {
cfg_if::cfg_if! {
if #[cfg(not(feature = "opt-reduce-fnv-table"))] {
invariant!(self.0 < (block_hash::ALPHABET_SIZE as u8));
self.0
} else {
self.0 & (block_hash::ALPHABET_SIZE as u8).wrapping_sub(1)
}
}
}
}
impl Default for PartialFNVHash {
fn default() -> Self {
Self::new()
}
}
impl AddAssign<&[u8]> for PartialFNVHash {
/// Updates the hash value by processing a slice of [`u8`].
#[inline(always)]
fn add_assign(&mut self, buffer: &[u8]) {
self.update(buffer);
}
}
impl<const N: usize> AddAssign<&[u8; N]> for PartialFNVHash {
/// Updates the hash value by processing an array of [`u8`].
#[inline(always)]
fn add_assign(&mut self, buffer: &[u8; N]) {
self.update(&buffer[..]);
}
}
impl AddAssign<u8> for PartialFNVHash {
/// Updates the hash value by processing a byte.
#[inline(always)]
fn add_assign(&mut self, byte: u8) {
self.update_by_byte(byte);
}
}
/// Constant assertions related to this module.
#[doc(hidden)]
mod const_asserts {
use static_assertions::{const_assert, const_assert_eq};
use super::*;
// Compare with original ssdeep constants
// fuzzy.c: HASH_INIT
const_assert_eq!(PartialFNVHash::FNV_HASH_INIT, 0x27);
// ALPHABET_SIZE and FNV_HASH_INIT properties (for PartialFNVHash)
const_assert!(0 < block_hash::ALPHABET_SIZE && block_hash::ALPHABET_SIZE <= 256);
const_assert!(block_hash::ALPHABET_SIZE.is_power_of_two());
const_assert!((PartialFNVHash::FNV_HASH_INIT as u16) < (block_hash::ALPHABET_SIZE as u16));
}
pub(crate) mod test_utils;
mod tests;