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 }