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