defmodule Murmur do @moduledoc ~S""" This module implements the x86_32, x86_128 and x64_128 variants of the non-cryptographic hash Murmur3. ## Examples iex> Murmur.hash_x86_32("b2622f5e1310a0aa14b7f957fe4246fa", 2147368987) 3297211900 iex> Murmur.hash_x86_128("some random data") 5586633072055552000169173700229798482 # hashes of erlang terms may change between erlang versions # iex> Murmur.hash_x64_128([:yes, :you, :can, :use, :any, :erlang, :term!]) # => 300414073828138369336317731503972665325 """ import Bitwise # murmur constants @c1_32 0xCC9E2D51 @c2_32 0x1B873593 @n_32 0xE6546B64 @c1_32_128 0x239B961B @c2_32_128 0xAB0E9789 @c3_32_128 0x38B34AE5 @c4_32_128 0xA1E38B93 @n1_32_128 0x561CCD1B @n2_32_128 0x0BCAA747 @n3_32_128 0x96CD1C35 @n4_32_128 0x32AC3B17 @c1_64_128 0x87C37B91114253D5 @c2_64_128 0x4CF5AD432745937F @n1_64_128 0x52DCE729 @n2_64_128 0x38495AB5 # since erlang/elixir integers are variable-length we have to guarantee them # to be 32 or 64 bit long defmacrop mask_32(x), do: quote(do: unquote(x) &&& 0xFFFFFFFF) defmacrop mask_64(x), do: quote(do: unquote(x) &&& 0xFFFFFFFFFFFFFFFF) @doc """ Returns the hashed erlang term `data` using an optional `seed` which defaults to `0`. This function uses the x64 128bit variant. """ @spec hash_x64_128(binary | term, non_neg_integer) :: non_neg_integer def hash_x64_128(data, seed \\ 0) def hash_x64_128(data, seed) when is_binary(data) do hashes = [seed, seed] |> hash_64_128_aux(data) |> Stream.zip([ {31, @c1_64_128, @c2_64_128}, {33, @c2_64_128, @c1_64_128} ]) |> Stream.map(fn {x, {r, a, b}} -> case x do {h, []} -> bxor(h, byte_size(data)) {h, t} -> h |> bxor( t |> swap_uint() |> Kernel.*(a) |> mask_64 |> rotl64(r) |> Kernel.*(b) |> mask_64 |> bxor(byte_size(data)) ) end end) |> Enum.to_list() [h1, h2] = hashes |> hash_64_128_intermix |> Enum.map(&fmix64/1) |> hash_64_128_intermix h1 <<< 64 ||| h2 end def hash_x64_128(data, seed) do hash_x64_128(:erlang.term_to_binary(data), seed) end @doc """ Returns the hashed erlang term `data` using an optional `seed` which defaults to `0`. This function uses the x86 128bit variant. """ @spec hash_x86_128(binary | term, non_neg_integer) :: non_neg_integer def hash_x86_128(data, seed \\ 0) def hash_x86_128(data, seed) when is_binary(data) do hashes = [seed, seed, seed, seed] |> hash_32_128_aux(data) |> Stream.zip([ {15, @c1_32_128, @c2_32_128}, {16, @c2_32_128, @c3_32_128}, {17, @c3_32_128, @c4_32_128}, {18, @c4_32_128, @c1_32_128} ]) |> Stream.map(fn {x, {r, a, b}} -> case x do {h, []} -> bxor(h, byte_size(data)) {h, t} -> h |> bxor( t |> swap_uint() |> Kernel.*(a) |> mask_32 |> rotl32(r) |> Kernel.*(b) |> mask_32 |> bxor(byte_size(data)) ) end end) |> Enum.to_list() [h1, h2, h3, h4] = hashes |> hash_32_128_intermix |> Enum.map(&fmix32/1) |> hash_32_128_intermix h1 <<< 96 ||| h2 <<< 64 ||| h3 <<< 32 ||| h4 end def hash_x86_128(data, seed) do hash_x86_128(:erlang.term_to_binary(data), seed) end @doc """ Returns the hashed erlang term `data` using an optional `seed` which defaults to `0`. This function uses the x86 32bit variant. """ @spec hash_x86_32(binary | term, non_neg_integer) :: non_neg_integer def hash_x86_32(data, seed \\ 0) def hash_x86_32(data, seed) when is_binary(data) do hash = case hash_32_aux(seed, data) do {h, []} -> h {h, t} -> h |> bxor( t |> swap_uint() |> Kernel.*(@c1_32) |> mask_32 |> rotl32(15) |> Kernel.*(@c2_32) |> mask_32 ) end hash |> bxor(byte_size(data)) |> fmix32() end def hash_x86_32(data, seed) do hash_x86_32(:erlang.term_to_binary(data), seed) end # x64_128 helper functions @spec hash_64_128_intermix([non_neg_integer]) :: [non_neg_integer] defp hash_64_128_intermix([h1, h2]) do h1 = mask_64(h1 + h2) h2 = mask_64(h2 + h1) [h1, h2] end @spec k_64_op( non_neg_integer, 5_545_529_020_109_919_103 | 9_782_798_678_568_883_157, 31 | 33, 5_545_529_020_109_919_103 | 9_782_798_678_568_883_157 ) :: non_neg_integer defp k_64_op(k, c1, rotl, c2) do k |> Kernel.*(c1) |> mask_64 |> rotl64(rotl) |> mask_64 |> Kernel.*(c2) |> mask_64 end @spec h_64_op( non_neg_integer, non_neg_integer, non_neg_integer, non_neg_integer, non_neg_integer, non_neg_integer ) :: non_neg_integer defp h_64_op(h1, k, rotl, h2, const, n) do h1 |> bxor(k) |> rotl64(rotl) |> Kernel.+(h2) |> Kernel.*(const) |> Kernel.+(n) |> mask_64 end @spec hash_64_128_aux([non_neg_integer], binary) :: [{non_neg_integer, [binary]}] defp hash_64_128_aux( [h1, h2], <> ) do k1 = k_64_op(k1, @c1_64_128, 31, @c2_64_128) h1 = h_64_op(h1, k1, 27, h2, 5, @n1_64_128) k2 = k_64_op(k2, @c2_64_128, 33, @c1_64_128) h2 = h_64_op(h2, k2, 31, h1, 5, @n2_64_128) hash_64_128_aux([h1, h2], t) end defp hash_64_128_aux([h1, h2], <>) do [{h1, t1}, {h2, t}] end defp hash_64_128_aux([h1, h2], t) when is_binary(t) do [{h1, t}, {h2, []}] end defp hash_64_128_aux([h1, h2], _) do [{h1, []}, {h2, []}] end # x86_128 helper functions @spec hash_32_128_intermix([non_neg_integer]) :: [non_neg_integer] defp hash_32_128_intermix([h1, h2, h3, h4]) do h1 = h1 |> Kernel.+(h2) |> mask_32 |> Kernel.+(h3) |> mask_32 |> Kernel.+(h4) |> mask_32 h2 = mask_32(h2 + h1) h3 = mask_32(h3 + h1) h4 = mask_32(h4 + h1) [h1, h2, h3, h4] end @spec k_32_op( non_neg_integer(), 597_399_067 | 951_274_213 | 2_716_044_179 | 2_869_860_233 | 3_432_918_353, 15 | 16 | 17 | 18, 461_845_907 | 597_399_067 | 951_274_213 | 2_716_044_179 | 2_869_860_233 ) :: non_neg_integer defp k_32_op(k, c1, rotl, c2) do k |> Kernel.*(c1) |> mask_32 |> rotl32(rotl) |> mask_32 |> Kernel.*(c2) |> mask_32 end @spec h_32_op( non_neg_integer, non_neg_integer, non_neg_integer, non_neg_integer, non_neg_integer, non_neg_integer ) :: non_neg_integer defp h_32_op(h1, k, rotl, h2, const, n) do h1 |> bxor(k) |> rotl32(rotl) |> Kernel.+(h2) |> Kernel.*(const) |> Kernel.+(n) |> mask_32 end @spec hash_32_128_aux([non_neg_integer], binary) :: [{non_neg_integer, [binary]}] defp hash_32_128_aux( [h1, h2, h3, h4], <> ) do k1 = k_32_op(k1, @c1_32_128, 15, @c2_32_128) h1 = h_32_op(h1, k1, 19, h2, 5, @n1_32_128) k2 = k_32_op(k2, @c2_32_128, 16, @c3_32_128) h2 = h_32_op(h2, k2, 17, h3, 5, @n2_32_128) k3 = k_32_op(k3, @c3_32_128, 17, @c4_32_128) h3 = h_32_op(h3, k3, 15, h4, 5, @n3_32_128) k4 = k_32_op(k4, @c4_32_128, 18, @c1_32_128) h4 = h_32_op(h4, k4, 13, h1, 5, @n4_32_128) hash_32_128_aux([h1, h2, h3, h4], t) end defp hash_32_128_aux( [h1, h2, h3, h4], <> ) do [{h1, t1}, {h2, t2}, {h3, t3}, {h4, t}] end defp hash_32_128_aux([h1, h2, h3, h4], <>) do [{h1, t1}, {h2, t2}, {h3, t3}, {h4, []}] end defp hash_32_128_aux([h1, h2, h3, h4], <>) do [{h1, t1}, {h2, t2}, {h3, []}, {h4, []}] end defp hash_32_128_aux([h1, h2, h3, h4], t1) when is_binary(t1) do [{h1, t1}, {h2, []}, {h3, []}, {h4, []}] end defp hash_32_128_aux([h1, h2, h3, h4], _) do [{h1, []}, {h2, []}, {h3, []}, {h4, []}] end # x86_32 helper functions @spec hash_32_aux(non_neg_integer, binary) :: {non_neg_integer, [binary] | binary} defp hash_32_aux(h0, <>) do k1 = k_32_op(k, @c1_32, 15, @c2_32) h0 |> bxor(k1) |> rotl32(13) |> Kernel.*(5) |> Kernel.+(@n_32) |> mask_32 |> hash_32_aux(t) end defp hash_32_aux(h, t) when byte_size(t) > 0, do: {h, t} defp hash_32_aux(h, _), do: {h, []} # 32 bit helper functions @spec fmix32(non_neg_integer) :: non_neg_integer defp fmix32(h0) do h0 |> xorbsr(16) |> Kernel.*(0x85EBCA6B) |> mask_32 |> xorbsr(13) |> Kernel.*(0xC2B2AE35) |> mask_32 |> xorbsr(16) end @spec rotl32(non_neg_integer, non_neg_integer) :: non_neg_integer defp rotl32(x, r), do: mask_32(x <<< r ||| x >>> (32 - r)) # 64bit helper functions @spec fmix64(non_neg_integer) :: non_neg_integer defp fmix64(h0) do h0 |> xorbsr(33) |> Kernel.*(0xFF51AFD7ED558CCD) |> mask_64 |> xorbsr(33) |> Kernel.*(0xC4CEB9FE1A85EC53) |> mask_64 |> xorbsr(33) end @spec rotl64(non_neg_integer, non_neg_integer) :: non_neg_integer defp rotl64(x, r), do: mask_64(x <<< r ||| x >>> (64 - r)) # generic helper functions @spec swap_uint(binary) :: non_neg_integer defp swap_uint( <> ) do v8 <<< 56 |> bxor(v7 <<< 48) |> bxor(v6 <<< 40) |> bxor(v5 <<< 32) |> bxor(v4 <<< 24) |> bxor(v3 <<< 16) |> bxor(v2 <<< 8) |> bxor(v1) end defp swap_uint( <> ) do v7 <<< 48 |> bxor(v6 <<< 40) |> bxor(v5 <<< 32) |> bxor(v4 <<< 24) |> bxor(v3 <<< 16) |> bxor(v2 <<< 8) |> bxor(v1) end defp swap_uint(<>) do v6 <<< 40 |> bxor(v5 <<< 32) |> bxor(v4 <<< 24) |> bxor(v3 <<< 16) |> bxor(v2 <<< 8) |> bxor(v1) end defp swap_uint(<>) do v5 <<< 32 |> bxor(v4 <<< 24) |> bxor(v3 <<< 16) |> bxor(v2 <<< 8) |> bxor(v1) end defp swap_uint(<>) do v4 <<< 24 |> bxor(v3 <<< 16) |> bxor(v2 <<< 8) |> bxor(v1) end defp swap_uint(<>) do v3 <<< 16 |> bxor(v2 <<< 8) |> bxor(v1) end defp swap_uint(<>) do v2 <<< 8 |> bxor(v1) end defp swap_uint(<>), do: 0 |> bxor(v1) defp swap_uint(""), do: 0 @spec xorbsr(non_neg_integer, non_neg_integer) :: non_neg_integer defp xorbsr(h, v), do: h |> bxor(h >>> v) end