@@ -18,17 +18,32 @@ module geod24.bitblob;
1818static import std.ascii ;
1919import std.algorithm ;
2020import std.range ;
21- import std.format ;
2221import std.utf ;
2322
2423// /
2524@nogc @safe pure nothrow unittest
2625{
27- import std.digest.sha ;
26+ // / Alias for a 256 bits / 32 byte hash type
2827 alias Hash = BitBlob! 256 ;
29- Hash k1 = sha256Of(" Hello World" );
28+
29+ import std.digest.sha ;
30+ // Can be initialized from an `ubyte[32]`
31+ // (or `ubyte[]` of length 32)
32+ Hash fromSha = sha256Of(" Hello World" );
33+
34+ // Of from a string
35+ Hash genesis = GenesisBlockHashStr;
36+
37+ assert (! genesis.isNull());
38+ assert (Hash.init.isNull());
39+
40+ ubyte [5 ] empty;
41+ assert (Hash.init < genesis);
42+ // The underlying 32 bytes can be access through `opIndex` and `opSlice`
43+ assert (genesis[$ - 5 .. $] == empty);
3044}
3145
46+
3247/* ******************************************************************************
3348
3449 A value type representing a hash
@@ -37,28 +52,56 @@ import std.utf;
3752 Bits = The size of the hash, in bits. Must be a multiple of 8.
3853
3954*******************************************************************************/
55+
4056public struct BitBlob (size_t Bits)
4157{
42- // / Used by std.format
43- // / Cannot be `nothrow @nogc` since sformat is not, but does not allocate
44- public void toString (scope void delegate (const (char )[]) @safe sink) const @safe
58+ @safe :
59+
60+ static assert (
61+ Bits % 8 == 0 ,
62+ " Argument to BitBlob must be a multiple of 8" );
63+
64+ // / Convenience enum
65+ public enum StringBufferSize = (Width * 2 + 2 );
66+
67+ /* **************************************************************************
68+
69+ Format the hash as a lowercase hex string
70+
71+ Used by `std.format`.
72+ Does not allocate/throw if the sink does not allocate/throw.
73+
74+ ***************************************************************************/
75+
76+ public void toString (scope void delegate (const (char )[]) @safe sink) const
4577 {
78+ // / Used for formatting
79+ static immutable LHexDigits = ` 0123456789abcdef` ;
80+
4681 sink(" 0x" );
4782 char [2 ] data;
4883 // retro because the data is stored in little endian
4984 this .data[].retro.each! (
5085 (bin)
5186 {
52- sformat(data, " %0.2x" , bin);
87+ data[0 ] = LHexDigits[bin >> 4 ];
88+ data[1 ] = LHexDigits[(bin & 0b0000_1111)];
5389 sink(data);
5490 });
5591 }
5692
57- // / Used for serialization
58- public string toString () const @safe
93+ /* **************************************************************************
94+
95+ Get the string representation of this hash
96+
97+ Only performs one allocation.
98+
99+ ***************************************************************************/
100+
101+ public string toString () const
59102 {
60103 size_t idx;
61- char [Width * 2 + 2 ] buffer = void ;
104+ char [StringBufferSize ] buffer = void ;
62105 scope sink = (const (char )[] v) {
63106 buffer[idx .. idx + v.length] = v;
64107 idx += v.length;
@@ -67,7 +110,7 @@ public struct BitBlob (size_t Bits)
67110 return buffer.idup;
68111 }
69112
70- pure nothrow @nogc @safe :
113+ pure nothrow @nogc :
71114
72115 /* **************************************************************************
73116
@@ -117,16 +160,22 @@ public struct BitBlob (size_t Bits)
117160 this .data[idx] = cast (ubyte )((chunk[0 ] << 4 ) + chunk[1 ]);
118161 }
119162
120- // / Used for deserialization
121- static auto fromString (const (char )[] str)
163+ /* **************************************************************************
164+
165+ Support deserialization
166+
167+ Vibe.d expects the `toString`/`fromString` to be present for it to
168+ correctly serialize and deserialize a type.
169+ This allows to use this type as parameter in `vibe.web.rest` methods,
170+ or use it with Vibe.d's serialization module.
171+
172+ ***************************************************************************/
173+
174+ static auto fromString (scope const (char )[] str)
122175 {
123176 return BitBlob! (Bits)(str);
124177 }
125178
126- static assert (
127- Bits % 8 == 0 ,
128- " Argument to BitBlob must be a multiple of 8" );
129-
130179 // / The width of this aggregate, in octets
131180 public static immutable Width = Bits / 8 ;
132181
@@ -136,15 +185,24 @@ public struct BitBlob (size_t Bits)
136185 // / Returns: If this BitBlob has any value
137186 public bool isNull () const
138187 {
139- return this .data[].all ! ((v) => v == 0 ) ;
188+ return this == typeof ( this ).init ;
140189 }
141190
142191 // / Used for sha256Of
143- public const (ubyte )[] opIndex () const
192+ public inout (ubyte )[] opIndex () inout
144193 {
145194 return this .data;
146195 }
147196
197+ // / Convenience overload
198+ public inout (ubyte )[] opSlice (size_t from, size_t to) inout
199+ {
200+ return this .data[from .. to];
201+ }
202+
203+ // / Ditto
204+ alias opDollar = Width;
205+
148206 // / Public because of a visibility bug
149207 public static ubyte fromHex (char c)
150208 {
@@ -195,6 +253,23 @@ unittest
195253 alias Hash = BitBlob! 256 ;
196254 Hash gen1 = GenesisBlockHashStr;
197255 assert (format(" %s" , gen1) == GenesisBlockHashStr);
256+ assert (gen1.toString() == GenesisBlockHashStr);
257+ }
258+
259+ // / Make sure `toString` does not allocate even if it's not `@nogc`
260+ unittest
261+ {
262+ import core.memory ;
263+ import std.format ;
264+ alias Hash = BitBlob! 256 ;
265+
266+ Hash gen1 = GenesisBlockHashStr;
267+ char [Hash.StringBufferSize] buffer;
268+ auto statsBefore = GC .stats();
269+ formattedWrite(buffer[], " %s" , gen1);
270+ auto statsAfter = GC .stats();
271+ assert (buffer == GenesisBlockHashStr);
272+ assert (statsBefore.usedSize == statsAfter.usedSize);
198273}
199274
200275version (unittest )
0 commit comments