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 }