1 /** FNV(Fowler-Noll-Vo) hash implementation.
2  *
3  * This module conforms to the APIs defined in std.digest.
4  */
5 module nxt.digestx.fnv;
6 
7 public import std.digest;
8 
9 /**
10  * Template API FNV-1(a) hash implementation.
11  */
12 struct FNV(ulong bitLength, bool fnv1a = false)
13 {
14     static if (bitLength == 32)
15     {
16         alias Element = uint;
17     }
18     else static if (bitLength == 64)
19     {
20         alias Element = ulong;
21     }
22     else
23     {
24         static assert(0, "Unsupported hash length " ~ bitLength.stringof);
25     }
26 
27     pragma(inline, true):
28 
29     /// Initializes the digest calculation.
30     void start() @safe pure nothrow @nogc
31     {
32         _hash = fnvOffsetBasis;
33     }
34 
35     /// Feeds the digest with data.
36     void put(scope const(ubyte)[] data...) pure nothrow @nogc
37     {
38         foreach (immutable ubyte e; data)
39         {
40             static if (fnv1a)
41             {
42                 _hash ^= e;
43                 _hash *= fnvPrime;
44             }
45             else
46             {
47                 _hash *= fnvPrime;
48                 _hash ^= e;
49             }
50         }
51     }
52 
53     /// Feeds the digest with `data` being a static array.
54     void putStaticArray(size_t n)(scope auto ref const(ubyte)[n] data)
55         pure nothrow @nogc
56     {
57         static foreach (i; 0 .. n) // unroll
58         {
59             static if (fnv1a)
60             {
61                 _hash ^= data[i];
62                 _hash *= fnvPrime;
63             }
64             else
65             {
66                 _hash *= fnvPrime;
67                 _hash ^= data[i];
68             }
69         }
70     }
71 
72     /// Returns the finished FNV digest. This also calls start to reset the internal state.
73     ubyte[bitLength / 8] finish() @trusted pure nothrow @nogc
74     {
75         import std.bitmanip : nativeToBigEndian;
76         _result = _hash;
77         start();
78         return nativeToBigEndian(_result);
79     }
80 
81     Element get() const
82     {
83         return _result;
84     }
85 
86 private:
87 
88     // FNV-1 hash parameters
89     static if (bitLength == 32)
90     {
91         enum Element fnvPrime = 0x1000193U;
92         enum Element fnvOffsetBasis = 0x811C9DC5U;
93     }
94     else static if (bitLength == 64)
95     {
96         enum Element fnvPrime = 0x100000001B3UL;
97         enum Element fnvOffsetBasis = 0xCBF29CE484222325UL;
98     }
99     else
100     {
101         static assert(0, "Unsupported hash length " ~ bitLength.stringof);
102     }
103 
104     Element _hash;
105     Element _result;
106 }
107 
108 alias FNV32 = FNV!32; /// 32bit FNV-1, hash size is ubyte[4]
109 alias FNV64 = FNV!64; /// 64bit FNV-1, hash size is ubyte[8]
110 alias FNV32A = FNV!(32, true); /// 32bit FNV-1a, hash size is ubyte[4]
111 alias FNV64A = FNV!(64, true); /// 64bit FNV-1a, hash size is ubyte[8]
112 
113 ///
114 unittest
115 {
116     import std.conv : hexString;
117     // alias FNV32Digest = WrapperDigest!FNV32; /// OOP API for 32bit FNV-1
118     // alias FNV64Digest = WrapperDigest!FNV64; /// OOP API for 64bit FNV-1
119     alias FNV32ADigest = WrapperDigest!FNV32A; /// OOP API for 32bit FNV-1a
120     // alias FNV64ADigest = WrapperDigest!FNV64A; /// OOP API for 64bit FNV-1a
121 
122     const immutable(char)[5] hello = "hello";
123 
124     FNV64 fnv64;
125     fnv64.start();
126     fnv64.put(cast(ubyte[])hello[]);
127     assert(toHexString(fnv64.finish()) == "7B495389BDBDD4C7");
128 
129     fnv64.putStaticArray(cast(ubyte[5])hello);
130     assert(toHexString(fnv64.finish()) == "7B495389BDBDD4C7");
131 
132     // Template API
133     assert(digest!FNV32("abc") == hexString!"439C2F4B");
134     assert(digest!FNV64("abc") == hexString!"D8DCCA186BAFADCB");
135     assert(digest!FNV32A("abc") == hexString!"1A47E90B");
136     assert(digest!FNV64A("abc") == hexString!"E71FA2190541574B");
137 
138     // OOP API
139     Digest fnv = new FNV32ADigest;
140     ubyte[] d = fnv.digest("1234");
141     assert(d == hexString!"FDC422FD");
142 }
143 
144 /// Convenience aliases for std.digest.digest using the FNV implementation.
145 auto fnv32Of(T...)(in T data)
146 {
147     return digest!(FNV32, T)(data);
148 }
149 /// ditto
150 auto fnv64Of(T...)(in T data)
151 {
152     return digest!(FNV64, T)(data);
153 }
154 /// ditto
155 auto fnv32aOf(T...)(in T data)
156 {
157     return digest!(FNV32A, T)(data);
158 }
159 /// ditto
160 auto fnv64aOf(T...)(in T data)
161 {
162     return digest!(FNV64A, T)(data);
163 }
164 
165 ///
166 @safe pure nothrow @nogc unittest
167 {
168     import std.conv : hexString;
169     assert(fnv32Of("") == hexString!"811C9DC5");
170     assert(fnv64Of("") == hexString!"CBF29CE484222325");
171     assert(fnv32aOf("") == hexString!"811C9DC5");
172     assert(fnv64aOf("") == hexString!"CBF29CE484222325");
173 }