1 /** Hash digestion of standard types. 2 * 3 * TODO: use: 4 * 5 * static if (__traits(hasMember,hasher, "putStaticArray")) 6 * digest.putStaticArray((cast(ubyte*)&value)[0 .. value.sizeof]); 7 * else 8 * digest.put((cast(ubyte*)&value)[0 .. value.sizeof]); 9 */ 10 module nxt.digestion; 11 12 import std.traits : isScalarType, hasIndirections, isArray, isPointer; 13 import std.digest : isDigest; 14 import nxt.container.traits : isAddress; 15 16 @safe: 17 18 /// ditto 19 hash_t hashOf2(alias hasher, T)(const scope auto ref T value) { 20 version (LDC) pragma(inline, true); 21 static if (__traits(compiles, { enum _ = isDigest!hasher; }) && 22 isDigest!hasher) { 23 import std.digest : makeDigest; 24 25 auto digest = makeDigest!(hasher); 26 static if (__traits(hasMember, T, "toDigest")) 27 value.toDigest(digest); 28 else 29 digestAny(digest, value); 30 31 static if (__traits(hasMember, hasher, "get")) { 32 digest.finish(); 33 auto result = digest.get(); 34 } 35 else 36 auto result = digest.finish(); 37 38 static if (is(typeof(result) == typeof(return))) 39 return result; 40 else static if (is(typeof(result) == ubyte[4])) { 41 static hash_t fix(in ubyte[4] x) @trusted => *cast(uint*)(&x); 42 return fix(result); 43 } 44 else static if (is(typeof(result) == ubyte[8])) { 45 static hash_t fix(in ubyte[8] x) @trusted => *cast(typeof(return)*)(&x); 46 return fix(result); 47 } 48 else static if (is(typeof(result) == typeof(return)[2])) 49 return (result[0] ^ result[1]); 50 else 51 static assert(0, "Handle get() with return type " ~ typeof(result).stringof ~ 52 " on " ~ size_t.sizeof.stringof ~ "-bit platform"); 53 } 54 else static if (__traits(compiles, { auto _ = hasher((ubyte[]).init); })) { 55 // cast input `value` to `ubyte[]` and use std.digest API 56 immutable digest = hasher((cast(ubyte*)&value)[0 .. value.sizeof]); /+ TODO: ask forums when this isn't correct +/ 57 58 static assert(digest.sizeof >= 59 typeof(return).sizeof, 60 `Size of digest is ` ~ digest.sizeof 61 ~ ` but needs to be at least ` ~ typeof(return).sizeof.stringof); 62 63 import std.traits : isUnsigned; 64 65 static if (isUnsigned!(typeof(digest))) 66 return cast(typeof(return))digest; // fast modulo calculation 67 else static if (__traits(isStaticArray, typeof(digest))) { 68 typeof(return) hashIndex; 69 static if (2*hash_t.sizeof == digest.sizeof) 70 // for instance, use all 128-bits when hash_t is 64-bit 71 (cast(ubyte*)&hashIndex)[0 .. hashIndex.sizeof] = (digest[0 .. hashIndex.sizeof] ^ 72 digest[hashIndex.sizeof .. 2*hashIndex.sizeof]); 73 else 74 (cast(ubyte*)&hashIndex)[0 .. hashIndex.sizeof] = digest[0 .. hashIndex.sizeof]; 75 return hashIndex; 76 } 77 else 78 static assert(0, "Unsupported digest type " ~ typeof(digest).stringof); 79 } 80 else 81 static assert(0, "Cannot combine hasher " ~ hasher.stringof ~ 82 " with element type " ~ T.stringof); 83 } 84 85 /** Digest `value` into `digest`. 86 */ 87 void digestAny(Digest, T)(ref Digest digest, 88 const scope auto ref T value) 89 if (isDigest!Digest) { 90 version (LDC) pragma(inline, true); 91 static if (isScalarType!T) // first because faster to evaluate than 92 // `!hasIndirections!T` below 93 digestRaw(digest, value); 94 else static if (__traits(hasMember, T, "toDigest")) 95 value.toDigest(digest); 96 else static if (isAddress!T) 97 digestAddress(digest, value); 98 else static if (!hasIndirections!T) // no pointers left in `T`. TODO: make this the default in-place of `isScalarType` 99 { 100 version (LDC) // LDC doesn't zero pad `real`s 101 { 102 /+ TODO: needed to improve this with a trait +/ 103 import std.traits : isInstanceOf; 104 static if (__traits(isStaticArray, T)) { 105 import std.meta : staticIndexOf; 106 enum isReal = is(typeof(T.init[0]) == real); 107 static if (isReal) // static array of `real`s 108 foreach (e; value) 109 digestRaw(digest, e); // hash each element for now because padding might now be zero 110 else 111 digestRaw(digest, value); // hash everything in one call for better speed 112 } 113 else 114 { 115 import std.typecons : Nullable; 116 static if (is(T == Nullable!(_), _)) 117 digestRaw(digest, value); // hash everything in one call for better speed 118 else 119 { 120 import std.meta : staticIndexOf; 121 enum realIndex = staticIndexOf!(real, typeof(T.init.tupleof)); 122 static if (realIndex != -1) 123 digestStruct(digest, value); // hash each element for now because padding might now be zero 124 else 125 digestRaw(digest, value); // hash everything in one call for better speed 126 } 127 } 128 } 129 else 130 digestRaw(digest, value); // hash everything in one call for better speed 131 } 132 else static if (isArray!T) // including `T` being `string`, `wstring`, `dstring` 133 digestArray(digest, value); 134 else static if (is(T == struct)) { 135 static if (is(typeof(T.init[])) && isArray!(typeof(T.init[]))) /+ TODO: trait: `isArrayLike` +/ 136 digestAnyWithTrustedSystemSlicing(digest, value); 137 else 138 digestStruct(digest, value); 139 } 140 else 141 static assert(0, "handle type " ~ T.stringof); 142 } 143 144 /+ TODO: remove when `SSOString` gets scope-checked opSlice +/ 145 private 146 void digestAnyWithTrustedSystemSlicing(Digest, T)(ref Digest digest, 147 const scope ref T value) @trusted 148 { 149 digestArray(digest, value[]); 150 } 151 152 /** Digest the `value` as an address (pointer). */ 153 private void digestAddress(Digest, T)(scope ref Digest digest, 154 const scope T value) @trusted // pointer passed by value 155 if (isDigest!Digest && 156 isAddress!T) { 157 static if (is(T == class)) { 158 static if (size_t.sizeof == 8) 159 enum bitshift = 3; // 64-bit platform 160 else 161 enum bitshift = 2; // 32-bit platform 162 } 163 else static if (isPointer!T) { 164 enum Tvalue = typeof(*T.init); 165 enum Talignment = T.value.alignof; 166 static if (Talignment == 16) 167 enum bitshift = 4; 168 else static if (Talignment == 8) 169 enum bitshift = 3; 170 else static if (Talignment == 4) 171 enum bitshift = 2; 172 else static if (Talignment == 2) 173 enum bitshift = 1; 174 else static if (Talignment == 1) 175 enum bitshift = 0; 176 else 177 static assert(0, "Cannot calculate alignment of T being " ~ T.stringof); 178 } 179 const valueAsSize = *cast(size_t*)(&value); // `value` as pointer 180 assert((valueAsSize & (bitshift - 1)) == 0); // shifted out bits should all be zero 181 digestRaw(digest, valueAsSize >> bitshift); 182 } 183 184 /** Digest the struct `value` by digesting each member sequentially. */ 185 private void digestStruct(Digest, T)(scope ref Digest digest, 186 const scope auto ref T value) @trusted 187 if (isDigest!Digest && 188 is(T == struct)) { 189 static if (!hasIndirections!T) { 190 version (D_Coverage) {} else pragma(inline, true); 191 digestRaw(digest, value); // hash everything in one call for better speed 192 } 193 else 194 { 195 version (LDC) pragma(inline, true); 196 foreach (const ref subValue; value.tupleof) // for each member 197 digestAny(digest, subValue); 198 } 199 } 200 201 /** Digest the array `value`. */ 202 void digestArray(Digest, T)(scope ref Digest digest, 203 const scope auto ref T value) @trusted 204 if (isDigest!Digest && 205 isArray!T) { 206 import std.traits : isDynamicArray; 207 static if (isDynamicArray!T) 208 // only dynamic arrays vary in length for a specific type `T` being hashed 209 digestRaw(digest, value.length); // length 210 211 alias E = typeof(T.init[0]); 212 static if (isScalarType!E || 213 isPointer!E || 214 (is(T == class) && 215 !__traits(hasMember, T, "toDigest"))) { 216 version (D_Coverage) {} else pragma(inline, true); 217 digest.put((cast(ubyte*)value.ptr)[0 .. value.length * value[0].sizeof]); // faster 218 } 219 else 220 foreach (const ref element; value) 221 digestAny(digest, element); // slower 222 } 223 224 /** Digest raw bytes of `values`. 225 */ 226 private void digestRaw(Digest, T)(scope ref Digest digest, 227 const scope T value) @trusted // pass scalar types by value 228 if (isDigest!Digest && 229 isScalarType!T) // scalar type `T` 230 { 231 version (LDC) pragma(inline, true); 232 /+ TODO: optimize when value is size_t, ulong and digest supports it +/ 233 digest.put((cast(ubyte*)&value)[0 .. value.sizeof]); 234 } 235 236 private void digestRaw(Digest, T)(scope ref Digest digest, 237 const scope auto ref T value) @trusted // pass non-scalar types by ref when possible 238 if (isDigest!Digest && 239 !isScalarType!T) // non-scalar type `T` 240 { 241 version (LDC) pragma(inline, true); 242 /+ TODO: optimize when value is size_t, ulong and digest supports it +/ 243 digest.put((cast(ubyte*)&value)[0 .. value.sizeof]); 244 } 245 246 /// arrays and containers and its slices 247 pure @safe unittest { 248 import nxt.container.dynamic_array : DynamicArray; 249 import nxt.construction : makeWithElements; 250 251 alias E = double; 252 alias A = DynamicArray!E; 253 254 immutable e = [cast(E)1, cast(E)2, cast(E)3].s; 255 auto a = makeWithElements!(A)(e[]); 256 257 // static array and its slice (dynamic array) hash differently 258 const sh = hashOf2!(FNV64)(e); /* does not need to include length in hash 259 * because all instances of typeof(e) have 260 * the same length */ 261 const dh = hashOf2!(FNV64)(e[]); // includes hash in length 262 assert(sh != dh); 263 264 // array container and its slice should hash equal 265 assert(hashOf2!(FNV64)(a) == 266 hashOf2!(FNV64)(e[])); 267 } 268 269 @trusted pure unittest { 270 const ubyte[8] bytes8 = [1, 2, 3, 4, 5, 6, 7, 8]; 271 assert(hashOf2!(FNV64)(bytes8) == 9130222009665091821UL); 272 273 enum E { first, second, third } 274 275 assert(hashOf2!(FNV64)(E.first) == 276 hashOf2!(FNV64)(E.first)); 277 assert(hashOf2!(FNV64)(E.second) == 278 hashOf2!(FNV64)(E.second)); 279 assert(hashOf2!(FNV64)(E.third) == 280 hashOf2!(FNV64)(E.third)); 281 assert(hashOf2!(FNV64)(E.first) != 282 hashOf2!(FNV64)(E.second)); 283 assert(hashOf2!(FNV64)(E.second) != 284 hashOf2!(FNV64)(E.third)); 285 286 struct V 287 { 288 float f = 3; 289 double d = 5; 290 real r = 7; 291 E e; 292 } 293 294 struct S 295 { 296 string str = `abc`; 297 wstring wstr = `XYZ`; 298 dstring dstr = `123`; 299 size_t sz = 17; 300 ushort us = 18; 301 ubyte ub = 255; 302 V v; 303 } 304 305 // check determinism 306 assert(hashOf2!(FNV64)("alpha") == 307 hashOf2!(FNV64)("alpha".dup)); 308 309 assert(hashOf2!(FNV64)(S()) == 310 hashOf2!(FNV64)(S())); 311 312 struct HasDigest 313 { 314 E e; 315 void toDigest(Digest)(scope ref Digest digest) const 316 pure nothrow @nogc 317 if (isDigest!Digest) { 318 digestRaw(digest, e); 319 } 320 } 321 322 assert(hashOf2!(FNV64)(HasDigest(E.first)) == 323 hashOf2!(FNV64)(HasDigest(E.first))); 324 325 assert(hashOf2!(FNV64)(HasDigest(E.first)) != 326 hashOf2!(FNV64)(HasDigest(E.second))); 327 } 328 329 version (unittest) { 330 import nxt.digest.fnv : FNV; 331 alias FNV64 = FNV!(64, true); 332 import nxt.debugio; 333 import nxt.array_help : s; 334 }