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