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 { return !empty; } 65 66 /** Check if digest is initialized. */ 67 bool opCast(T : bool)() const @safe pure nothrow { return 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 { 78 mixin("tmp = _bytes[] " ~ op ~ " rhs._bytes[];"); 79 } 80 else 81 { 82 static assert(0, "Unsupported binary operator " ~ op); 83 } 84 return tmp; 85 } 86 } 87 88 void toMsgpack(Packer)(ref Packer packer) const 89 { 90 immutable bool definedFlag = defined; 91 packer.pack(definedFlag); 92 if (definedFlag) 93 { 94 packer.pack(_bytes); // no header 95 } 96 } 97 98 void fromMsgpack(Unpacker)(auto ref Unpacker unpacker) 99 { 100 bool definedFlag = void; 101 unpacker.unpack(definedFlag); 102 if (definedFlag) 103 { 104 unpacker.unpack(_bytes); // no header 105 } 106 else 107 { 108 _bytes[] = 0; // zero it! 109 } 110 } 111 } 112 113 alias MD5Digest = Digest!(16, "MD5"); 114 alias SHA1Digest = Digest!(20, "SHA-1"); 115 alias SHA224Digest = Digest!(28, "SHA-224"); 116 alias SHA256Digest = Digest!(32, "SHA-256"); 117 alias SHA384Digest = Digest!(48, "SHA-384"); 118 alias SHA512Digest = Digest!(64, "SHA-512"); 119 120 @safe pure nothrow @nogc unittest 121 { 122 SHA1Digest a, b; 123 assert(a.empty); 124 assert(b.empty); 125 assert(a == b); 126 /* a[0] = 1; */ 127 /* b[0] = 1; */ 128 /* b[1] = 1; */ 129 /* const c = a^b; */ 130 /* assert(c[0] == 0); */ 131 /* assert(c[1] == 1); */ 132 /* assert(!c.empty); */ 133 }