/* * fnv_util - utility tools for FNV hash and "FNV-style" hash operations * * This file provides the following functions: * * find_fnv_prime(bits) * deprecated_fnv0(bits, fnv_prime, string) * fnv_offset_basis(bits, fnv_prime) * fnv1a_style_hash(bits, fnv_prime, prev_hash, string) * * See the individual function for details on args and return value. * * If no args are given to find_fnv_prime() and stdin is associated * with a tty (i.e., an interactive terminal), then bits will be * prompted for and commentary will be printed to stdout as well. * * If fnv_prime == null(), then an attempt to compute the FNV prime * for a hash if size bits is attempted.. * * If prev_hash == null(), then the FNV offset basis for * for a hash if size bits is computed. * * For more information on the FNV hash see: * * https://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function * http://www.isthe.com/chongo/tech/comp/fnv/index.html * * IMPORTANT NOTE: * * These functions, if given non-standard values, will produce bogus results. * In some cases, such as specifying the number of bits in the hash, * using a non-power of 2 bit will produce a result that may work, * but the hash will be only an "FNV-style" hash and not a true FNV hash. * * We say "FNV-style" because the result hash is not a "true FNV-like" hash. * * Let integer n > 0 be the number if bits in the FNV hash. Then: * * t = floor((5+n)/12) * * The FNV prime, for the given n bits is the smallest prime of the form: * * p = 256^t + 2^8 + b * * such that: * * 0 < b < 2^8 * The number of one-bits in b must be 4 or 5 * p mod (2^40 - 2^24 - 1) > (2^24 + 2^8 + 2^7) * * If you force n to not be a power of 2, for example: * * n = 44 * * you will find that the FNV prime for 44 bits is: * * p44 = 4294967597 * = 0x10000012d * = 0b100000000000000000000000100101101 * = 2^32 + 301 = 2^32 + 2^8 + 2^5 + 2^3 + 2^2 + 2^0 * * However a hash size of 44 bits is not a true FNV hash, it is only a "FNV-style" hash. * * NOTE: We disallow n <= 31 because there are no FNV primes that small. * * NOTE: For n that is a power of 2 and n > 1024, you will find that * that FNV primes become so rare that that one may not find a suitable * FNV prime. For n = powers of 2 >= 2048 and <= 1048576, * there is NO FNV primes. * * As for as hashing goes, large values of n, even if an * FNV hash may be found, are unlikely to be truly useful. :-) */ /* * Copyright (c) 2023 by Landon Curt Noll. All Rights Reserved. * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby granted, * provided that the above copyright, this permission notice and text * this comment, and the disclaimer below appear in all of the following: * * supporting documentation * source copies * source works derived from this source * binaries derived from this source or from derived source * * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * * chongo (Landon Curt Noll, http://www.isthe.com/chongo/index.html) /\oo/\ * * Share and enjoy! :-) */ /* * find_fnv_prime - try to find a FNV prime given the number of bits * * If bits == null(), this function will attempt to prompt stdin * for a value and provide commends on the value of bits. * * given: * bits number of bits in the hash, null() ==> prompt for value * * returns: * 0 ==> no FNV prime found * >0 ==> FNV prime */ define find_fnv_prime(bits) { local b; /* lower octet of the potential FNV prime: [1,255] */ local p; /* value to test as an FNV prime */ local t; /* power of 256 part of the FNV prime */ local one_bits; /* number of 1 bits in b */ local p_minus_b; /* potential FNV prime less b */ local interactive; /* true ==> interactive mode and print commentary */ /* * case: no arg, prompt for bits and print commentary */ interactive = 0; /* assume non-interactive mode */ if (isnull(bits)) { /* * must be attached to an interactive terminal */ if (!isatty(files(0))) { print "# FATAL: stdin is not a tty: not attached to an interactive terminal"; return 0; } interactive = 1; /* set interactive mode */ /* * prompt for the number of bits */ do { local strscanf_ret; /* return from strscanf_ret */ local input; /* value read after prompt */ /* * prompt and obtain the input */ input = prompt("Enter hash size in bits: "); strscanf_ret = strscanf(input, "%i", bits); print "input =", input; print "bits =", bits; if (!isint(bits) || bits <= 0) { print; print "# NOTE: must enter a integer > 0, try again"; print; } } while (!isint(bits) || bits <= 0); } /* * firewall - bits must be non-negative integer */ if (!isint(bits) || bits < 0) { if (interactive) { print "# FATAL: bits must be non-negative integer"; } return 0; } /* * provide commentary on the choice of bits if we are interactive */ if (interactive) { if (popcnt(bits) == 1) { if (bits > 1024) { print "# WARNING: FNV primes for bit size powers of 2 > 1024 are extremely rare."; print "# WARNING: There are no FNV primes for bit size powers of 2 >= 2048 and <= 1048576."; } print "n =", bits; } else { if (bits < 32) { print "# WARNING: bits < 32 is not recommended because there isn't enough bits to be worth hashing"; } print "# WARNING: because bits is not a power of 2, we can only form an \"FNV-style hash\": not a true FNV hash."; print "# WARNING: A \"FNV-style hash\" may not have the desired hash properties of a true FNV hash."; print "n =", bits; } } /* * search setup */ t = floor((5+bits)/12); p_minus_b = 256^t + 2^8; /* * search for a b that forms a suitable FNV prime */ for (b=1; b < 256; ++b) { /* * reject b unless the of one-bits in bottom octet of p is 4 or 5 */ one_bits = popcnt(b); if (one_bits != 4 && one_bits != 5) { continue; } /* * reject p if p mod (2^40 - 2^24 - 1) <= (2^24 + 2^8 + 2^7) */ p = p_minus_b + b; if ((p % (2^40 - 2^24 - 1)) <= (2^24 + 2^8 + 2^7)) { continue; } /* * accept potential p value that is prime */ if (ptest(p) == 1) { return p; } } /* * case: did not find an FNV prime */ if (b >= 256) { /* * examine results if interactive */ if (interactive) { print "# FATAL: There is no a suitable FNV prime for bits =", bits; quit "find_fnv_prime: FATAL: FNV prime search failed"; } /* * return 0 to indicate no FNV prime found */ return 0; } /* * provide FNV commentary if interactive */ if (interactive) { print "t =", t; print "b =", b; print "# NOTE: p = 256^":t, "+ 2^8 +", b; print; print "p =", p; } /* * return FNV prime */ return p; } /* * deprecated_fnv0 - FNV-0 hash that should only be used to generate an FNV offset basis * * If fnv_prime == null(), this function will try to compute the FNV prime * for a hash of size bits. * * given: * bits number of bits in FNV hash * fnv_prime FNV prime, null() ==> generate suitable FNV prime if possible * string string to hash * * returns: * FNV-0 hash, for size bytes, of string * * NOTE: This function does NOT attempt to determine that fnv_prime is prime. */ define deprecated_fnv0(bits, fnv_prime, string) { local hash; /* FNV hash value */ local len; /* length of string */ local base; /* base of FNV hash: 2^bits */ local i; /* * firewall */ if (!isint(bits) || bits <= 0) { quit "deprecated_fnv0: FATAL: bits arg must be an integer > 0"; } if (!isstr(string)) { quit "deprecated_fnv0: FATAL: string arg must be a string"; } /* * fnv_prime == null() means to try and generate the FNV prime */ if (isnull(fnv_prime)) { /* try to generate an FNV prime */ fnv_prime = find_fnv_prime(bits); if (fnv_prime == 0) { quit "deprecated_fnv0: FATAL: no FNV prime exists for the given hash size in bits"; } } if (!isint(fnv_prime) || fnv_prime <= 0) { quit "deprecated_fnv0: FATAL: fnv_prime arg must be an integer > 0 and should be prime"; } /* * FNV-0 hash each character */ len = strlen(string); base = 2^bits; hash = 0; for (i=0; i < len; ++i) { hash = xor((hash * fnv_prime) % base, ord(string[i])); } return hash; } /* * fnv_offset_basis - generate and FNV offset basis * * given: * bits number of bits in FNV hash * fnv_prime FNV prime, null() ==> generate suitable FNV prime if possible * * returns: * FNV offset basis for a hash size of bits and an FNV prime of fnv_prime * * NOTE: This function does NOT attempt to determine that fnv_prime is prime. */ define fnv_offset_basis(bits, fnv_prime) { local fnv0_hash = 0; /* FNV-0 hash value */ /* string to generate a FNV offset basis - do not change this value */ static chongo_was_here = "chongo /\\../\\"; /* * firewall */ if (!isint(bits) || bits <= 0) { quit "fnv_offset_basis: FATAL: bits arg must be an integer > 0"; } /* * fnv_prime == null() means to try and generate the FNV prime */ if (isnull(fnv_prime)) { /* try to generate an FNV prime */ fnv_prime = find_fnv_prime(bits); if (fnv_prime == 0) { quit "fnv_offset_basis: FATAL: no FNV prime exists for the given hash size in bits"; } } if (!isint(fnv_prime) || fnv_prime <= 0) { quit "fnv_offset_basis: FATAL: fnv_prime arg must be an integer > 0 and should be prime"; } /* * return the FNV-0 hash of fnv_offset_basis as the FNV offset basis */ fnv0_hash = deprecated_fnv0(bits, fnv_prime, chongo_was_here); return fnv0_hash; } /* * fnv_style_hash - compute an "FNV-1a-style" hash * * These functions, if given non-standard values, will produce bogus results. * To produce a true FNV-1a hash: * * bits must be a power of 2 * 32 <= bits * fnv_prime == find_fnv_prime(bits) OR fnv_prime == null() * prev_hash == previous FNV hash OR prev_hash == null() * * If fnv_prime == null(), this function will try to compute the FNV prime * for a hash of size bits. * * If prev_hash == null(), this function will try to compute the FNV offset basis * for a hash of size bits. * * One may chain "FNV-style" hashes by replacing the offset_basis with * the hash state of the previous hash. For the first hash: * * fnv_prime = find_fnv_prime(bits) * hash_val = fnv_style_hash(bits, fnv_prime, null(), string_a); * * then: * * hash_val = fnv_style_hash(bits, fnv_prime, hash_val, string_b); * * This will produce the same as the string_a concatenated with string_b: * * hash_val = fnv_style_hash(bits, null(), null(), string_a + string_b); * * NOTE: Because string_a and string_b are strings, the expression: * * string_a + string_b * * is string_a concatenated with string_b. * * given: * bits number of bits in FNV hash * fnv_prime FNV prime, null() ==> generate suitable FNV prime if possible * prev_hash previous hash value, null() ==> generate FNV offset basis * string string to hash * * returns: * "FNV-style" hash of bits * * NOTE: This function does NOT attempt to determine that fnv_prime is prime. */ define fnv1a_style_hash(bits, fnv_prime, prev_hash, string) { local hash = 0; /* FNV hash value */ local len; /* length of string */ local base; /* base of FNV hash: 2^bits */ local i; /* * firewall */ if (!isint(bits) || bits <= 0) { quit "fnv1a_style_hash: FATAL: bits arg must be an integer > 0"; } if (!isstr(string)) { quit "fnv1a_style_hash: FATAL: string arg must be a string"; } /* * fnv_prime == null() means to try and generate the FNV prime */ if (isnull(fnv_prime)) { /* try to generate an FNV prime */ fnv_prime = find_fnv_prime(bits); if (fnv_prime == 0) { quit "fnv1a_style_hash: FATAL: no FNV prime exists for the given hash size in bits"; } } if (!isint(fnv_prime) || fnv_prime <= 0) { quit "fnv1a_style_hash: FATAL: fnv_prime arg must be an integer > 0 and should be prime"; } /* * prev_hash == null() means to generate the FNV offset basis */ if (isnull(prev_hash)) { /* generate the FNV offset basis for a hash of size bits */ prev_hash = fnv_offset_basis(bits, fnv_prime); } if (!isint(prev_hash) || prev_hash < 0) { quit "fnv1a_style_hash: FATAL: prev_hash arg must be an integer => 0"; } /* * FNV-1a hash each character */ len = strlen(string); base = 2^bits; hash = prev_hash; for (i=0; i < len; ++i) { hash = xor((hash * fnv_prime) % base, ord(string[i])); } return hash; }