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 }