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 }