1 // Written in the D programming language.
2 
3 /**
4  * Utility functions and unittests.
5  */
6 
7 module wrapper.sodium.utils;
8 
9 import wrapper.sodium.core; // assure sodium got initialized
10 import std.exception : assertThrown, assertNotThrown;
11 import nogc.exception: enforce;
12 
13 public
14 import  deimos.sodium.utils :
15 //                            sodium_memzero,
16                               sodium_stackzero,
17 /*                            sodium_memcmp,
18                               sodium_compare,
19                               sodium_is_zero,
20                               sodium_increment,
21                               sodium_add,
22                               sodium_sub,
23                               sodium_bin2hex,
24                               sodium_hex2bin, */
25                               sodium_base64_VARIANT_ORIGINAL,
26                               sodium_base64_VARIANT_ORIGINAL_NO_PADDING,
27                               sodium_base64_VARIANT_URLSAFE,
28                               sodium_base64_VARIANT_URLSAFE_NO_PADDING,
29                               sodium_base64_ENCODED_LEN,
30                               sodium_base64_encoded_len,
31 //                            sodium_bin2base64,
32 //                            sodium_base642bin,
33                               sodium_mlock,
34                               sodium_munlock,
35                               sodium_malloc,
36                               sodium_allocarray,
37                               sodium_free,
38                               sodium_mprotect_noaccess,
39                               sodium_mprotect_readonly,
40                               sodium_mprotect_readwrite;
41 //                            sodium_pad,
42 //                            sodium_unpad;
43 
44 
45 /* overloading some functions between module deimos.sodium.utils and this module, "overload-set" */
46 
47 
48 /** Zeroing memory.
49  *
50  * After use, sensitive data should be overwritten, but memset() and hand-written code can be
51  * silently stripped out by an optimizing compiler or by the linker.
52  * The sodium_memzero() function tries to effectively zero `len` bytes starting at `pnt`, even if
53  * optimizations are being applied to the code.
54  * See_Also: https://download.libsodium.org/doc/memory_management#zeroing-memory
55  */
56 alias sodium_memzero     = deimos.sodium.utils.sodium_memzero;
57 
58 /** Zeroing memory.
59  *
60  * After use, sensitive data should be overwritten, but memset() and hand-written code can be
61  * silently stripped out by an optimizing compiler or by the linker.
62  * The sodium_memzero() function tries to effectively zero the bytes of array `a`, even if
63  * optimizations are being applied to the code.
64  * See_Also: https://download.libsodium.org/doc/memory_management#zeroing-memory
65  */
66 pragma(inline, true)
67 void sodium_memzero(scope ubyte[] a) @nogc nothrow pure @trusted
68 {
69 //    enforce(a !is null, "a is null"); // not necessary (tested on Linux and Windows)
70     sodium_memzero(a.ptr, a.length);
71 }
72 
73 /** Constant-time test for equality
74  *
75  * WARNING: sodium_memcmp() must be used to verify if two secret keys
76  * are equal, in constant time.
77  * This function is not designed for lexicographical comparisons.
78  * Returns: 0 if the keys are equal, and -1 if they differ.
79  * See_Also: https://download.libsodium.org/doc/helpers#constant-time-test-for-equality
80  */
81 alias sodium_memcmp     = deimos.sodium.utils.sodium_memcmp;
82 
83 /** Constant-time test for equality
84  *
85  * WARNING: sodium_memcmp() must be used to verify if two secret keys
86  * are equal, in constant time (if array's length are equal).
87  * If array's length are NOT equal, the decision is solely based on that,
88  * i.e. no constant time guarantee and the function returns false.
89  * This function is not designed for lexicographical comparisons.
90  * Preferably use this function with equal length arrays.
91  * Returns: true if the keys are equal, and false if they differ.
92  * See_Also: https://download.libsodium.org/doc/helpers#constant-time-test-for-equality
93  */
94 pragma(inline, true)
95 bool  sodium_memcmp(scope const ubyte[] b1_, scope const ubyte[] b2_) @nogc nothrow pure @trusted
96 {
97     if (b1_.length != b2_.length)
98         return false;
99     return sodium_memcmp(b1_.ptr, b2_.ptr, b1_.length) == 0; // __attribute__ ((warn_unused_result))
100 }
101 
102 /** Comparing large numbers.
103  *
104  * It is suitable for lexicographical comparisons, or to compare nonces
105  * and counters stored in little-endian format.
106  * However, it is slower than sodium_memcmp().
107  * The comparison is done in constant time for a given length.
108  * Returns: -1 if b1_ < b2_, 1 if b1_ > b2_ and 0 if b1_ == b2_
109  * See_Also: https://download.libsodium.org/doc/helpers#comparing-large-numbers
110  */
111 alias sodium_compare   = deimos.sodium.utils.sodium_compare;
112 
113 /** Comparing large numbers.
114  *
115  * It is suitable for lexicographical comparisons, or to compare nonces
116  * and counters stored in little-endian format.
117  * However, it is slower than sodium_memcmp().
118  * The comparison is done in constant time for a given length.
119  * The two numbers don't need to have the same length in bytes.
120  * If array's length are NOT equal, the decision may be based on that, i.e. no constant time guarantee.
121  * Preferably use this function with equal length arrays.
122  * Returns: -1 if b1_ < b2_, 1 if b1_ > b2_ and 0 if b1_ == b2_
123  * See_Also: https://download.libsodium.org/doc/helpers#comparing-large-numbers
124  */
125 pragma(inline, true)
126 int sodium_compare(scope const ubyte[] b1_, scope const ubyte[] b2_) @nogc nothrow pure @trusted
127 {
128     import std.algorithm.comparison: min;
129     if      (b1_.length < b2_.length && !sodium_is_zero(b2_[b1_.length..$]))
130         return -1;
131     else if (b1_.length > b2_.length && !sodium_is_zero(b1_[b2_.length..$]))
132         return 1;
133     return  sodium_compare(b1_.ptr, b2_.ptr, min(b1_.length, b2_.length)); // __attribute__ ((warn_unused_result));
134 }
135 
136 /** Testing for all zeros.
137  *
138  * It's execution time is constant for a given length.
139  * Returns:  1  if the `nlen` bytes vector pointed by `n` contains only zeros.
140  *           0  if non-zero bits are found.
141  * See_Also: https://download.libsodium.org/doc/helpers#testing-for-all-zeros
142  */
143 alias sodium_is_zero     = deimos.sodium.utils.sodium_is_zero;
144 
145 /** Testing for all zeros.
146  *
147  * It's execution time is constant for a given length.
148  * Returns:  `ţrue`  if array `a` contains only zeros.
149  *           `false`  if non-zero bits are found.
150  * See_Also: https://download.libsodium.org/doc/helpers#testing-for-all-zeros
151  */
152 pragma(inline, true)
153 bool sodium_is_zero(const ubyte[] a) @nogc nothrow pure @trusted
154 {
155 //  enforce(a !is null, "a is null"); // not necessary
156     return  sodium_is_zero(a.ptr, a.length) == 1;
157 }
158 
159 /// See_Also: https://download.libsodium.org/doc/helpers#incrementing-large-numbers
160 alias sodium_increment = deimos.sodium.utils.sodium_increment;
161 
162 /** Incrementing large numbers.
163  * The sodium_increment() function takes an ubyte array representing an arbitrary-long unsigned number,
164  * and increments it.
165  * It runs in constant-time for a given length, and considers the number to be encoded in little-
166  * endian format.
167  * sodium_increment() can be used to increment nonces in constant time.
168  * This function was introduced in libsodium 1.0.4.
169  * Does nothing if the array is null
170  * See_Also: https://download.libsodium.org/doc/helpers#incrementing-large-numbers
171  */
172 pragma(inline, true)
173 void sodium_increment(ubyte[] n) @nogc nothrow pure @trusted
174 {
175 //  enforce(n !is null, "n is null"); // not necessary
176     sodium_increment(n.ptr, n.length);
177 }
178 
179 /// See_Also: https://download.libsodium.org/doc/helpers#adding-large-numbers
180 alias sodium_add       = deimos.sodium.utils.sodium_add;
181 
182 /** Adding large numbers
183  *
184  * The sodium_add() function accepts two arrays of unsigned numbers encoded in little-
185  * endian format, a and b, both of size len bytes.
186  * It computes (a + b) mod 2^(8*len) in constant time for a given length, and overwrites a
187  * with the result.
188  * History: This function was introduced in libsodium 1.0.7.
189  * Throws: NoGcException, if a_.length != b.length
190  * See_Also: https://download.libsodium.org/doc/helpers#adding-large-numbers
191  */
192 pragma(inline, true)
193 void sodium_add(scope ubyte[] a, scope const ubyte[] b) @nogc /*nothrow pure*/ @trusted
194 {
195     enforce(a.length == b.length, "a.length doesn't equal b.length");
196 //    enforce(a !is null, "a and b are null"); // not necessary
197     sodium_add(a.ptr, b.ptr, a.length);
198 }
199 
200 version(bin_v1_0_16) {}
201 else {
202     /// See_Also: https://download.libsodium.org/doc/helpers#substracting-large-numbers
203     alias sodium_sub       = deimos.sodium.utils.sodium_sub;
204 
205     /** Substracting large numbers
206      * The sodium_sub() function accepts two arrays of unsigned numbers encoded in little-
207      * endian format, a and b, both of size len bytes.
208      * It computes (a - b) mod 2^(8*len) in constant time for a given length, and overwrites a
209      * with the result.
210      * History: This function was introduced in libsodium 1.0.17.
211      * Throws: NoGcException, if a_.length != b.length
212      * See_Also: https://download.libsodium.org/doc/helpers#substracting-large-numbers
213      */
214     pragma(inline, true)
215     void sodium_sub(scope ubyte[] a, scope const ubyte[] b) @nogc /*nothrow pure*/ @trusted
216     {
217         enforce(a.length == b.length, "a.length doesn't equal b.length");
218 //        enforce(a !is null, "a and b are null"); // not necessary
219         sodium_sub(a.ptr, b.ptr, a.length);
220     }
221 } // version(> bin_v1_0_16)
222 
223 /// Returns: char* hex
224 /// See_Also: https://download.libsodium.org/doc/helpers#hexadecimal-encoding-decoding
225 alias sodium_bin2hex     = deimos.sodium.utils.sodium_bin2hex;
226 
227 /** Hexadecimal encoding.
228  * The sodium_bin2hex() function converts the bytes stored at bin into a hexadecimal string.
229  *
230  * It evaluates in constant time for a given size.
231  * hex will receive a terminating null character
232  * Throws: NoGcException, if  hex.length != 2*bin.length+1  or bin.length >= size_t.max/2
233  * See_Also: https://download.libsodium.org/doc/helpers#hexadecimal-encoding-decoding
234  */
235 pragma(inline, true)
236 void sodium_bin2hex(scope char[] hex, scope const ubyte[] bin) @nogc /*nothrow pure*/ @trusted
237 {
238     enforce(bin.length < size_t.max/2);
239 //  enforce(hex.length == 2*bin.length+1, "Expected hex.length: ", hex.length, " to be equal to 2*bin.length+1: ", 2*bin.length+1);
240     enforce(hex.length == 2*bin.length+1, "Expected hex.length is not equal to 2*bin.length+1");
241     sodium_bin2hex(hex.ptr, hex.length, bin.ptr, bin.length); // __attribute__ ((nonnull(1)));
242 }
243 
244 /// Returns:  0  on success,  -1  otherwise
245 /// See_Also: https://download.libsodium.org/doc/helpers#hexadecimal-encoding-decoding
246 alias sodium_hex2bin     = deimos.sodium.utils.sodium_hex2bin;
247 
248 /** Hexadecimal decoding.
249  * The sodium_hex2bin() function parses a hexadecimal string hex and converts it to a byte sequence bin.
250  * ignore_nullterminated is a string of characters to skip.
251  * ignore_nullterminated's last character MUST be '\0' in order to save allocating a new C string and thus be @nogc
252  * For example, the string ": \0" allows colons and
253  * spaces to be present at any locations in the hexadecimal string. These characters will just
254  * be ignored. As a result, "69:FC", "69 FC", "69 : FC" and "69FC" will be valid inputs,
255  * and will produce the same output.
256  * ignore_nullterminated can be set to null in order to disallow any non-hexadecimal character.
257  * bin.length is the maximum number of bytes to put into bin, thus bin has to be sized
258  * appropriately in advance as >= hex.length/2, otherwise the function throws.
259  * The parser stops when a non-hexadecimal, non-ignored character is found or when
260  * bin.length  bytes have been written.
261  * bin_len is the number of bytes that actually got written into bin.
262  * @returns always true (success) and sets `pos_hex_non_parsed` to the position
263  * within `hex` following the last parsed character.
264  * It evaluates in constant time for a given length and format.
265  * Returns:  true  on success,  false  otherwise
266  * Throws: NoGcException, if  bin.length < hex.length/2
267  * See_Also: https://download.libsodium.org/doc/helpers#hexadecimal-encoding-decoding
268  */
269 bool sodium_hex2bin(scope ubyte[] bin, const char[] hex, const string ignore_nullterminated,
270                     out size_t bin_len, out size_t pos_hex_non_parsed) @nogc /*nothrow pure*/ @trusted
271 {
272     import std.algorithm.comparison : clamp;
273     enforce(bin.length >= hex.length/2);
274     const(char)*  hex_non_parsed_ptr;
275     bool result;
276     result = sodium_hex2bin(bin.ptr, bin.length, hex.ptr, hex.length, ignore_nullterminated.ptr, &bin_len, &hex_non_parsed_ptr) == 0;
277     pos_hex_non_parsed = cast(size_t) clamp(hex_non_parsed_ptr - hex.ptr, ptrdiff_t(0), ptrdiff_t(hex.length)); // __attribute__ ((nonnull(1)));
278     return result;
279 }
280 
281 /// Returns: char* b64
282 /// See_Also: https://download.libsodium.org/doc/helpers#base64-encoding-decoding
283 alias sodium_bin2base64  = deimos.sodium.utils.sodium_bin2base64;
284 
285 /// Throws: NoGcException, if  b64.length < sodium_base64_encoded_len(bin.length, variant)
286 /// See_Also: https://download.libsodium.org/doc/helpers#base64-encoding-decoding
287 pragma(inline, true)
288 void sodium_bin2base64(scope char[] b64, scope const ubyte[] bin, const int variant) @nogc /*nothrow pure*/ @trusted
289 {
290     size_t min_len = sodium_base64_encoded_len(bin.length, variant);
291     enforce(b64.length >= min_len);
292     sodium_bin2base64(b64.ptr, b64.length, bin.ptr, bin.length, variant); // __attribute__ ((nonnull(1)));
293 }
294 
295 /// Returns: 0  on success,  -1  otherwise
296 /// See_Also: https://download.libsodium.org/doc/helpers#base64-encoding-decoding
297 alias sodium_base642bin  = deimos.sodium.utils.sodium_base642bin;
298 
299 /// Returns: true  on success,  false  otherwise
300 /// See_Also: https://download.libsodium.org/doc/helpers#base64-encoding-decoding
301 bool sodium_base642bin(scope ubyte[] bin, const char[] b64, const string ignore_nullterminated,
302                        out size_t bin_len, out size_t pos_b64_non_parsed, const int variant) /*@nogc nothrow pure*/ @trusted
303 {
304     import std.algorithm.comparison : clamp;
305     import std.algorithm.searching: countUntil; // not @nogc
306     import std.range: retro;
307     const(char)*  b64_non_parsed_ptr;
308     bool result;
309     enforce(bin.length >= (b64.length-1)/4*3 -countUntil!"a!=b"(b64[0..$-1].retro, '='));
310     result = sodium_base642bin(&bin[0], bin.length, b64.ptr, b64.length, ignore_nullterminated.ptr, &bin_len, &b64_non_parsed_ptr, variant) == 0;
311     pos_b64_non_parsed = cast(size_t) clamp(b64_non_parsed_ptr - b64.ptr, ptrdiff_t(0), ptrdiff_t(b64.length)); // __attribute__ ((nonnull(1)));
312     return result;
313 }
314 
315 /// Returns: 0  on success,  -1  otherwise
316 /// See_Also: https://download.libsodium.org/doc/padding#usage
317 alias sodium_pad         = deimos.sodium.utils.sodium_pad;
318 
319 /// Returns: true  on success,  false  otherwise
320 /// See_Also: https://download.libsodium.org/doc/padding#usage
321 pragma(inline, true)
322 bool sodium_pad(out size_t padded_buflen, scope ubyte[] buf,
323                 size_t unpadded_buflen, size_t blocksize)  @nogc nothrow pure @trusted
324 {
325     return sodium_pad(&padded_buflen, buf.ptr, unpadded_buflen, blocksize, buf.length) == 0;
326 }
327 
328 
329 /// Returns: 0  on success,  -1  otherwise
330 /// See_Also: https://download.libsodium.org/doc/padding#usage
331 alias sodium_unpad       = deimos.sodium.utils.sodium_unpad;
332 
333 /// Returns: true  on success,  false  otherwise
334 /// See_Also: https://download.libsodium.org/doc/padding#usage
335 pragma(inline, true)
336 bool sodium_unpad(out size_t unpadded_buflen, scope ubyte[] buf,
337                   size_t padded_buflen, size_t blocksize)  @nogc nothrow pure @trusted
338 {
339     return sodium_unpad(&unpadded_buflen, buf.ptr, padded_buflen, blocksize) == 0;
340 }
341 
342 
343 @safe
344 unittest
345 {
346 }
347 
348 pure @system
349 unittest
350 {
351     debug {
352         import std.stdio : writeln;
353         writeln("unittest block 1 from sodium.utils.d");
354     }
355     assert(sodium_base64_ENCODED_LEN(19, sodium_base64_VARIANT_ORIGINAL)            == sodium_base64_encoded_len(19, sodium_base64_VARIANT_ORIGINAL));
356     assert(sodium_base64_ENCODED_LEN(19, sodium_base64_VARIANT_ORIGINAL_NO_PADDING) == sodium_base64_encoded_len(19, sodium_base64_VARIANT_ORIGINAL_NO_PADDING));
357     assert(sodium_base64_ENCODED_LEN(19, sodium_base64_VARIANT_URLSAFE)             == sodium_base64_encoded_len(19, sodium_base64_VARIANT_URLSAFE));
358     assert(sodium_base64_ENCODED_LEN(19, sodium_base64_VARIANT_URLSAFE_NO_PADDING)  == sodium_base64_encoded_len(19, sodium_base64_VARIANT_URLSAFE_NO_PADDING));
359 //sodium_memzero
360 //sodium_stackzero
361 //sodium_is_zero
362     import std.algorithm.searching : any;
363     import std.range : iota, array;
364 
365     int[8] a = [1,2,3,4,5,6,7,8]; // allocate on the stack
366     sodium_memzero(a.ptr, a.length*int.sizeof);
367 
368     assert(!any(a[])); // none of a[] evaluate to true
369 //  assert( all!"a == 0"(a[]));
370 //  assert(all!(x => x == 0)(a[]));
371     sodium_memzero(null, 0);
372     sodium_stackzero(8); // TODO check this later
373 
374     int[] b = array(iota(99)); // allocate on the heap
375     assert(sodium_is_zero(cast(ubyte*)b.ptr, b.length*int.sizeof) == 0);
376     sodium_memzero(b.ptr, b.length*int.sizeof);
377     assert(!any(b));
378     assert(sodium_is_zero(cast(ubyte*)b.ptr, b.length*int.sizeof) == 1);
379     assert(sodium_is_zero(null, 0) == 1);
380 
381 //sodium_memcmp
382     a = [1,2,3,4,5,6,7,8];
383     b = a.dup;
384     assert(sodium_memcmp(a.ptr, b.ptr, a.length*int.sizeof) ==  0, "sodium_memcmp error");
385     b[7] = 255;
386     assert(sodium_memcmp(a.ptr, b.ptr, a.length*int.sizeof) == -1, "sodium_memcmp error");
387     cast(void) sodium_memcmp(null, null, 0);
388 
389 //sodium_compare
390 //sodium_increment
391 //sodium_add
392 
393     //           LSB       MSB
394     ubyte[] c = [254,2,3,4,254];
395     ubyte[] d = c.dup;
396     d[0] = 253;
397     d[4] = 255;
398     assert(sodium_compare(c.ptr, d.ptr, c.length) == -1);
399     d[0] = d[4] = 254;
400     assert(sodium_compare(c.ptr, d.ptr, c.length) ==  0);
401     c[0] = 253;
402     c[4] = 255;
403     assert(sodium_compare(c.ptr, d.ptr, c.length) ==  1);
404     assert(sodium_compare(null, null, 0) ==  0);
405 
406     union anonymous {
407         ulong    a = 0x40_00_00_00_00_00_00_00UL;
408         ubyte[8] b;
409     }
410     anonymous  u, v, w;
411     sodium_increment(u.b.ptr,    u.b.length);
412     assert(u.a == 0x40_00_00_00_00_00_00_01UL);
413 
414     sodium_add(u.b.ptr, v.b.ptr, u.b.length);
415     assert(u.a == 0x80_00_00_00_00_00_00_01UL);
416     sodium_add(null, null, 0);
417 
418     ubyte[100]  buf;
419     size_t      buf_unpadded_len  =  10;
420     size_t      buf_padded_len;
421     size_t      block_size  =  16;
422     /* round the length of the buffer to a multiple of `block_size` by appending
423      * padding data and put the new, total length into `buf_padded_len` */
424     assert(sodium_pad(&buf_padded_len, buf.ptr, buf_unpadded_len, block_size, buf.sizeof) == 0);
425     assert(buf_padded_len == 16); // otherwise  /* overflow!  buf[] is not large enough */
426     buf_unpadded_len = 0;
427     /* compute the original, unpadded length */
428     assert(sodium_unpad(&buf_unpadded_len, buf.ptr, buf_padded_len, block_size) ==  0);
429     assert(buf_unpadded_len == 10); // otherwise  /* incorrect padding */
430 }
431 
432 /*pure*/ @safe
433 unittest // same as before except @safe and wrapping delegates + overloads
434 {
435     debug {
436         import std.stdio : writeln;
437         writeln("unittest block 2 from sodium.utils.d");
438     }
439 //sodium_memzero
440 //sodium_is_zero
441     import std.algorithm.searching : any;
442     import std.range : iota, array;
443 
444     int[8] a = [1,2,3,4,5,6,7,8]; // allocate on the stack
445     (() @trusted => sodium_memzero(a.ptr, a.length*int.sizeof))();
446     assert(!any(a[]));
447 
448     int[] b = array(iota(99)); // allocate on the heap
449     assert((() @trusted => sodium_is_zero(cast(ubyte*)b.ptr, b.length*int.sizeof))() == 0);
450     (() @trusted => sodium_memzero(b.ptr, b.length*int.sizeof))();
451     assert(!any(b));
452     assert((() @trusted => sodium_is_zero(cast(ubyte*)b.ptr, b.length*int.sizeof))() == 1);
453 
454 //sodium_memcmp
455     a = [1,2,3,4,5,6,7,8];
456     b = a.dup;
457     assert((() @trusted => sodium_memcmp(a.ptr, b.ptr, a.length*int.sizeof))() ==  0, "sodium_memcmp error");
458     b[7] = 255;
459     assert((() @trusted => sodium_memcmp(a.ptr, b.ptr, a.length*int.sizeof))() == -1, "sodium_memcmp error");
460 
461 //sodium_compare (both functions)
462 //sodium_increment  (both functions)
463 //sodium_add  (both functions)
464 //sodium_is_zero (overload)
465 
466     {
467         ulong c, d;
468         c = ulong.max-1;
469         d = ulong.max;
470         /*
471           doing several "dangerous" actions here in a controlled way:
472           The following operations are not allowed in safe functions:
473 
474           No casting from a pointer type to any type other than void*.
475           Calling any system functions.
476           No taking the address of a local variable or function parameter.
477          */
478         assert( (() @trusted => sodium_compare(cast(ubyte*)&c, cast(ubyte*)&d, ulong.sizeof))() == -1);
479         --d;
480         assert( (() @trusted => sodium_compare(cast(ubyte*)&c, cast(ubyte*)&d, ulong.sizeof))() ==  0);
481         ++c;
482         assert( (() @trusted => sodium_compare(cast(ubyte*)&c, cast(ubyte*)&d, ulong.sizeof))() ==  1);
483     }
484     {   // testing the overload
485         //           LSB       MSB
486         ubyte[] c = [254,2,3,4,254];
487         ubyte[] d = c.dup;
488         assert(!sodium_is_zero(c));
489         d[0] = 253;
490         d[4] = 255;
491         assert(sodium_compare(c, d) == -1);
492         assert(!sodium_memcmp(c, d));
493 
494         d[0] = d[4] = 254;
495         assert(sodium_compare(c, d) ==  0);
496         assert( sodium_memcmp(c, d));
497         c[0] = 253;
498         c[4] = 255;
499         assert(sodium_compare(c, d) ==  1);
500         // checking unequal length
501         d ~= 0;
502         assert(sodium_compare(c, d) ==  1);
503         c = [254,2,3,4,254];
504         d = [254,2,3,4,254, 0];
505         assert(sodium_compare(c, d) ==  0);
506         c ~= 0;
507         d.length = 5;
508         d[0] = 253;
509         d[4] = 255;
510         assert(sodium_compare(c, d) == -1);
511         c[5] = 1;
512         assert(sodium_compare(c, d) ==  1); // if (b1_.length > b2_.length && !sodium_is_zero(b1_[b2_.length..$])) return  1;
513         d = c.dup;
514         d ~= 1;
515         assert(sodium_compare(c, d) == -1); // if (b1_.length < b2_.length && !sodium_is_zero(b2_[b1_.length..$])) return -1;
516         assert(!sodium_memcmp(c, d));
517 
518         assert(sodium_compare(null, null) ==  0);
519 //      ubyte[] dummy = [1];
520 //      assertThrown(enforce( sodium_compare(null, dummy) == -1, new Exception("this should be thrown")));
521         assert(sodium_is_zero(null));
522 
523         sodium_memzero(c);
524         assert(sodium_is_zero(c));
525         sodium_memzero(null);
526     }
527 
528     union anonymous {
529         ulong    a = 0x40_00_00_00_00_00_00_00UL;
530         ubyte[8] b;
531     }
532     anonymous  u, v;
533     (() @trusted => sodium_increment(u.b.ptr, u.b.length))();
534     assert(u.a == 0x40_00_00_00_00_00_00_01UL);
535 
536     (() @trusted => sodium_add(u.b.ptr, v.b.ptr, u.b.length))();
537     assert(u.a == 0x80_00_00_00_00_00_00_01UL);
538 
539     u.a = v.a   = 0x40_00_00_00_00_00_00_00UL;
540     sodium_increment(u.b);
541     assert(u.a == 0x40_00_00_00_00_00_00_01UL);
542     sodium_increment(null);
543 
544     sodium_add(u.b, v.b);
545     assert(u.a == 0x80_00_00_00_00_00_00_01UL);
546     ubyte[] dummy; // dummy is null
547     sodium_add(dummy, null);
548     assert(dummy is null);
549 
550     version(bin_v1_0_16) {}
551     else {
552         sodium_sub(u.b, v.b);
553         assert(u.a == 0x40_00_00_00_00_00_00_01UL);
554     }
555 
556     ubyte[100]  buf;
557     size_t      buf_unpadded_len  =  10;
558     size_t      buf_padded_len;
559     size_t      block_size  =  16;
560     /* round the length of the buffer to a multiple of `block_size` by appending
561      * padding data and put the new, total length into `buf_padded_len` */
562     assert(sodium_pad(buf_padded_len, buf, buf_unpadded_len, block_size));
563     assert(buf_padded_len == 16); // otherwise  /* overflow!  buf[] is not large enough */
564     buf_unpadded_len = 0;
565     /* compute the original, unpadded length */
566     assert(sodium_unpad(buf_unpadded_len, buf, buf_padded_len, block_size));
567     assert(buf_unpadded_len == 10); // otherwise  /* incorrect padding */
568 }
569 
570 pure @system
571 unittest
572 {
573     debug {
574         import std.stdio : writeln;
575         writeln("unittest block 3 from sodium.utils.d");
576     }
577     import std.string : fromStringz;
578     import std.conv : hexString;
579 
580 //sodium_bin2hex
581     ubyte[] a = [1,2,3,4,255];
582     char[] result_out = new char[](11);
583     char* result = sodium_bin2hex(result_out.ptr, result_out.length, a.ptr, a.length);
584     assert(result.fromStringz==result_out[0..$-1]); // the "strings" result.fromStringz as well as "01020304ff".dup have no terminating \0 character, but result_out has it!
585     assert("01020304ff".dup  ==result_out[0..$-1]);
586 
587 //sodium_hex2bin
588     auto    vbuf = cast(immutable(ubyte)[]) hexString!"ac 9f ff 4e ba"; // for comparison
589     string  hex = "ac:9f:ff:4e:bay";
590     const(char)*  ignore_nullterminated_ptr = ":";
591     ubyte[] bin = new ubyte[]((hex.length+1)/3);
592     size_t  bin_len;
593     const(char)*   hex_end;
594     assert(sodium_hex2bin(bin.ptr, bin.length, hex.ptr, hex.length, ignore_nullterminated_ptr, &bin_len, &hex_end) == 0);
595     assert(bin == vbuf); // [172, 159, 255, 78, 186]);
596     assert(bin_len  == 5);
597     assert(hex.ptr+14 == hex_end); // addresses pointing to character y; i.e. hex_end points into hex
598     assert(*hex_end == 'y');
599 //  assert(*++hex_end == '\0'); // i.e. a D string is guaranteed to be followed by a '\0' in memory ? Is this always true ???
600 
601     --hex.length;
602     hex ~= ":fa";
603     bin[]   = ubyte.init; // bin.length unchanged; to small now to incorporate all input
604     assert(sodium_hex2bin(bin.ptr, bin.length, hex.ptr, hex.length, ignore_nullterminated_ptr, &bin_len, &hex_end) == -1);
605     assert(bin == vbuf); // [172, 159, 255, 78, 186]);
606     assert(bin_len == 0);
607     assert(*hex_end == 'f');
608     assert(*++hex_end == 'a');
609 }
610 
611 @safe
612 unittest // same as before except @safe and wrapping delegates + overloads
613 {
614     import std.stdio : writeln;
615     import std.algorithm.comparison : equal;
616     import std.conv : hexString;
617     debug  writeln("unittest block 4 from sodium.utils.d");
618 
619 //sodium_bin2hex
620     ubyte[] a = [1,2,3,4,255];
621     char[] result_out = new char[](a.length*2+1);
622     char* result = (() @trusted => sodium_bin2hex(result_out.ptr, result_out.length, a.ptr, a.length))();
623 //  assert(result.fromStringz==result_out[0..$-1]); // the "strings" result.fromStringz as well as "01020304ff".dup have no terminating \0 character, but result_out has it!
624     assert(equal("01020304ff", result_out[0..$-1]));
625     result_out[] = char.init;
626     sodium_bin2hex(result_out, a);
627     assert(equal("01020304ff", result_out[0..$-1]));
628     char[] one = [char.init];
629     sodium_bin2hex(one, null);
630     assertThrown(sodium_bin2hex(null, null));
631 
632 //sodium_hex2bin overload only
633     auto    vbuf = cast(immutable(ubyte)[]) hexString!"ac 9f ff 4e ba"; // for comparison
634     string  hex = "ac:9f:ff:4e:bay";
635     string  ignore_nullterminated = ":\0";
636     ubyte[] bin = new ubyte[](hex.length/2); //((hex.length+1)/3);
637     size_t  bin_len, pos_hex_non_parsed;
638 
639     assert(sodium_hex2bin(bin, hex, ignore_nullterminated, bin_len, pos_hex_non_parsed));
640     assert(bin_len == 5);
641     assert(bin[0..bin_len] == vbuf); // [172, 159, 255, 78, 186]);
642     assert(pos_hex_non_parsed == 14);
643     assertNotThrown(sodium_hex2bin(bin, hex, null, bin_len, pos_hex_non_parsed));
644 /*
645   --hex.length;
646   hex ~= ":fa87";
647 //  bin.length = hex.length/2;
648   bin[] = ubyte.init;
649   assert(!sodium_hex2bin(bin, hex, ignore_nullterminated, bin_len, pos_hex_non_parsed));
650   assert(bin[0..6] == vbuf~ubyte(0xfa));
651   assert(bin_len == 0);
652   assert(pos_hex_non_parsed == 17);
653 */
654 }
655 
656 @system
657 unittest
658 {
659     debug {
660         import std.stdio : writeln;
661         writeln("unittest block 5 from sodium.utils.d");
662     }
663     import std.algorithm.comparison : equal;
664 //sodium_mlock
665 //sodium_munlock
666     ubyte[] a = [1,2,3,4,5,6,7,8];
667     assert(sodium_is_zero(a.ptr, a.length) == 0);
668     {
669         assert(      sodium_mlock  (a.ptr, a.length) != -1); // inhibits swapping and excludes from a core dump on Linux
670         assert(equal(a, [1,2,3,4,5,6,7,8]));
671         scope(exit)  sodium_munlock(a.ptr, a.length); // sodium_munlock calls sodium_memzero internally
672     }
673     assert(sodium_is_zero(a.ptr, a.length) == 1);
674 }
675 
676 @safe
677 unittest // same as before except @safe and wrapping delegates + overloads
678 {
679     debug {
680         import std.stdio : writeln;
681         writeln("unittest block 6 from sodium.utils.d");
682     }
683     import std.algorithm.comparison : equal;
684 //sodium_mlock
685 //sodium_munlock
686     ubyte[] a = [1,2,3,4,5,6,7,8];
687     assert(!sodium_is_zero(a));
688     {
689         assert( (() @trusted => sodium_mlock(a.ptr, a.length))() != -1);
690         assert(equal(a, [1,2,3,4,5,6,7,8]));
691         scope(exit)  (() @trusted => sodium_munlock(a.ptr, a.length))();
692     }
693     assert(sodium_is_zero(a));
694 
695     a = [65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83];
696     size_t min_len = sodium_base64_encoded_len(a.length, sodium_base64_VARIANT_ORIGINAL);
697     char[] b64 = new char[](min_len); // 29
698     sodium_bin2base64(b64, a, sodium_base64_VARIANT_ORIGINAL);
699 //  writeln(b64); // QUJDREVGR0hJSktMTU5PUFFSUw==
700     a[] = ubyte.init;
701     size_t bin_len;
702     size_t pos_b64_non_parsed;
703     assert(sodium_base642bin(a, b64, null, bin_len, pos_b64_non_parsed, sodium_base64_VARIANT_ORIGINAL));
704     assert(bin_len == a.length);
705     assert(equal(a, [65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83]));
706     assert(pos_b64_non_parsed == b64.length-1);
707 }
708 
709 @system
710 unittest
711 {
712     debug {
713         import std.stdio : writeln;
714         writeln("unittest block 7 from sodium.utils.d");
715     }
716     import std.conv : emplace;
717     import std.algorithm.searching : all;
718     import std.algorithm.comparison : equal;
719 
720     struct S {
721         int[16] a;
722     }
723 //sodium_malloc
724 //sodium_free
725     S* p = cast(S*)sodium_malloc(64);
726     scope(exit)  sodium_free(p);
727     emplace!S(p, S( [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]));
728     assert(equal(p.a[], [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]));
729     int[] arr_int = new (p) int[16];
730     assert( all!"a == 0"(arr_int));
731 
732     enum s_len = 2;
733     alias s_type = S[s_len];
734 //sodium_allocarray
735     s_type* p2 = cast(s_type*)sodium_allocarray(s_len, 64);
736     scope(exit)  sodium_free(p2);
737     emplace!s_type(p2, S( [17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]));
738     assert( (*p2)[0].a == [17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]);
739     assert( (*p2)[1].a == [17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]);
740 
741     ubyte* p3 = cast(ubyte*)sodium_malloc(64);
742     scope(exit)  sodium_free(p3);
743     ubyte[] arr_ubyte = p3[0..64]; // may be ubyte[] as well, but may not be increased, to still live in sodium_malloc'ed chunk!
744 //  writefln("arr_ubyte = p3[0..64] : [%(%02X %)]", p3[0..64]);
745 //assert( all!"a == 0xD0"(arr_ubyte));   in version 1.0.11
746     assert( all!"a == 0xDB"(arr_ubyte)); //in version 1.0.12
747     assert( arr_ubyte.capacity == 0);
748 
749     p.a = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16];
750 //sodium_mprotect_noaccess
751     sodium_mprotect_noaccess(p);
752     //assert(equal(p.a[], [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16])); //unrecoverable, just crashes !
753 
754 //sodium_mprotect_readonly
755     sodium_mprotect_readonly(p);
756     assert(equal(p.a[], [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]));
757     //p.a[0] = 0;  //unrecoverable, just crashes !
758 
759 //sodium_mprotect_readwrite
760     sodium_mprotect_readwrite(p);
761     p.a[0] = 0;
762     assert(p.a[0] == 0);
763 }
764 
765 @nogc @safe
766 unittest
767 {
768     import std.string : fromStringz; // is @system
769     ubyte[8] a, b;
770     sodium_memzero(a);
771     assert(sodium_is_zero(a));
772     assert(sodium_memcmp(a, b));
773     assert(sodium_compare(a, b) == 0);
774     sodium_increment(a);
775     sodium_add(a, b);
776     char[17] hex;
777     sodium_bin2hex(hex, a);
778     size_t bin_len, pos_hex_non_parsed;
779     assert(sodium_hex2bin(b, (() @trusted => fromStringz(hex.ptr))(), null, bin_len, pos_hex_non_parsed));
780     assert(bin_len == 8);
781     assert(pos_hex_non_parsed == 16);
782 }