1 /*******************************************************************************
2 
3     Utility functions for converting hexadecimal strings.
4 
5     Copyright:
6         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
7         All rights reserved.
8 
9     License:
10         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11         Alternatively, this file may be distributed under the terms of the Tango
12         3-Clause BSD License (see LICENSE_BSD.txt for details).
13 
14 *******************************************************************************/
15 
16 module ocean.text.convert.Hex;
17 
18 
19 
20 
21 import ocean.meta.types.Qualifiers;
22 
23 import Integer = ocean.text.convert.Integer_tango;
24 
25 import ocean.core.TypeConvert;
26 
27 version (unittest)
28 {
29     import ocean.core.Test;
30 }
31 
32 /*******************************************************************************
33 
34     Checks whether str is a hex string (contains only valid hex digits),
35     optionally with radix specifier ("0x").
36 
37     Params:
38         str = string to check
39         allow_radix = if true, the radix specified "0x" is allowed at the start
40             of str
41 
42     Returns:
43         true if str is a hex string
44 
45 *******************************************************************************/
46 
47 public bool isHex ( cstring str, bool allow_radix = false )
48 {
49     return handleRadix(str, allow_radix,
50         ( cstring str )
51         {
52             foreach ( c; str )
53             {
54                 if ( !isHex(c) )
55                 {
56                     return false;
57                 }
58             }
59             return true;
60         });
61 }
62 
63 unittest
64 {
65     // empty string
66     test!("==")(isHex(""), true);
67 
68     // radix only, allowed
69     test!("==")(isHex("0x", true), true);
70 
71     // radix only, disallowed
72     test!("==")(isHex("0x"), false);
73 
74     // non-hex
75     test!("==")(isHex("zzz"), false);
76 
77     // simple hex
78     test!("==")(isHex("1234567890abcdef"), true);
79 
80     // simple hex, upper case
81     test!("==")(isHex("1234567890ABCDEF"), true);
82 
83     // simple hex with radix, allowed
84     test!("==")(isHex("0x1234567890abcdef", true), true);
85 
86     // simple hex with radix, disallowed
87     test!("==")(isHex("0x1234567890abcdef"), false);
88 }
89 
90 
91 /*******************************************************************************
92 
93     Checks whether a character is a valid hexadecimal digit.
94 
95     Params:
96         c = character to check
97 
98     Returns:
99         true if the character is a valid hex digit, false otherwise
100 
101 *******************************************************************************/
102 
103 public bool isHex ( char c )
104 {
105     return (c >= '0' && c <= '9')
106         || (c >= 'a' && c <= 'f')
107         || (c >= 'A' && c <= 'F');
108 }
109 
110 unittest
111 {
112     bool contains ( cstring str, char c )
113     {
114         foreach ( cc; str )
115         {
116             if ( cc == c )
117             {
118                 return true;
119             }
120         }
121         return false;
122     }
123 
124     istring good = "0123456789abcdefABCDEF";
125 
126     for ( int i = char.min; i <= char.max; i++ )
127     {
128         // can't use char for i because of expected overflow
129         auto c = castFrom!(int).to!(char)(i);
130         if ( contains(good, c) )
131         {
132             test!("==")(isHex(c), true);
133         }
134         else
135         {
136             test!("==")(!isHex(c), true);
137         }
138     }
139 }
140 
141 
142 /*******************************************************************************
143 
144     Converts any characters in the range A..F in a hex string to lower case
145     (a..f).
146 
147     Params:
148         str = string to convert
149 
150     Returns:
151         converted string (characters modified in-place)
152 
153 *******************************************************************************/
154 
155 public mstring hexToLower ( mstring str )
156 {
157     static immutable to_lower = ('A' - 'a');
158     foreach ( ref c; str )
159     {
160         if ( c >= 'A' && c <= 'F' )
161         {
162             c -= to_lower;
163         }
164     }
165 
166     return str;
167 }
168 
169 unittest
170 {
171     // empty string
172     test!("==")(hexToLower(null), "");
173 
174     // numbers only
175     test!("==")(hexToLower("123456678".dup), "123456678");
176 
177     // lower case letters
178     test!("==")(hexToLower("abcdef".dup), "abcdef");
179 
180     // upper case letters
181     test!("==")(hexToLower("ABCDEF".dup), "abcdef");
182 
183     // non-hex letters, lower case
184     test!("==")(hexToLower("uvwxyz".dup), "uvwxyz");
185 
186     // non-hex letters, upper case
187     test!("==")(hexToLower("UVWXYZ".dup), "UVWXYZ");
188 
189     // mixed
190     test!("==")(hexToLower("12345678abcdefABCDEFUVWXYZ".dup), "12345678abcdefabcdefUVWXYZ");
191 
192     // check that string is modified in-place
193     mstring str = "ABCDEF".dup;
194     auto converted = hexToLower(str);
195     test!("==")(converted.ptr, str.ptr);
196 }
197 
198 
199 /*******************************************************************************
200 
201     Checks whether the radix in str (if present) matches the allow_radix flag,
202     and passes the radix-stripped string to the provided delegate.
203 
204     Params:
205         str = string to convert
206         allow_radix = if true, the radix specified "0x" is allowed at the start
207             of str
208         process = process to perform on string if radix is as expected
209 
210     Returns:
211         if str starts with "0x" and allow_radix is false, returns false
212         otherwise, passes on the return value of the process delegate
213 
214 *******************************************************************************/
215 
216 package bool handleRadix ( cstring str, bool allow_radix,
217     scope bool delegate ( cstring ) process )
218 {
219     if ( str.length >= 2 && str[0..2] == "0x" )
220     {
221         if ( !allow_radix )
222         {
223             return false;
224         }
225         else
226         {
227             str = str[2..$];
228         }
229     }
230 
231     return process(str);
232 }
233 
234 
235 /*********************************************************************
236 
237     Convert a string of hex digits to a byte array. This is only useful
238     for RT strings. If one needs to do this with literals, x"FF FF" is
239     a better approach.
240 
241     Params:
242         str = the string of hex digits to be converted
243         buf = a byte array where the values will be stored
244 
245     Returns:
246         true if conversion succeeded, false if the length of the
247         string is odd, or any of the characters is not valid hex digit.
248 
249  *********************************************************************/
250 
251 public bool hexToBin (cstring str, ref ubyte[] buf)
252 {
253     static uint digit_for_character (char c)
254     {
255         if ( c >= '0' && c <= '9' )
256               return c - '0';
257         else if ( c >= 'a' && c <= 'f' )
258               return c - 'a' + 10;
259         else if ( c >= 'A' && c <= 'F' )
260               return c - 'A' + 10;
261 
262         // c is guaranteed to be a valid hex string
263         // by the caller
264         assert (false);
265     }
266 
267     if (str.length % 2 != 0)
268     {
269         return false;
270     }
271 
272     if (!isHex(str))
273     {
274         return false;
275     }
276 
277     buf.length = str.length / 2;
278 
279     foreach (i, ref b; buf)
280     {
281         auto j = i * 2;
282 
283         b = cast(ubyte)(((digit_for_character(str[j]) & 0x0F) << 4) |
284                        (digit_for_character(str[j + 1]) & 0x0F));
285     }
286 
287     return true;
288 }
289 
290 unittest
291 {
292     ubyte[] arr;
293     test!("==")(hexToBin("FFFF", arr), true);
294     test!("==")(arr, cast(ubyte[])[255, 255]);
295     arr.length = 0;
296     assumeSafeAppend(arr);
297 
298     test!("==")(hexToBin("0000", arr), true);
299     test!("==")(arr, cast(ubyte[])[0, 0]);
300     arr.length = 0;
301     assumeSafeAppend(arr);
302 
303     test!("==")(hexToBin("", arr), true);
304     test!("==")(arr, cast(ubyte[])[]);
305     arr.length = 0;
306     assumeSafeAppend(arr);
307 
308     test!("==")(hexToBin("FFF", arr), false);
309     arr.length = 0;
310     assumeSafeAppend(arr);
311 
312     test!("==")(hexToBin("(FFF", arr), false);
313     arr.length = 0;
314     assumeSafeAppend(arr);
315 }