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