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 }