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 }