1 /******************************************************************************* 2 3 Utility functions for converting hash_t <-> hexadecimal strings. 4 5 A few different types of data are handled: 6 * Hex strings: strings of variable length containing valid hexadecimal 7 digits (case insensitive), optionally prepended by the hex radix 8 specifier ("0x") 9 * Hash digests: hex strings of exactly hash_t.sizeof * 2 digits 10 * hash_t 11 12 Copyright: 13 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 14 All rights reserved. 15 16 License: 17 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 18 Alternatively, this file may be distributed under the terms of the Tango 19 3-Clause BSD License (see LICENSE_BSD.txt for details). 20 21 *******************************************************************************/ 22 23 module ocean.text.convert.Hash; 24 25 26 27 28 import ocean.meta.types.Qualifiers; 29 30 import Integer = ocean.text.convert.Integer; 31 32 import ocean.core.TypeConvert; 33 34 static import ocean.text.convert.Hex; 35 36 version (unittest) 37 { 38 import ocean.core.Test; 39 } 40 41 42 /******************************************************************************* 43 44 Constant defining the number of hexadecimal digits needed to represent a 45 hash_t. 46 47 *******************************************************************************/ 48 49 public static immutable HashDigits = hash_t.sizeof * 2; 50 51 52 /******************************************************************************* 53 54 Converts from a hex string to a hash_t. 55 56 Params: 57 str = string to convert 58 hash = output value to receive hex value, only set if conversion 59 succeeds 60 allow_radix = if true, the radix specified "0x" is allowed at the start 61 of str 62 63 Returns: 64 true if the conversion succeeded 65 66 *******************************************************************************/ 67 68 public bool toHashT ( cstring str, out hash_t hash, 69 bool allow_radix = false ) 70 { 71 return ocean.text.convert.Hex.handleRadix(str, allow_radix, 72 ( cstring str ) 73 { 74 return Integer.toUlong(str, hash, 16); 75 }); 76 } 77 78 unittest 79 { 80 static if ( HashDigits == 16 ) 81 { 82 hash_t hash; 83 84 // empty string 85 test!("==")(toHashT("", hash), false); 86 87 // just radix 88 test!("==")(toHashT("0x", hash, true), false); 89 90 // non-hex 91 test!("==")(toHashT("zzz", hash), false); 92 93 // integer overflow 94 test!("==")(toHashT("12345678123456789", hash), false); 95 96 // simple hash 97 toHashT("12345678", hash); 98 test!("==")(hash, 0x12345678); 99 100 // hash with radix, disallowed 101 test!("==")(toHashT("0x12345678", hash), false); 102 103 // hash with radix, allowed 104 toHashT("0x12345678", hash, true); 105 test!("==")(hash, 0x12345678); 106 } 107 else 108 { 109 pragma(msg, "Warning: ocean.text.convert.Hash.toHashT unittest not run in 32-bit"); 110 } 111 } 112 113 114 /******************************************************************************* 115 116 Converts from a hash digest (exactly HashDigits digits) to a hash_t. 117 118 Params: 119 str = string to convert 120 hash = output value to receive hex value, only set if conversion 121 succeeds 122 allow_radix = if true, the radix specified "0x" is allowed at the start 123 of str 124 125 Returns: 126 true if the conversion succeeded 127 128 *******************************************************************************/ 129 130 public bool hashDigestToHashT ( cstring str, out hash_t hash, 131 bool allow_radix = false ) 132 { 133 return ocean.text.convert.Hex.handleRadix(str, allow_radix, 134 ( cstring str ) 135 { 136 if ( str.length != HashDigits ) 137 { 138 return false; 139 } 140 141 return Integer.toUlong(str, hash, 16); 142 }); 143 } 144 145 unittest 146 { 147 static if ( HashDigits == 16 ) 148 { 149 hash_t hash; 150 151 // empty string 152 test!("==")(hashDigestToHashT("", hash), false); 153 154 // just radix 155 test!("==")(hashDigestToHashT("0x", hash, true), false); 156 157 // non-hex 158 test!("==")(hashDigestToHashT("zzz", hash), false); 159 160 // too short 161 test!("==")(hashDigestToHashT("123456781234567", hash), false); 162 163 // too short, with radix 164 test!("==")(hashDigestToHashT("0x" ~ "123456781234567", hash, true), false); 165 166 // too long 167 test!("==")(hashDigestToHashT("12345678123456789", hash), false); 168 169 // too long, with radix 170 test!("==")(hashDigestToHashT("0x12345678123456789", hash, true), false); 171 172 // just right 173 hashDigestToHashT("1234567812345678", hash); 174 test!("==")(hash, 0x1234567812345678); 175 176 // just right with radix, disallowed 177 test!("==")(hashDigestToHashT("0x1234567812345678", hash), false); 178 179 // just right with radix, allowed 180 hashDigestToHashT("0x1234567812345678", hash, true); 181 test!("==")(hash, 0x1234567812345678); 182 } 183 else 184 { 185 pragma(msg, "Warning: ocean.text.convert.Hash.hashDigestToHashT unittest not run in 32-bit"); 186 } 187 } 188 189 190 /******************************************************************************* 191 192 Creates a hash digest string from a hash_t. 193 194 Params: 195 hash = hash_t value to render to string 196 str = destination string; length will be set to HashDigits 197 198 Returns: 199 string containing the hash digest 200 201 *******************************************************************************/ 202 203 public mstring toHashDigest ( hash_t hash, ref mstring str ) 204 { 205 str.length = HashDigits; 206 foreach_reverse ( ref c; str ) 207 { 208 c = "0123456789abcdef"[hash & 0xF]; 209 hash >>= 4; 210 } 211 return str; 212 } 213 214 unittest 215 { 216 mstring str; 217 218 static if ( HashDigits == 16 ) 219 { 220 test!("==")(toHashDigest(hash_t.min, str), "0000000000000000"); 221 test!("==")(toHashDigest(hash_t.max, str), "ffffffffffffffff"); 222 } 223 else 224 { 225 test!("==")(toHashDigest(hash_t.min, str), "00000000"); 226 test!("==")(toHashDigest(hash_t.max, str), "ffffffff"); 227 } 228 } 229 230 231 /******************************************************************************* 232 233 Checks whether str is a hash digest. 234 235 Params: 236 str = string to check 237 allow_radix = if true, the radix specified "0x" is allowed at the start 238 of str 239 240 Returns: 241 true if str is a hash digest 242 243 *******************************************************************************/ 244 245 public bool isHashDigest ( cstring str, bool allow_radix = false ) 246 { 247 return ocean.text.convert.Hex.handleRadix(str, allow_radix, 248 ( cstring str ) 249 { 250 if ( str.length != HashDigits ) 251 { 252 return false; 253 } 254 255 return ocean.text.convert.Hex.isHex(str); 256 }); 257 } 258 259 unittest 260 { 261 static if ( HashDigits == 16 ) 262 { 263 // empty string 264 test!("==")(isHashDigest(""), false); 265 266 // radix only, allowed 267 test!("==")(isHashDigest("0x", true), false); 268 269 // radix only, disallowed 270 test!("==")(isHashDigest("0x"), false); 271 272 // too short 273 test!("==")(isHashDigest("123456781234567"), false); 274 275 // too short, with radix 276 test!("==")(isHashDigest("0x" ~ "123456781234567", true), false); 277 278 // too long 279 test!("==")(isHashDigest("12345678123456789"), false); 280 281 // too long, with radix 282 test!("==")(isHashDigest("0x" ~ "12345678123456789", true), false); 283 284 // just right 285 test!("==")(isHashDigest("1234567812345678"), true); 286 287 // just right, with radix 288 test!("==")(isHashDigest("0x1234567812345678", true), true); 289 } 290 else 291 { 292 pragma(msg, "Warning: ocean.text.convert.Hash.isHashDigest unittest not run in 32-bit"); 293 } 294 }