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 }