1 /*
2 MIT License
3 
4 Copyright (c) 2025 Matheus C. França
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 */
24 
25 /++
26     D bindings for IPCrypt2, a simple and secure IP address obfuscation scheme.
27 
28     IPCrypt2 is a format-preserving encryption scheme for IPv4 and IPv6 addresses.
29     It allows IP addresses to be encrypted while maintaining their format, making it
30     suitable for logging and data retention purposes where IP addresses need to be
31     pseudonymized.
32 
33     $(SECTION Features)
34     $(UL
35         $(LI Format-preserving encryption for both IPv4 and IPv6 addresses)
36         $(LI Cryptographically secure using AES-128 as the underlying cipher)
37         $(LI Preserves subnets: addresses sharing a prefix are encrypted to addresses sharing the same prefix)
38         $(LI Deterministic: same input and key always produces the same output)
39         $(LI Fast and constant-time operation)
40     )
41 +/
42 
43 module ipcrypt2;
44 
45 /// IPCrypt2 C bindings
46 public import c.ipcrypt2c; // @system
47 
48 /**
49  * IPCrypt context, providing encryption/decryption of IP addresses.
50  * Ensures proper initialization and cleanup of the underlying IPCrypt context.
51  */
52 struct IPCrypt2
53 {
54     private IPCrypt context; // Opaque IPCrypt context
55 
56     /**
57      * Constructs an IPCrypt2 with the given 16-byte key.
58      */
59     this(scope const(ubyte)[] key) nothrow @nogc @safe
60     {
61         assert(key.length == IPCRYPT_KEYBYTES, "Invalid key length");
62         () @trusted { ipcrypt_init(&context, &key[0]); }();
63     }
64 
65     /// Ditto, but constructs from a hexadecimal key string.
66     this(string hexKey) nothrow @nogc @safe
67     {
68         ubyte[IPCRYPT_KEYBYTES] key;
69         assert(() @trusted {
70             return ipcrypt_key_from_hex(&key[0], IPCRYPT_KEYBYTES, &hexKey[0], hexKey.length);
71         }() == 0, "Invalid hex key");
72         () @trusted { ipcrypt_init(&context, &key[0]); }();
73     }
74 
75     /// Destructor ensures the IPCrypt context is cleaned up.
76     ~this() nothrow @nogc @safe
77     {
78         () @trusted { ipcrypt_deinit(&context); }();
79     }
80 
81     // Disable copying to prevent double-free
82     @disable this(this);
83 
84     /**
85      * Encrypts a 16-byte IP address (IPv4 or IPv6).
86      * Params:
87      *   ip16 = The 16-byte IP address to encrypt.
88      * Returns: The encrypted 16-byte IP address.
89      */
90     ubyte[IPCRYPT_KEYBYTES] encryptIP16(scope const(ubyte)[] ip16) nothrow @nogc @safe
91     {
92         ubyte[IPCRYPT_KEYBYTES] result;
93         result[] = ip16[0 .. IPCRYPT_KEYBYTES];
94         () @trusted { ipcrypt_encrypt_ip16(&context, &result[0]); }();
95         return result;
96     }
97 
98     /**
99      * Decrypts a 16-byte IP address (IPv4 or IPv6).
100      * Params:
101      *   ip16 = The 16-byte encrypted IP address.
102      * Returns: The decrypted 16-byte IP address.
103      */
104     ubyte[IPCRYPT_KEYBYTES] decryptIP16(scope const(ubyte)[] ip16) nothrow @nogc @safe
105     {
106         ubyte[IPCRYPT_KEYBYTES] result;
107         result[] = ip16[0 .. IPCRYPT_KEYBYTES];
108         () @trusted { ipcrypt_decrypt_ip16(&context, &result[0]); }();
109         return result;
110     }
111 
112     /**
113      * Encrypts an IP address string (IPv4 or IPv6).
114      * Params:
115      *   output = Buffer to store the encrypted IP string (must be at least IPCRYPT_MAX_IP_STR_BYTES).
116      *   ipStr = The IP address string to encrypt.
117      * Returns: The length of the encrypted IP string, or 0 on error.
118      */
119     size_t encryptIPStr(scope char[] output, scope const(char)[] ipStr) nothrow @safe
120     {
121         assert(output.length >= IPCRYPT_MAX_IP_STR_BYTES);
122         return () @trusted {
123             return ipcrypt_encrypt_ip_str(&context, &output[0], &ipStr[0]);
124         }();
125     }
126 
127     /**
128      * Decrypts an encrypted IP address string.
129      * Params:
130      *   output = Buffer to store the decrypted IP string (must be at least IPCRYPT_MAX_IP_STR_BYTES).
131      *   encryptedIPStr = The encrypted IP address string.
132      * Returns: The length of the decrypted IP string, or 0 on error.
133      */
134     size_t decryptIPStr(scope char[] output, scope const(char)[] encryptedIPStr) nothrow @safe
135     {
136         assert(output.length >= IPCRYPT_MAX_IP_STR_BYTES);
137         return () @trusted {
138             return ipcrypt_decrypt_ip_str(&context, &output[0], &encryptedIPStr[0]);
139         }();
140     }
141 
142     /**
143      * Non-deterministic encryption of a 16-byte IP address.
144      * Params:
145      *   ip16 = The 16-byte IP address to encrypt.
146      *   random = 8-byte random data for non-determinism.
147      * Returns: The 24-byte encrypted IP address.
148      */
149     ubyte[IPCRYPT_NDIP_BYTES] ndEncryptIP16(scope const(ubyte)[] ip16, scope const(ubyte)[] random) nothrow @nogc @safe
150     {
151         assert(random.length == IPCRYPT_TWEAKBYTES);
152         ubyte[IPCRYPT_NDIP_BYTES] result;
153         () @trusted {
154             ipcrypt_nd_encrypt_ip16(&context, &result[0], &ip16[0], &random[0]);
155         }();
156         return result;
157     }
158 
159     /**
160      * Non-deterministic decryption of a 24-byte encrypted IP address.
161      * Params:
162      *   ndip = The 24-byte encrypted IP address.
163      * Returns: The 16-byte decrypted IP address.
164      */
165     ubyte[IPCRYPT_KEYBYTES] ndDecryptIP16(scope const(ubyte)[] ndip) nothrow @nogc @safe
166     {
167         assert(ndip.length == IPCRYPT_NDIP_BYTES);
168         ubyte[IPCRYPT_KEYBYTES] result;
169         () @trusted {
170             ipcrypt_nd_decrypt_ip16(&context, &result[0], &ndip[0]);
171         }();
172         return result;
173     }
174 
175     /**
176      * Non-deterministic encryption of an IP address string.
177      * Params:
178      *   output = Buffer to store the encrypted IP string (must be at least IPCRYPT_NDIP_STR_BYTES).
179      *   ipStr = The IP address string to encrypt.
180      *   random = 8-byte random data for non-determinism.
181      * Returns: The length of the encrypted IP string, or 0 on error.
182      */
183     size_t ndEncryptIPStr(scope char[] output, scope const(char)[] ipStr, scope const(ubyte)[] random) nothrow @safe
184     {
185         assert(output.length >= IPCRYPT_NDIP_STR_BYTES);
186         assert(random.length == IPCRYPT_TWEAKBYTES);
187         return () @trusted {
188             return ipcrypt_nd_encrypt_ip_str(&context, &output[0], &ipStr[0], &random[0]);
189         }();
190     }
191 
192     /**
193      * Non-deterministic decryption of an encrypted IP address string.
194      * Params:
195      *   output = Buffer to store the decrypted IP string (must be at least IPCRYPT_MAX_IP_STR_BYTES).
196      *   encryptedIPStr = The encrypted IP address string.
197      * Returns: The length of the decrypted IP string, or 0 on error.
198      */
199     size_t ndDecryptIPStr(scope char[] output, scope const(char)[] encryptedIPStr) nothrow @safe
200     {
201         assert(output.length >= IPCRYPT_MAX_IP_STR_BYTES);
202         return () @trusted {
203             return ipcrypt_nd_decrypt_ip_str(&context, &output[0], &encryptedIPStr[0]);
204         }();
205     }
206 
207     /**
208      * Converts a hexadecimal string to a non-deterministic encrypted IP address.
209      * Params:
210      *   hex = The hexadecimal string.
211      * Returns: The non-deterministic encrypted IP address.
212      */
213     ubyte[IPCRYPT_NDIP_BYTES] ndipFromHex(string hex) nothrow @safe
214     {
215         ubyte[IPCRYPT_NDIP_BYTES] result;
216         assert(() @trusted {
217             return ipcrypt_ndip_from_hex(&result[0], &hex[0], hex.length);
218         }() == 0, "Invalid hex string");
219         return result;
220     }
221 }
222 
223 /**
224  * IPCryptNDX context, providing extended encryption/decryption.
225  * Ensures proper initialization and cleanup of the underlying IPCryptNDX context.
226  */
227 struct IPCryptNDXCtx
228 {
229     private IPCryptNDX context; // Opaque IPCryptNDX context
230 
231     /**
232      * Constructs an IPCryptNDXCtx with the given 32-byte key.
233      */
234     this(scope const(ubyte)[] key) nothrow @nogc @safe
235     {
236         assert(key.length == IPCRYPT_NDX_KEYBYTES, "Invalid key length");
237         assert(() @trusted { return ipcrypt_ndx_init(&context, &key[0]); }() == 0, "Initialization failed");
238     }
239 
240     /// Ditto, but constructs from a hexadecimal key string.
241     this(string hexKey) nothrow @nogc @safe
242     {
243         ubyte[IPCRYPT_NDX_KEYBYTES] key;
244         assert(() @trusted {
245             return ipcrypt_key_from_hex(&key[0], IPCRYPT_NDX_KEYBYTES, &hexKey[0], hexKey.length);
246         }() == 0, "Invalid hex key");
247         assert(() @trusted { return ipcrypt_ndx_init(&context, &key[0]); }() == 0, "Initialization failed");
248     }
249 
250     /// Destructor ensures the IPCryptNDX context is cleaned up.
251     ~this() nothrow @nogc @safe
252     {
253         () @trusted { ipcrypt_ndx_deinit(&context); }();
254     }
255 
256     // Disable copying to prevent double-free
257     @disable this(this);
258 
259     /**
260      * Encrypts a 16-byte IP address (IPv4 or IPv6) with extended non-determinism.
261      * Params:
262      *   ip16 = The 16-byte IP address to encrypt.
263      *   random = 16-byte random data for non-determinism.
264      * Returns: The 32-byte encrypted IP address.
265      */
266     ubyte[IPCRYPT_NDX_NDIP_BYTES] encryptIP16(scope const(ubyte)[] ip16, scope const(ubyte)[] random) nothrow @nogc @safe
267     {
268         assert(random.length == IPCRYPT_NDX_TWEAKBYTES);
269         ubyte[IPCRYPT_NDX_NDIP_BYTES] result;
270         () @trusted {
271             ipcrypt_ndx_encrypt_ip16(&context, &result[0], &ip16[0], &random[0]);
272         }();
273         return result;
274     }
275 
276     /**
277      * Decrypt a non-deterministically encrypted 16-byte IP address, previously encrypted with
278      * `ipcrypt_ndx_encrypt_ip16`.
279      *
280      * Input is ndip, and output is written to ip16.
281      */
282     ubyte[IPCRYPT_KEYBYTES] decryptIP16(scope const(ubyte)[] ndip) nothrow @nogc @safe
283     {
284         assert(ndip.length == IPCRYPT_NDX_NDIP_BYTES);
285         ubyte[IPCRYPT_KEYBYTES] result;
286         () @trusted {
287             ipcrypt_ndx_decrypt_ip16(&context, &result[0], &ndip[0]);
288         }();
289         return result;
290     }
291 
292     /**
293      * Encrypts an IP address string with extended non-determinism.
294      * Params:
295      *   output = Buffer to store the encrypted IP string (must be at least IPCRYPT_NDX_NDIP_STR_BYTES).
296      *   ipStr = The IP address string to encrypt.
297      *   random = 16-byte random data for non-determinism.
298      * Returns: The length of the encrypted IP string, or 0 on error.
299      */
300     size_t encryptIPStr(scope char[] output, scope const(char)[] ipStr, scope const(ubyte)[] random) nothrow @safe
301     {
302         assert(output.length >= IPCRYPT_NDX_NDIP_STR_BYTES);
303         assert(random.length == IPCRYPT_NDX_TWEAKBYTES);
304         return () @trusted {
305             return ipcrypt_ndx_encrypt_ip_str(&context, &output[0], &ipStr[0], &random[0]);
306         }();
307     }
308 
309     /**
310      * Decrypts an encrypted IP address string.
311      * Params:
312      *   output = Buffer to store the decrypted IP string (must be at least IPCRYPT_MAX_IP_STR_BYTES).
313      *   encryptedIPStr = The encrypted IP address string.
314      * Returns: The length of the decrypted IP string, or 0 on error.
315      */
316     size_t decryptIPStr(scope char[] output, scope const(char)[] encryptedIPStr) nothrow @safe
317     {
318         assert(output.length >= IPCRYPT_MAX_IP_STR_BYTES);
319         return () @trusted {
320             return ipcrypt_ndx_decrypt_ip_str(&context, &output[0], &encryptedIPStr[0]);
321         }();
322     }
323 
324     /**
325      * Converts a hexadecimal string to a non-deterministic encrypted IP address.
326      * Params:
327      *   hex = The hexadecimal string.
328      * Returns: The non-deterministic encrypted IP address.
329      */
330     ubyte[IPCRYPT_NDX_NDIP_BYTES] ndipFromHex(string hex) nothrow @safe
331     {
332         ubyte[IPCRYPT_NDX_NDIP_BYTES] result;
333         assert(() @trusted {
334             return ipcrypt_ndx_ndip_from_hex(&result[0], &hex[0], hex.length);
335         }() == 0, "Invalid hex string");
336         return result;
337     }
338 }
339 
340 /**
341  * IPCryptPFX context, providing prefix-preserving encryption/decryption.
342  * Ensures proper initialization and cleanup of the underlying IPCryptPFX context.
343  */
344 struct IPCryptPFXCtx
345 {
346     private IPCryptPFX context; // Opaque IPCryptPFX context
347 
348     /**
349      * Constructs an IPCryptPFXCtx with the given 32-byte key.
350      */
351     this(scope const(ubyte)[] key) nothrow @nogc @safe
352     {
353         assert(key.length == IPCRYPT_PFX_KEYBYTES, "Invalid key length");
354         assert(() @trusted { return ipcrypt_pfx_init(&context, &key[0]); }() == 0, "Initialization failed");
355     }
356 
357     /// Ditto, but constructs from a hexadecimal key string.
358     this(string hexKey) nothrow @nogc @safe
359     {
360         ubyte[IPCRYPT_PFX_KEYBYTES] key;
361         assert(() @trusted {
362             return ipcrypt_key_from_hex(&key[0], IPCRYPT_PFX_KEYBYTES, &hexKey[0], hexKey.length);
363         }() == 0, "Invalid hex key");
364         assert(() @trusted { return ipcrypt_pfx_init(&context, &key[0]); }() == 0, "Initialization failed");
365     }
366 
367     /// Destructor ensures the IPCryptPFX context is cleaned up.
368     ~this() nothrow @nogc @safe
369     {
370         () @trusted { ipcrypt_pfx_deinit(&context); }();
371     }
372 
373     // Disable copying to prevent double-free
374     @disable this(this);
375 
376     /**
377      * Encrypts a 16-byte IP address (IPv4 or IPv6) with prefix preservation.
378      * Params:
379      *   ip16 = The 16-byte IP address to encrypt.
380      * Returns: The encrypted 16-byte IP address.
381      */
382     ubyte[IPCRYPT_KEYBYTES] encryptIP16(scope const(ubyte)[] ip16) nothrow @nogc @safe
383     {
384         ubyte[IPCRYPT_KEYBYTES] result;
385         result[] = ip16[0 .. IPCRYPT_KEYBYTES];
386         () @trusted { ipcrypt_pfx_encrypt_ip16(&context, &result[0]); }();
387         return result;
388     }
389 
390     /**
391      * Decrypts a 16-byte IP address (IPv4 or IPv6) with prefix preservation.
392      * Params:
393      *   ip16 = The 16-byte encrypted IP address.
394      * Returns: The decrypted 16-byte IP address.
395      */
396     ubyte[IPCRYPT_KEYBYTES] decryptIP16(scope const(ubyte)[] ip16) nothrow @nogc @safe
397     {
398         ubyte[IPCRYPT_KEYBYTES] result;
399         result[] = ip16[0 .. IPCRYPT_KEYBYTES];
400         () @trusted { ipcrypt_pfx_decrypt_ip16(&context, &result[0]); }();
401         return result;
402     }
403 
404     /**
405      * Encrypts an IP address string (IPv4 or IPv6) with prefix preservation.
406      * Params:
407      *   output = Buffer to store the encrypted IP string (must be at least IPCRYPT_MAX_IP_STR_BYTES).
408      *   ipStr = The IP address string to encrypt.
409      * Returns: The length of the encrypted IP string, or 0 on error.
410      */
411     size_t encryptIPStr(scope char[] output, scope const(char)[] ipStr) nothrow @safe
412     {
413         assert(output.length >= IPCRYPT_MAX_IP_STR_BYTES);
414         return () @trusted {
415             return ipcrypt_pfx_encrypt_ip_str(&context, &output[0], &ipStr[0]);
416         }();
417     }
418 
419     /**
420      * Decrypts an encrypted IP address string with prefix preservation.
421      * Params:
422      *   output = Buffer to store the decrypted IP string (must be at least IPCRYPT_MAX_IP_STR_BYTES).
423      *   encryptedIPStr = The encrypted IP address string.
424      * Returns: The length of the decrypted IP string, or 0 on error.
425      */
426     size_t decryptIPStr(scope char[] output, scope const(char)[] encryptedIPStr) nothrow @safe
427     {
428         assert(output.length >= IPCRYPT_MAX_IP_STR_BYTES);
429         return () @trusted {
430             return ipcrypt_pfx_decrypt_ip_str(&context, &output[0], &encryptedIPStr[0]);
431         }();
432     }
433 }
434 
435 /**
436  * Converts an IP address string to a 16-byte representation.
437  * Params:
438  *   ipStr = The IP address string (IPv4 or IPv6).
439  * Returns: The 16-byte IP address.
440  */
441 ubyte[IPCRYPT_KEYBYTES] ipStrToIP16(scope const(char)[] ipStr) nothrow @safe
442 {
443     ubyte[IPCRYPT_KEYBYTES] result;
444     assert(() @trusted { return ipcrypt_str_to_ip16(&result[0], &ipStr[0]); }() == 0, "Invalid IP string");
445     return result;
446 }
447 
448 /**
449  * Converts a 16-byte IP address to a string.
450  * Params:
451  *   output = Buffer to store the IP string (must be at least IPCRYPT_MAX_IP_STR_BYTES).
452  *   ip16 = The 16-byte IP address.
453  * Returns: The length of the IP string, or 0 on error.
454  */
455 size_t ip16ToStr(scope char[] output, scope const(ubyte)[] ip16) nothrow @safe
456 {
457     assert(output.length >= IPCRYPT_MAX_IP_STR_BYTES);
458     return () @trusted { return ipcrypt_ip16_to_str(&output[0], &ip16[0]); }();
459 }
460 
461 /**
462  * Converts a sockaddr to a 16-byte IP address.
463  * Params:
464  *   sa = The sockaddr structure.
465  * Returns: The 16-byte IP address.
466  */
467 ubyte[IPCRYPT_KEYBYTES] sockaddrToIP16(scope sockaddr* sa) nothrow @safe
468 {
469     ubyte[IPCRYPT_KEYBYTES] result;
470     assert(() @trusted { return ipcrypt_sockaddr_to_ip16(&result[0], sa); }() == 0, "Invalid sockaddr");
471     return result;
472 }
473 
474 /**
475  * Converts a 16-byte IP address to a sockaddr_storage.
476  * Params:
477  *   ip16 = The 16-byte IP address.
478  * Returns: The sockaddr_storage structure.
479  */
480 sockaddr_storage ip16ToSockaddr(scope const(ubyte)[] ip16) nothrow @nogc @safe
481 {
482     sockaddr_storage result;
483     () @trusted { ipcrypt_ip16_to_sockaddr(&result, &ip16[0]); }();
484     return result;
485 }
486 
487 version (unittest)
488 {
489     @("ip string encryption and decryption") unittest
490     {
491         ubyte[16] key = cast(ubyte[16]) "0123456789abcdef";
492 
493         auto crypt = IPCrypt2(key);
494 
495         string ip_str = "1.2.3.4";
496 
497         char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
498         size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
499         assert(encrypted_ip_len > 0);
500 
501         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
502 
503         const string expected_encrypted_ip = "9f4:e6e1:c77e:ffe8:49ac:6a6a:9f11:620f";
504         assert(expected_encrypted_ip == encrypted_ip);
505 
506         char[IPCRYPT_MAX_IP_STR_BYTES] decrypted_ip_buf;
507         size_t decrypted_ip_len = crypt.decryptIPStr(decrypted_ip_buf[], encrypted_ip);
508         assert(decrypted_ip_len > 0);
509         string decrypted_ip_str = decrypted_ip_buf[0 .. decrypted_ip_len].idup;
510         assert(ip_str == decrypted_ip_str);
511     }
512 
513     @("ip string non-deterministic encryption and decryption") unittest
514     {
515         ubyte[16] key = cast(ubyte[16]) "0123456789abcdef";
516 
517         auto crypt = IPCrypt2(key);
518 
519         string ip_str = "1.2.3.4";
520         ubyte[8] tweak = [1, 2, 3, 4, 5, 6, 7, 8];
521 
522         char[IPCRYPT_NDIP_STR_BYTES] encrypted_ip_buf;
523         size_t encrypted_ip_len = crypt.ndEncryptIPStr(encrypted_ip_buf[], ip_str, tweak);
524         assert(encrypted_ip_len > 0);
525 
526         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
527 
528         const string expected_encrypted_ip = "01020304050607085f8ec3223eaa68378ba06d3bc3df0209";
529         assert(expected_encrypted_ip == encrypted_ip);
530 
531         char[IPCRYPT_MAX_IP_STR_BYTES] decrypted_ip_buf;
532         size_t decrypted_ip_len = crypt.ndDecryptIPStr(decrypted_ip_buf[], encrypted_ip);
533         assert(decrypted_ip_len > 0);
534         string decrypted_ip_str = decrypted_ip_buf[0 .. decrypted_ip_len].idup;
535         assert(ip_str == decrypted_ip_str);
536     }
537 
538     @("binary ip deterministic encryption and decryption") unittest
539     {
540         ubyte[16] expected_ip = [
541             1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
542         ];
543         ubyte[16] key = cast(ubyte[16]) "0123456789abcdef";
544 
545         auto crypt = IPCrypt2(key);
546 
547         ubyte[16] ip = expected_ip;
548         ip = crypt.encryptIP16(ip);
549         ip = crypt.decryptIP16(ip);
550         assert(expected_ip == ip);
551     }
552 
553     @("binary ip non-deterministic encryption and decryption") unittest
554     {
555         ubyte[16] ip = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
556         ubyte[16] key = cast(ubyte[16]) "0123456789abcdef";
557         ubyte[8] tweak = [1, 2, 3, 4, 5, 6, 7, 8];
558 
559         auto crypt = IPCrypt2(key);
560 
561         ubyte[24] encrypted_ip = crypt.ndEncryptIP16(ip, tweak);
562         ubyte[16] decrypted_ip = crypt.ndDecryptIP16(encrypted_ip);
563         assert(ip == decrypted_ip);
564     }
565 
566     @("equivalence between AES and KIASU-BC with tweak=0*") unittest
567     {
568         ubyte[16] ip = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
569         ubyte[16] key = cast(ubyte[16]) "0123456789abcdef";
570         ubyte[8] tweak = [0, 0, 0, 0, 0, 0, 0, 0];
571 
572         auto crypt = IPCrypt2(key);
573 
574         ubyte[24] encrypted_ip = crypt.ndEncryptIP16(ip, tweak);
575 
576         ubyte[16] encrypted_ip2 = crypt.encryptIP16(ip);
577 
578         assert(encrypted_ip[8 .. $] == encrypted_ip2);
579     }
580 
581     @("binary ip NDX encryption and decryption") unittest
582     {
583         ubyte[16] ip = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
584         ubyte[32] key = cast(ubyte[32]) "0123456789abcdef1032547698badcfe";
585         ubyte[16] tweak = [
586             1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
587         ];
588 
589         auto crypt = IPCryptNDXCtx(key);
590 
591         ubyte[32] encrypted_ip = crypt.encryptIP16(ip, tweak);
592         ubyte[16] decrypted_ip = crypt.decryptIP16(encrypted_ip);
593         assert(ip == decrypted_ip);
594     }
595 
596     @("ip string NDX encryption and decryption") unittest
597     {
598         ubyte[32] key = cast(ubyte[32]) "0123456789abcdef1032547698badcfe";
599 
600         auto crypt = IPCryptNDXCtx(key);
601 
602         string ip_str = "1.2.3.4";
603         ubyte[16] tweak = [
604             1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
605         ];
606 
607         char[IPCRYPT_NDX_NDIP_STR_BYTES] encrypted_ip_buf;
608         size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str, tweak);
609         assert(encrypted_ip_len > 0);
610 
611         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
612 
613         const string expected_encrypted_ip = "0102030405060708090a0b0c0d0e0f10a472dd736f82eb599b85141580b21c40";
614         assert(expected_encrypted_ip == encrypted_ip);
615 
616         char[IPCRYPT_MAX_IP_STR_BYTES] decrypted_ip_buf;
617         size_t decrypted_ip_len = crypt.decryptIPStr(decrypted_ip_buf[], encrypted_ip);
618         assert(decrypted_ip_len > 0);
619         string decrypted_ip_str = decrypted_ip_buf[0 .. decrypted_ip_len].idup;
620         assert(ip_str == decrypted_ip_str);
621     }
622 
623     @("test vector for ipcrypt-deterministic") unittest
624     {
625         ubyte[16] key = [
626             0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98,
627             0x76, 0x54, 0x32, 0x10
628         ];
629 
630         auto crypt = IPCrypt2(key);
631 
632         string ip_str = "0.0.0.0";
633 
634         char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
635         size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
636         assert(encrypted_ip_len > 0);
637 
638         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
639 
640         const string expected = "bde9:6789:d353:824c:d7c6:f58a:6bd2:26eb";
641         assert(expected == encrypted_ip);
642     }
643 
644     @("test vector 1 for ipcrypt-nd") unittest
645     {
646         ubyte[16] key = [
647             0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98,
648             0x76, 0x54, 0x32, 0x10
649         ];
650 
651         auto crypt = IPCrypt2(key);
652 
653         string ip_str = "0.0.0.0";
654         ubyte[8] tweak = [0x08, 0xe0, 0xc2, 0x89, 0xbf, 0xf2, 0x3b, 0x7c];
655 
656         char[IPCRYPT_NDIP_STR_BYTES] encrypted_ip_buf;
657         size_t encrypted_ip_len = crypt.ndEncryptIPStr(encrypted_ip_buf[], ip_str, tweak);
658         assert(encrypted_ip_len > 0);
659 
660         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
661 
662         const string expected = "08e0c289bff23b7cb349aadfe3bcef56221c384c7c217b16";
663         assert(expected == encrypted_ip);
664     }
665 
666     @("test vector 2 for ipcrypt-nd") unittest
667     {
668         ubyte[16] key = [
669             0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0xef, 0xcd, 0xab, 0x89,
670             0x67, 0x45, 0x23, 0x01
671         ];
672 
673         auto crypt = IPCrypt2(key);
674 
675         string ip_str = "192.0.2.1";
676         ubyte[8] tweak = [0x21, 0xbd, 0x18, 0x34, 0xbc, 0x08, 0x8c, 0xd2];
677 
678         char[IPCRYPT_NDIP_STR_BYTES] encrypted_ip_buf;
679         size_t encrypted_ip_len = crypt.ndEncryptIPStr(encrypted_ip_buf[], ip_str, tweak);
680         assert(encrypted_ip_len > 0);
681 
682         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
683 
684         const string expected = "21bd1834bc088cd2e5e1fe55f95876e639faae2594a0caad";
685         assert(expected == encrypted_ip);
686     }
687 
688     @("test vector 3 for ipcrypt-nd") unittest
689     {
690         ubyte[16] key = [
691             0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88,
692             0x09, 0xcf, 0x4f, 0x3c
693         ];
694 
695         auto crypt = IPCrypt2(key);
696 
697         string ip_str = "2001:db8::1";
698         ubyte[8] tweak = [0xb4, 0xec, 0xbe, 0x30, 0xb7, 0x08, 0x98, 0xd7];
699 
700         char[IPCRYPT_NDIP_STR_BYTES] encrypted_ip_buf;
701         size_t encrypted_ip_len = crypt.ndEncryptIPStr(encrypted_ip_buf[], ip_str, tweak);
702         assert(encrypted_ip_len > 0);
703 
704         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
705 
706         const string expected = "b4ecbe30b70898d7553ac8974d1b4250eafc4b0aa1f80c96";
707         assert(expected == encrypted_ip);
708     }
709 
710     @("test vector 1 for ipcrypt-ndx") unittest
711     {
712         ubyte[32] key = [
713             0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98,
714             0x76, 0x54, 0x32, 0x10, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe,
715             0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01
716         ];
717 
718         auto crypt = IPCryptNDXCtx(key);
719 
720         string ip_str = "0.0.0.0";
721         ubyte[16] tweak = [
722             0x21, 0xbd, 0x18, 0x34, 0xbc, 0x08, 0x8c, 0xd2, 0xb4, 0xec, 0xbe, 0x30,
723             0xb7, 0x08, 0x98, 0xd7
724         ];
725 
726         char[IPCRYPT_NDX_NDIP_STR_BYTES] encrypted_ip_buf;
727         size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str, tweak);
728         assert(encrypted_ip_len > 0);
729 
730         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
731 
732         const string expected = "21bd1834bc088cd2b4ecbe30b70898d782db0d4125fdace61db35b8339f20ee5";
733         assert(expected == encrypted_ip);
734     }
735 
736     @("test vector 2 for ipcrypt-ndx") unittest
737     {
738         ubyte[32] key = [
739             0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0xef, 0xcd, 0xab, 0x89,
740             0x67, 0x45, 0x23, 0x01, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
741             0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10
742         ];
743 
744         auto crypt = IPCryptNDXCtx(key);
745 
746         string ip_str = "192.0.2.1";
747         ubyte[16] tweak = [
748             0x08, 0xe0, 0xc2, 0x89, 0xbf, 0xf2, 0x3b, 0x7c, 0xb4, 0xec, 0xbe, 0x30,
749             0xb7, 0x08, 0x98, 0xd7
750         ];
751 
752         char[IPCRYPT_NDX_NDIP_STR_BYTES] encrypted_ip_buf;
753         size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str, tweak);
754         assert(encrypted_ip_len > 0);
755 
756         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
757 
758         const string expected = "08e0c289bff23b7cb4ecbe30b70898d7766a533392a69edf1ad0d3ce362ba98a";
759         assert(expected == encrypted_ip);
760     }
761 
762     @("test vector 3 for ipcrypt-ndx") unittest
763     {
764         ubyte[32] key = [
765             0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88,
766             0x09, 0xcf, 0x4f, 0x3c, 0x3c, 0x4f, 0xcf, 0x09, 0x88, 0x15, 0xf7, 0xab,
767             0xa6, 0xd2, 0xae, 0x28, 0x16, 0x15, 0x7e, 0x2b
768         ];
769 
770         auto crypt = IPCryptNDXCtx(key);
771 
772         string ip_str = "2001:db8::1";
773         ubyte[16] tweak = [
774             0x21, 0xbd, 0x18, 0x34, 0xbc, 0x08, 0x8c, 0xd2, 0xb4, 0xec, 0xbe, 0x30,
775             0xb7, 0x08, 0x98, 0xd7
776         ];
777 
778         char[IPCRYPT_NDX_NDIP_STR_BYTES] encrypted_ip_buf;
779         size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str, tweak);
780         assert(encrypted_ip_len > 0);
781 
782         string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
783 
784         const string expected = "21bd1834bc088cd2b4ecbe30b70898d76089c7e05ae30c2d10ca149870a263e4";
785         assert(expected == encrypted_ip);
786     }
787 
788     @("socket address conversion") unittest
789     {
790         // Test IPv4-mapped IPv6 address (1.2.3.4)
791         ubyte[16] ipv4_mapped = [
792             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4
793         ];
794 
795         sockaddr_storage sa = ip16ToSockaddr(ipv4_mapped);
796 
797         ubyte[16] ip16 = sockaddrToIP16(cast(sockaddr*)&sa);
798 
799         assert(ipv4_mapped == ip16);
800 
801         // Test IPv6 address (2001:db8::1)
802         ubyte[16] ipv6 = [
803             0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
804         ];
805 
806         sa = ip16ToSockaddr(ipv6);
807 
808         ip16 = sockaddrToIP16(cast(sockaddr*)&sa);
809 
810         assert(ipv6 == ip16);
811     }
812 
813     @("key from hex conversion") unittest
814     {
815         // Test valid 16-byte key
816         string hex16 = "0123456789abcdef0123456789abcdef";
817         ubyte[16] key16;
818         int ret = () @trusted {
819             return ipcrypt_key_from_hex(&key16[0], key16.length, &hex16[0], hex16.length);
820         }();
821         assert(ret == 0);
822         ubyte[16] expected_key16 = [
823             0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
824             0x89, 0xab, 0xcd, 0xef
825         ];
826         assert(expected_key16 == key16);
827 
828         // Test valid 32-byte key
829         string hex32 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
830         ubyte[32] key32;
831         ret = () @trusted {
832             return ipcrypt_key_from_hex(&key32[0], key32.length, &hex32[0], hex32.length);
833         }();
834         assert(ret == 0);
835         ubyte[32] expected_key32 = [
836             0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
837             0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
838             0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef
839         ];
840         assert(expected_key32 == key32);
841 
842         // Test invalid hex length
843         string invalid_hex = "0123456789abcdef";
844         ubyte[16] key;
845         ret = () @trusted {
846             return ipcrypt_key_from_hex(&key[0], key.length, &invalid_hex[0], invalid_hex.length);
847         }();
848         assert(ret == -1);
849 
850         // Test invalid hex characters
851         string invalid_chars = "0123456789abcdef0123456789abcdeg";
852         ret = () @trusted {
853             return ipcrypt_key_from_hex(&key[0], key.length, &invalid_chars[0], invalid_chars
854                     .length);
855         }();
856         assert(ret == -1);
857     }
858 
859     @("ipcrypt-pfx round-trip") unittest
860     {
861         ubyte[32] key = [
862             0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98,
863             0x76, 0x54, 0x32, 0x10, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe,
864             0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01
865         ];
866 
867         auto crypt = IPCryptPFXCtx(key);
868 
869         // Test with IPv4 address string
870         string ipv4_str = "192.168.1.100";
871 
872         char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ipv4_buf;
873         size_t encrypted_ipv4_len = crypt.encryptIPStr(encrypted_ipv4_buf, ipv4_str);
874         assert(encrypted_ipv4_len > 0);
875         string encrypted_ipv4 = encrypted_ipv4_buf[0 .. $].idup;
876 
877         char[IPCRYPT_MAX_IP_STR_BYTES] decrypted_ipv4_buf;
878         size_t decrypted_ipv4_len = crypt.decryptIPStr(decrypted_ipv4_buf, encrypted_ipv4);
879         assert(decrypted_ipv4_len > 0);
880         string decrypted_ipv4 = decrypted_ipv4_buf[0 .. $].idup;
881 
882         import std.algorithm.comparison : cmp;
883         // import core.stdc.stdio;
884 
885         // printf("ip: |%s|\n", &ipv4_str[0]);
886         // printf("encrypt: |%s|\n", &encrypted_ipv4[0]);
887         // printf("decrypt: |%s|\n", &decrypted_ipv4[0]);
888 
889         assert(cmp(ipv4_str, decrypted_ipv4));
890 
891         // Test with IPv6 address string
892         string ipv6_str = "2001:db8:85a3::8a2e:370:7334";
893 
894         char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ipv6_buf;
895         size_t encrypted_ipv6_len = crypt.encryptIPStr(encrypted_ipv6_buf[], ipv6_str);
896         assert(encrypted_ipv6_len > 0);
897         string encrypted_ipv6 = encrypted_ipv6_buf[0 .. encrypted_ipv6_len].idup;
898 
899         char[IPCRYPT_MAX_IP_STR_BYTES] decrypted_ipv6_buf;
900         size_t decrypted_ipv6_len = crypt.decryptIPStr(decrypted_ipv6_buf[], encrypted_ipv6);
901         assert(decrypted_ipv6_len > 0);
902         string decrypted_ipv6 = decrypted_ipv6_buf[0 .. decrypted_ipv6_len].idup;
903 
904         assert(ipv6_str == decrypted_ipv6);
905 
906         // Test with binary IP16 format for IPv4
907         ubyte[16] ipv4_binary = [
908             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 100
909         ];
910         ubyte[16] original_ipv4_binary = ipv4_binary;
911 
912         ipv4_binary = crypt.encryptIP16(ipv4_binary);
913         ipv4_binary = crypt.decryptIP16(ipv4_binary);
914 
915         assert(original_ipv4_binary == ipv4_binary);
916 
917         // Test with binary IP16 format for IPv6
918         ubyte[16] ipv6_binary = [
919             0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0, 0, 0, 0, 0x8a, 0x2e, 0x03, 0x70,
920             0x73, 0x34
921         ];
922         ubyte[16] original_ipv6_binary = ipv6_binary;
923 
924         ipv6_binary = crypt.encryptIP16(ipv6_binary);
925         ipv6_binary = crypt.decryptIP16(ipv6_binary);
926 
927         assert(original_ipv6_binary == ipv6_binary);
928     }
929 
930     @("ipcrypt-pfx test vectors from python reference") unittest
931     {
932         // Test vector 1
933         {
934             ubyte[32] key = [
935                 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
936                 0x98, 0x76, 0x54, 0x32, 0x10, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba,
937                 0xdc, 0xfe, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01
938             ];
939 
940             auto crypt = IPCryptPFXCtx(key);
941 
942             string ip_str = "0.0.0.0";
943             const string expected = "151.82.155.134";
944 
945             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
946             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
947             assert(encrypted_ip_len > 0);
948             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
949 
950             assert(expected == encrypted_ip);
951         }
952 
953         // Test vector 2
954         {
955             ubyte[32] key = [
956                 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
957                 0x98, 0x76, 0x54, 0x32, 0x10, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba,
958                 0xdc, 0xfe, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01
959             ];
960 
961             auto crypt = IPCryptPFXCtx(key);
962 
963             string ip_str = "255.255.255.255";
964             const string expected = "94.185.169.89";
965 
966             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
967             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
968             assert(encrypted_ip_len > 0);
969             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
970 
971             assert(expected == encrypted_ip);
972         }
973 
974         // Test vector 3
975         {
976             ubyte[32] key = [
977                 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
978                 0x98, 0x76, 0x54, 0x32, 0x10, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba,
979                 0xdc, 0xfe, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01
980             ];
981 
982             auto crypt = IPCryptPFXCtx(key);
983 
984             string ip_str = "192.0.2.1";
985             const string expected = "100.115.72.131";
986 
987             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
988             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
989             assert(encrypted_ip_len > 0);
990             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
991 
992             assert(expected == encrypted_ip);
993         }
994 
995         // Test vector 4
996         {
997             ubyte[32] key = [
998                 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
999                 0x98, 0x76, 0x54, 0x32, 0x10, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba,
1000                 0xdc, 0xfe, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01
1001             ];
1002 
1003             auto crypt = IPCryptPFXCtx(key);
1004 
1005             string ip_str = "2001:db8::1";
1006             const string expected = "c180:5dd4:2587:3524:30ab:fa65:6ab6:f88";
1007 
1008             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1009             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1010             assert(encrypted_ip_len > 0);
1011             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1012 
1013             assert(expected == encrypted_ip);
1014         }
1015 
1016         // Test vector 5
1017         {
1018             ubyte[32] key = [
1019                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1020                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1021                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1022             ];
1023 
1024             auto crypt = IPCryptPFXCtx(key);
1025 
1026             string ip_str = "10.0.0.47";
1027             const string expected = "19.214.210.244";
1028 
1029             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1030             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1031             assert(encrypted_ip_len > 0);
1032             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1033 
1034             assert(expected == encrypted_ip);
1035         }
1036 
1037         // Test vector 6
1038         {
1039             ubyte[32] key = [
1040                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1041                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1042                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1043             ];
1044 
1045             auto crypt = IPCryptPFXCtx(key);
1046 
1047             string ip_str = "10.0.0.129";
1048             const string expected = "19.214.210.80";
1049 
1050             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1051             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1052             assert(encrypted_ip_len > 0);
1053             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1054 
1055             assert(expected == encrypted_ip);
1056         }
1057 
1058         // Test vector 7
1059         {
1060             ubyte[32] key = [
1061                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1062                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1063                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1064             ];
1065 
1066             auto crypt = IPCryptPFXCtx(key);
1067 
1068             string ip_str = "10.0.0.234";
1069             const string expected = "19.214.210.30";
1070 
1071             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1072             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1073             assert(encrypted_ip_len > 0);
1074             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1075 
1076             assert(expected == encrypted_ip);
1077         }
1078 
1079         // Test vector 8
1080         {
1081             ubyte[32] key = [
1082                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1083                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1084                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1085             ];
1086 
1087             auto crypt = IPCryptPFXCtx(key);
1088 
1089             string ip_str = "172.16.5.193";
1090             const string expected = "210.78.229.136";
1091 
1092             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1093             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1094             assert(encrypted_ip_len > 0);
1095             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1096 
1097             assert(expected == encrypted_ip);
1098         }
1099 
1100         // Test vector 9
1101         {
1102             ubyte[32] key = [
1103                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1104                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1105                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1106             ];
1107 
1108             auto crypt = IPCryptPFXCtx(key);
1109 
1110             string ip_str = "172.16.97.42";
1111             const string expected = "210.78.179.241";
1112 
1113             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1114             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1115             assert(encrypted_ip_len > 0);
1116             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1117 
1118             assert(expected == encrypted_ip);
1119         }
1120 
1121         // Test vector 10
1122         {
1123             ubyte[32] key = [
1124                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1125                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1126                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1127             ];
1128 
1129             auto crypt = IPCryptPFXCtx(key);
1130 
1131             string ip_str = "172.16.248.177";
1132             const string expected = "210.78.121.215";
1133 
1134             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1135             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1136             assert(encrypted_ip_len > 0);
1137             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1138 
1139             assert(expected == encrypted_ip);
1140         }
1141 
1142         // Test vector 11
1143         {
1144             ubyte[32] key = [
1145                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1146                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1147                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1148             ];
1149 
1150             auto crypt = IPCryptPFXCtx(key);
1151 
1152             string ip_str = "2001:db8::a5c9:4e2f:bb91:5a7d";
1153             const string expected = "7cec:702c:1243:f70:1956:125:b9bd:1aba";
1154 
1155             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1156             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1157             assert(encrypted_ip_len > 0);
1158             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1159 
1160             assert(expected == encrypted_ip);
1161         }
1162 
1163         // Test vector 12
1164         {
1165             ubyte[32] key = [
1166                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1167                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1168                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1169             ];
1170 
1171             auto crypt = IPCryptPFXCtx(key);
1172 
1173             string ip_str = "2001:db8::7234:d8f1:3c6e:9a52";
1174             const string expected = "7cec:702c:1243:f70:a3ef:c8e:95c1:cd0d";
1175 
1176             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1177             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1178             assert(encrypted_ip_len > 0);
1179             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1180 
1181             assert(expected == encrypted_ip);
1182         }
1183 
1184         // Test vector 13
1185         {
1186             ubyte[32] key = [
1187                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1188                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1189                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1190             ];
1191 
1192             auto crypt = IPCryptPFXCtx(key);
1193 
1194             string ip_str = "2001:db8::f1e0:937b:26d4:8c1a";
1195             const string expected = "7cec:702c:1243:f70:443c:c8e:6a62:b64d";
1196 
1197             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1198             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1199             assert(encrypted_ip_len > 0);
1200             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1201 
1202             assert(expected == encrypted_ip);
1203         }
1204 
1205         // Test vector 14
1206         {
1207             ubyte[32] key = [
1208                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1209                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1210                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1211             ];
1212 
1213             auto crypt = IPCryptPFXCtx(key);
1214 
1215             string ip_str = "2001:db8:3a5c::e7d1:4b9f:2c8a:f673";
1216             const string expected = "7cec:702c:3503:bef:e616:96bd:be33:a9b9";
1217 
1218             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1219             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1220             assert(encrypted_ip_len > 0);
1221             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1222 
1223             assert(expected == encrypted_ip);
1224         }
1225 
1226         // Test vector 15
1227         {
1228             ubyte[32] key = [
1229                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1230                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1231                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1232             ];
1233 
1234             auto crypt = IPCryptPFXCtx(key);
1235 
1236             string ip_str = "2001:db8:9f27::b4e2:7a3d:5f91:c8e6";
1237             const string expected = "7cec:702c:a504:b74e:194a:3d90:b047:2d1a";
1238 
1239             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1240             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1241             assert(encrypted_ip_len > 0);
1242             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1243 
1244             assert(expected == encrypted_ip);
1245         }
1246 
1247         // Test vector 16
1248         {
1249             ubyte[32] key = [
1250                 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15,
1251                 0x88, 0x09, 0xcf, 0x4f, 0x3c, 0xa9, 0xf5, 0xba, 0x40, 0xdb, 0x21,
1252                 0x4c, 0x37, 0x98, 0xf2, 0xe1, 0xc2, 0x34, 0x56, 0x78, 0x9a
1253             ];
1254 
1255             auto crypt = IPCryptPFXCtx(key);
1256 
1257             string ip_str = "2001:db8:d8b4::193c:a5e7:8b2f:46d1";
1258             const string expected = "7cec:702c:f840:aa67:1b8:e84f:ac9d:77fb";
1259 
1260             char[IPCRYPT_MAX_IP_STR_BYTES] encrypted_ip_buf;
1261             size_t encrypted_ip_len = crypt.encryptIPStr(encrypted_ip_buf[], ip_str);
1262             assert(encrypted_ip_len > 0);
1263             string encrypted_ip = encrypted_ip_buf[0 .. encrypted_ip_len].idup;
1264 
1265             assert(expected == encrypted_ip);
1266         }
1267     }
1268 }