1 module crypto.hex;
2 
3 import std.traits;
4 
5 // from https://github.com/dlang/phobos/pull/3058/files
6 
7 /**
8 Check the correctness of a string for `hexString`.
9 The result is true if and only if the input string is composed of whitespace
10 characters (\f\n\r\t\v lineSep paraSep nelSep) and
11 an even number of hexadecimal digits (regardless of the case).
12 */
13 @safe pure @nogc
14 private bool isHexLiteral(String)(scope const String hexData)
15 {
16     import std.ascii : isHexDigit;
17     import std.uni : lineSep, paraSep, nelSep;
18     size_t i;
19     foreach (const dchar c; hexData)
20     {
21         switch (c)
22         {
23             case ' ':
24             case '\t':
25             case '\v':
26             case '\f':
27             case '\r':
28             case '\n':
29             case lineSep:
30             case paraSep:
31             case nelSep:
32             continue;
33 
34             default:
35             break;
36         }
37         if (c.isHexDigit)
38             ++i;
39         else
40             return false;
41     }
42     return !(i & 1);
43 }
44 
45 ///
46 @safe unittest
47 {
48     // test all the hex digits
49     static assert( ("0123456789abcdefABCDEF").isHexLiteral);
50     // empty or white strings are not valid
51     static assert( "\r\n\t".isHexLiteral);
52     // but are accepted if the count of hex digits is even
53     static assert( "A\r\n\tB".isHexLiteral);
54 }
55 
56 @safe unittest
57 {
58     import std.ascii;
59     // empty/whites
60     static assert( "".isHexLiteral);
61     static assert( " \r".isHexLiteral);
62     static assert( whitespace.isHexLiteral);
63     static assert( ""w.isHexLiteral);
64     static assert( " \r"w.isHexLiteral);
65     static assert( ""d.isHexLiteral);
66     static assert( " \r"d.isHexLiteral);
67     static assert( "\u2028\u2029\u0085"d.isHexLiteral);
68     // odd x strings
69     static assert( !("5" ~ whitespace).isHexLiteral);
70     static assert( !"123".isHexLiteral);
71     static assert( !"1A3".isHexLiteral);
72     static assert( !"1 23".isHexLiteral);
73     static assert( !"\r\n\tC".isHexLiteral);
74     static assert( !"123"w.isHexLiteral);
75     static assert( !"1A3"w.isHexLiteral);
76     static assert( !"1 23"w.isHexLiteral);
77     static assert( !"\r\n\tC"w.isHexLiteral);
78     static assert( !"123"d.isHexLiteral);
79     static assert( !"1A3"d.isHexLiteral);
80     static assert( !"1 23"d.isHexLiteral);
81     static assert( !"\r\n\tC"d.isHexLiteral);
82     // even x strings with invalid charset
83     static assert( !"12gG".isHexLiteral);
84     static assert( !"2A  3q".isHexLiteral);
85     static assert( !"12gG"w.isHexLiteral);
86     static assert( !"2A  3q"w.isHexLiteral);
87     static assert( !"12gG"d.isHexLiteral);
88     static assert( !"2A  3q"d.isHexLiteral);
89     // valid x strings
90     static assert( ("5A" ~ whitespace).isHexLiteral);
91     static assert( ("5A 01A C FF de 1b").isHexLiteral);
92     static assert( ("0123456789abcdefABCDEF").isHexLiteral);
93     static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF").isHexLiteral);
94     static assert( ("5A 01A C FF de 1b"w).isHexLiteral);
95     static assert( ("0123456789abcdefABCDEF"w).isHexLiteral);
96     static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF"w).isHexLiteral);
97     static assert( ("5A 01A C FF de 1b"d).isHexLiteral);
98     static assert( ("0123456789abcdefABCDEF"d).isHexLiteral);
99     static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF"d).isHexLiteral);
100     // library version allows what's pointed by issue 10454
101     static assert( ("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").isHexLiteral);
102 }
103 
104 /**
105 The $(D hexBytes) function is similar to the $(D hexString) one except
106 that it returns the hexadecimal data as an array of integral.
107 Params:
108 hexData = the $(D string) to be converted.
109 T = the integer type of an array element. By default it is set to $(D ubyte)
110 but all the integer types, excepted $(D ulong) and $(D long), are accepted.
111 Returns:
112 an array of T.
113  */
114 @property @trusted nothrow pure
115 auto hexBytes(string hexData, T = ubyte)()
116 if (hexData.isHexLiteral && isIntegral!T && (T.sizeof <= 4))
117 {
118     static if (T.sizeof == 1)
119         return cast(T[]) hexStrImpl!char(hexData);
120     else static if (T.sizeof == 2)
121         return cast(T[]) hexStrImpl!wchar(hexData);
122     else
123         return cast(T[]) hexStrImpl!dchar(hexData);
124 }
125 
126 /// ditto
127 @property @trusted pure
128 auto hexBytes(T = ubyte)(string hexData)
129 if (isIntegral!T && (T.sizeof <= 4))
130 {
131     if (hexData.isHexLiteral)
132     {
133         static if (T.sizeof == 1)
134             return cast(T[]) hexStrImpl!char(hexData);
135         else static if (T.sizeof == 2)
136             return cast(T[]) hexStrImpl!wchar(hexData);
137         else
138             return cast(T[]) hexStrImpl!dchar(hexData);
139     }
140     else assert(0, "Invalid input string format in " ~ __FUNCTION__);
141 }
142 
143 ///
144 unittest
145 {
146     // conversion at compile time
147     auto array1 = hexBytes!"304A314B";
148     assert(array1 == [0x30, 0x4A, 0x31, 0x4B]);
149     // conversion at run time
150     auto arbitraryData = "30 4A 31 4B";
151     auto array2 = hexBytes(arbitraryData);
152     assert(array2 == [0x30, 0x4A, 0x31, 0x4B]);
153 }
154 
155 /*
156     takes a hexadecimal string literal and returns its representation.
157     hexData is granted to be a valid string by the caller.
158     C is granted to be a valid char type by the caller.
159 */
160 @safe nothrow pure
161 private auto hexStrImpl(C)(string hexData)
162 {
163     import std.ascii;
164     C[] result;
165     ubyte chr;
166     size_t cnt;
167     result.length = hexData.length / 2;
168     foreach(c; hexData)
169         if (c.isHexDigit)
170         {
171             if (! (cnt & 1))
172             {
173                 chr = 0;
174                 if (c.isAlpha)
175                     chr += (c.toLower - 'a' + 10) << 4;
176                 else
177                     chr += (c - '0') << 4;
178             }
179             else
180             {
181                 if (c.isAlpha)
182                     chr += (c.toLower - 'a' + 10);
183                 else
184                     chr += (c - '0');
185                 result[cnt / 2] = chr;
186             }
187             ++cnt;
188         }
189     result.length = cnt / 2;
190     return result;
191 }
192 
193 unittest
194 {
195     // compile time
196     assert(hexBytes!"49 4A 4B" == [0x49, 0x4A, 0x4B]);
197     assert(hexBytes!"494A4B" == [0x49, 0x4A, 0x4B]);
198     assert(hexBytes!("494A4B", uint) == [0x49u, 0x4Au, 0x4Bu]);
199     assert(hexBytes!("FF FE FD", byte) == [-1, -2, -3]);
200     assert(hexBytes!("FF FE FD", short) == [255, 254, 253]);
201     // run-time
202     assert(hexBytes("49 4A 4B") == [0x49, 0x4A, 0x4B]);
203 }