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 }