1 module nxt.digest_ex; 2 3 /** Message Digest. 4 5 Zeros contents means uninitialized digest. 6 7 Copyright: Per Nordlöw 2022-. 8 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 9 Authors: $(WEB Per Nordlöw) 10 11 See_Also: http://stackoverflow.com/questions/1902340/can-a-sha-1-hash-be-all-zeroes 12 See_Also: http://stackoverflow.com/questions/20179287/sha1-indexed-hash-table-in-d 13 */ 14 struct Digest(size_t numBytes = 20, string name = "SHA-1") { 15 static assert(hash_t.sizeof == 4 || 16 hash_t.sizeof == 8, 17 ("Unsupported size of hash_t " ~ to!string(hash_t.sizeof))); 18 19 //enum numBytes = 20; /+ TODO: templatize +/ 20 enum numInts = numBytes / 4; 21 static assert(numBytes % 4 == 0, "numBytes must be a multiple of 4"); 22 23 import std.conv: to; 24 25 /** Data */ 26 static if (hash_t.sizeof == 4) align(4) ubyte[numBytes] _bytes; 27 else static if (hash_t.sizeof == 8) align(8) ubyte[numBytes] _bytes; 28 else 29 { 30 static assert(0, "Cannot handle hash_t of size " ~ to!string(hash_t.sizeof)); 31 } 32 33 alias _bytes this; 34 35 hash_t toHash() const @property @trusted pure nothrow 36 { 37 static assert(hash_t.sizeof <= numBytes, "hash_t.sizeof must be <= numBytes"); 38 auto x = _bytes[0..hash_t.sizeof]; // Use offset 0 to preserv alignment of _bytes 39 return *(cast(hash_t*)&x); // which is required for this execute 40 } 41 42 // Alternatively: writefln("%-(%02x%)", digest[0 .. numBytes]) 43 string toString(bool abbreviated = true) const @property @trusted pure /* nothrow */ 44 { 45 import std.digest.sha: toHexString; 46 import std.range: chunks; 47 import std.algorithm: map, joiner; 48 if (abbreviated) 49 return name ~ ":" ~ _bytes[0..4].toHexString ~ "\u2026"; 50 else 51 return name ~ ":" ~ _bytes[].chunks(4).map!toHexString.joiner(":").to!string; 52 } 53 54 /** Check if digest is undefined. */ 55 bool empty() @property @safe const pure nothrow 56 { 57 /* TODO: Is this unrolled to uint/ulong compares? */ 58 import nxt.predicates: allZero; 59 return (cast(uint[numInts])_bytes)[].allZero; // Note: D understands that this is @safe! :) 60 } 61 62 /** Check if digest is defined. */ 63 bool defined() @property @safe const pure nothrow => !empty; 64 65 /** Check if digest is initialized. */ 66 bool opCast(T : bool)() const @safe pure nothrow => defined; 67 68 version (none) { 69 Digest opBinary(string op)(Digest rhs) { 70 typeof(return) tmp = void; 71 static if (op == "^" || 72 op == "|" || 73 op == "&") 74 mixin("tmp = _bytes[] " ~ op ~ " rhs._bytes[];"); 75 else 76 static assert(0, "Unsupported binary operator " ~ op); 77 return tmp; 78 } 79 } 80 81 void toMsgpack(Packer)(ref Packer packer) const 82 { 83 immutable bool definedFlag = defined; 84 packer.pack(definedFlag); 85 if (definedFlag) { 86 packer.pack(_bytes); // no header 87 } 88 } 89 90 void fromMsgpack(Unpacker)(auto ref Unpacker unpacker) { 91 bool definedFlag = void; 92 unpacker.unpack(definedFlag); 93 if (definedFlag) { 94 unpacker.unpack(_bytes); // no header 95 } 96 else 97 { 98 _bytes[] = 0; // zero it! 99 } 100 } 101 } 102 103 alias MD5Digest = Digest!(16, "MD5"); 104 alias SHA1Digest = Digest!(20, "SHA-1"); 105 alias SHA224Digest = Digest!(28, "SHA-224"); 106 alias SHA256Digest = Digest!(32, "SHA-256"); 107 alias SHA384Digest = Digest!(48, "SHA-384"); 108 alias SHA512Digest = Digest!(64, "SHA-512"); 109 110 pure nothrow @safe @nogc unittest { 111 SHA1Digest a, b; 112 assert(a.empty); 113 assert(b.empty); 114 assert(a == b); 115 /* a[0] = 1; */ 116 /* b[0] = 1; */ 117 /* b[1] = 1; */ 118 /* const c = a^b; */ 119 /* assert(c[0] == 0); */ 120 /* assert(c[1] == 1); */ 121 /* assert(!c.empty); */ 122 }