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