Line data Source code
1 : // Copyright (c) 2021-2025 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : /** \file
20 : * \brief Verify that the hexadecimal convertions work.
21 : *
22 : * This file implements tests for the hexadecimal to binary and vice
23 : * versa functions.
24 : */
25 :
26 : // self
27 : //
28 : #include <snapdev/hexadecimal_string.h>
29 :
30 : #include "catch_main.h"
31 :
32 :
33 :
34 : // C++
35 : //
36 : #include <iomanip>
37 : #include <set>
38 :
39 :
40 : // last include
41 : //
42 : #include <snapdev/poison.h>
43 :
44 :
45 :
46 : namespace
47 : {
48 :
49 :
50 :
51 : // function stolen from the libutf8 library
52 : // see https://snapwebsites.org/project/libutf8
53 190 : int wctombs(char * mb, char32_t wc, size_t len)
54 : {
55 190 : auto verify_length = [&len](size_t required_len)
56 : {
57 190 : if(len < required_len)
58 : {
59 0 : throw std::logic_error("wctombs() called with an output buffer which is too small.");
60 : }
61 190 : };
62 :
63 190 : if(wc < 0x80)
64 : {
65 1 : verify_length(2);
66 :
67 : /* this will also encode '\0'... */
68 1 : mb[0] = static_cast<char>(wc);
69 1 : mb[1] = '\0';
70 1 : return 1;
71 : }
72 189 : if(wc < 0x800)
73 : {
74 1 : verify_length(3);
75 :
76 1 : mb[0] = static_cast<char>((wc >> 6) | 0xC0);
77 1 : mb[1] = (wc & 0x3F) | 0x80;
78 1 : mb[2] = '\0';
79 1 : return 2;
80 : }
81 :
82 : // avoid encoding the UTF-16 surrogate because those code points do not
83 : // represent characters
84 : //
85 188 : if(wc < 0xD800 || wc > 0xDFFF)
86 : {
87 188 : if(wc < 0x10000)
88 : {
89 14 : verify_length(4);
90 :
91 14 : mb[0] = static_cast<char>((wc >> 12) | 0xE0);
92 14 : mb[1] = ((wc >> 6) & 0x3F) | 0x80;
93 14 : mb[2] = (wc & 0x3F) | 0x80;
94 14 : mb[3] = '\0';
95 14 : return 3;
96 : }
97 174 : if(wc < 0x110000)
98 : {
99 174 : verify_length(5);
100 :
101 174 : mb[0] = static_cast<char>((wc >> 18) | 0xF0);
102 174 : mb[1] = ((wc >> 12) & 0x3F) | 0x80;
103 174 : mb[2] = ((wc >> 6) & 0x3F) | 0x80;
104 174 : mb[3] = (wc & 0x3F) | 0x80;
105 174 : mb[4] = '\0';
106 174 : return 4;
107 : }
108 : }
109 :
110 0 : verify_length(1);
111 :
112 : /* an invalid wide character */
113 0 : mb[0] = '\0';
114 0 : return -1;
115 : }
116 :
117 :
118 : } // no name namespace
119 :
120 :
121 :
122 1 : CATCH_TEST_CASE("hexadecimal_string_hex_digits", "[hexadecimal][string]")
123 : {
124 1 : CATCH_START_SECTION("hexadecimal_string: verify hexadecimal digit detection")
125 : {
126 1114113 : for(int i(0); i < 0x110000; ++i) // all of Unicode
127 : {
128 1114112 : if((i >= '0' && i <= '9')
129 1114102 : || (i >= 'a' && i <= 'f')
130 1114096 : || (i >= 'A' && i <= 'F'))
131 : {
132 22 : CATCH_REQUIRE(snapdev::is_hexdigit(i));
133 22 : }
134 : else
135 : {
136 1114090 : CATCH_REQUIRE_FALSE(snapdev::is_hexdigit(i));
137 : }
138 : }
139 : }
140 1 : CATCH_END_SECTION()
141 1 : }
142 :
143 :
144 4 : CATCH_TEST_CASE("hexadecimal_string_16_bit_values", "[hexadecimal][string]")
145 : {
146 4 : CATCH_START_SECTION("hexadecimal_string: all 16 bit values")
147 : {
148 65537 : for(int i(0); i < 65'536; ++i)
149 : {
150 65536 : std::stringstream ss;
151 65536 : ss << std::hex << i;
152 65536 : std::string value(ss.str());
153 65536 : if((value.length() & 1) != 0)
154 : {
155 3856 : value = "0" + value;
156 : }
157 65536 : std::string bin(snapdev::hex_to_bin(value));
158 65536 : std::string upper;
159 65536 : if(bin.length() == 1)
160 : {
161 256 : CATCH_REQUIRE(i < 256);
162 256 : CATCH_REQUIRE(static_cast<unsigned char>(bin[0]) == static_cast<unsigned char>(i));
163 :
164 256 : CATCH_REQUIRE(snapdev::int_to_hex(i, false) == ss.str());
165 256 : CATCH_REQUIRE(snapdev::int_to_hex(i, false, 2) == value);
166 :
167 256 : CATCH_REQUIRE(snapdev::hex_to_int<int>(ss.str()) == i);
168 256 : CATCH_REQUIRE(snapdev::hex_to_int<int>(value) == i);
169 :
170 256 : upper = snapdev::int_to_hex(i, true, 2);
171 : }
172 : else
173 : {
174 65280 : CATCH_REQUIRE(bin.length() == 2);
175 65280 : CATCH_REQUIRE(static_cast<unsigned char>(bin[0]) == static_cast<unsigned char>(i >> 8));
176 65280 : CATCH_REQUIRE(static_cast<unsigned char>(bin[1]) == static_cast<unsigned char>(i));
177 :
178 65280 : CATCH_REQUIRE(snapdev::int_to_hex(i, false) == ss.str());
179 65280 : CATCH_REQUIRE(snapdev::int_to_hex(i, false, 4) == value);
180 65280 : CATCH_REQUIRE(snapdev::hex_to_int<int>(ss.str()) == i);
181 65280 : CATCH_REQUIRE(snapdev::hex_to_int<int>(value) == i);
182 :
183 65280 : upper = snapdev::int_to_hex(i, true, 4);
184 : }
185 :
186 65536 : CATCH_REQUIRE(snapdev::bin_to_hex(bin) == value);
187 :
188 65536 : std::string bin_from_upper(snapdev::hex_to_bin(upper));
189 65536 : if(bin_from_upper.length() == 1)
190 : {
191 256 : CATCH_REQUIRE(i < 256);
192 256 : CATCH_REQUIRE(static_cast<unsigned char>(bin[0]) == static_cast<unsigned char>(i));
193 : }
194 : else
195 : {
196 65280 : CATCH_REQUIRE(bin.length() == 2);
197 65280 : CATCH_REQUIRE(static_cast<unsigned char>(bin[0]) == static_cast<unsigned char>(i >> 8));
198 65280 : CATCH_REQUIRE(static_cast<unsigned char>(bin[1]) == static_cast<unsigned char>(i));
199 : }
200 65536 : }
201 : }
202 4 : CATCH_END_SECTION()
203 :
204 4 : CATCH_START_SECTION("hexadecimal_string: hex_to_bin & bin_to_hex, large (and small) random numbers")
205 : {
206 1001 : for(int i(0); i < 1'000; ++i)
207 : {
208 1000 : std::stringstream ss;
209 1000 : std::string result;
210 1000 : int const length((rand() % 16 + 2) & -2);
211 9910 : for(int j(0); j < length; ++j)
212 : {
213 8910 : int value(rand() % 256);
214 8910 : ss << std::setw(2) << std::setfill('0') << std::hex << value;
215 8910 : result.push_back(value);
216 : }
217 1000 : CATCH_REQUIRE(snapdev::hex_to_bin(ss.str()) == result);
218 1000 : CATCH_REQUIRE(snapdev::bin_to_hex(result) == ss.str());
219 1000 : }
220 : }
221 4 : CATCH_END_SECTION()
222 :
223 4 : CATCH_START_SECTION("hexadecimal_string: bin_to_hex with empty")
224 : {
225 1 : CATCH_REQUIRE(snapdev::bin_to_hex(std::string()) == std::string());
226 3 : CATCH_REQUIRE(snapdev::bin_to_hex("") == "");
227 : }
228 4 : CATCH_END_SECTION()
229 :
230 4 : CATCH_START_SECTION("hexadecimal_string: large width for int_to_hex()")
231 : {
232 257 : for(int i(0); i < 256; ++i)
233 : {
234 256 : std::uint8_t c(i & 255);
235 256 : std::string const value(snapdev::int_to_hex(c, true, 4)); // 4 when type is at most 2 digits, return 2 digits...
236 256 : std::stringstream ss;
237 256 : ss << std::setw(2) << std::setfill('0') << std::hex << std::uppercase << i;
238 256 : CATCH_REQUIRE(ss.str() == value);
239 256 : }
240 : }
241 4 : CATCH_END_SECTION()
242 4 : }
243 :
244 :
245 3 : CATCH_TEST_CASE("hexadecimal_string_invalid_input", "[hexadecimal][string][error]")
246 : {
247 3 : CATCH_START_SECTION("hexadecimal_string: invalid length")
248 : {
249 17 : for(int i(1); i <= 31; i += 2)
250 : {
251 16 : std::stringstream ss;
252 272 : for(int j(0); j < i; ++j)
253 : {
254 256 : int const value(rand() % 16);
255 256 : ss << std::hex << value;
256 : }
257 96 : CATCH_REQUIRE_THROWS_MATCHES(
258 : snapdev::hex_to_bin(ss.str())
259 : , snapdev::hexadecimal_string_invalid_parameter
260 : , Catch::Matchers::ExceptionMessage(
261 : "hexadecimal_string_exception: the hex parameter must have an even size."));
262 16 : }
263 : }
264 3 : CATCH_END_SECTION()
265 :
266 3 : CATCH_START_SECTION("hexadecimal_string: invalid digits")
267 : {
268 1 : int left(0);
269 1 : int right(0);
270 1 : bool small_char(false);
271 191 : while(left < 25 || right < 25 || !small_char)
272 : {
273 190 : std::stringstream ss;
274 190 : ss << std::hex << rand();
275 190 : if((ss.str().length() & 1) != 0)
276 : {
277 25 : ++right;
278 : }
279 : else
280 : {
281 165 : ++left;
282 : }
283 :
284 : // all of our std::string are considered UTF-8 so add a Unicode
285 : // character even though the hex_to_bin() function really
286 : // only expects ASCII
287 : //
288 190 : char32_t invalid(U'\0');
289 : for(;;)
290 : {
291 190 : invalid = rand() % (0x110000 - 1) + 1;
292 190 : if(!small_char && left > 10 && right > 10)
293 : {
294 1 : invalid = (invalid - 1) % 0x7F + 1;
295 : }
296 175 : if((invalid < 0xD800 || invalid >= 0xE000) // don't try with UTF-16 surrogates
297 365 : && !snapdev::is_hexdigit(invalid))
298 : {
299 190 : if(invalid < 0x80)
300 : {
301 1 : small_char = true;
302 : }
303 190 : char buf[16];
304 190 : wctombs(buf, invalid, sizeof(buf));
305 190 : ss << buf;
306 190 : break;
307 : }
308 0 : }
309 :
310 : // make sure the length is even
311 : //
312 190 : if((ss.str().length() & 1) != 0)
313 : {
314 36 : ss << std::hex << rand() % 16;
315 : }
316 :
317 190 : if(invalid < 0x20 || invalid >= 0x7F)
318 : {
319 : // in this case the character is not available so we cannot
320 : // just add it cleanly to the exception message
321 : //
322 1140 : CATCH_REQUIRE_THROWS_MATCHES(
323 : snapdev::hex_to_bin(ss.str())
324 : , snapdev::hexadecimal_string_invalid_parameter
325 : , Catch::Matchers::ExceptionMessage(
326 : "hexadecimal_string_exception: input character is not a hexadecimal digit."));
327 : }
328 : else
329 : {
330 0 : CATCH_REQUIRE_THROWS_MATCHES(
331 : snapdev::hex_to_bin(ss.str())
332 : , snapdev::hexadecimal_string_invalid_parameter
333 : , Catch::Matchers::ExceptionMessage(
334 : std::string("hexadecimal_string_exception: input character '")
335 : + static_cast<char>(invalid)
336 : + "' is not a hexadecimal digit."));
337 : }
338 190 : }
339 : }
340 3 : CATCH_END_SECTION()
341 :
342 3 : CATCH_START_SECTION("hexadecimal_string: integer too small (overflow)")
343 : {
344 1002 : for(int i(0); i <= 1'000; ++i)
345 : {
346 1001 : std::uint32_t value(rand());
347 1001 : while(value < 256)
348 : {
349 0 : value = rand();
350 : }
351 1001 : std::stringstream ss;
352 1001 : ss << value;
353 5005 : CATCH_REQUIRE_THROWS_MATCHES(
354 : snapdev::hex_to_int<std::uint8_t>(ss.str())
355 : , snapdev::hexadecimal_string_out_of_range
356 : , Catch::Matchers::ExceptionMessage(
357 : "hexadecimal_string_out_of_range: input string has a hexadecimal number which is too large for the output integer type."));
358 1001 : }
359 : }
360 3 : CATCH_END_SECTION()
361 3 : }
362 :
363 :
364 :
365 : // vim: ts=4 sw=4 et
|