Line data Source code
1 : // Copyright (c) 2021-2023 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++ lib 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 221 : int wctombs(char * mb, char32_t wc, size_t len) 54 : { 55 221 : auto verify_length = [&len](size_t required_len) 56 : { 57 221 : if(len < required_len) 58 : { 59 0 : throw std::logic_error("wctombs() called with an output buffer which is too small."); 60 : } 61 221 : }; 62 : 63 221 : 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 220 : if(wc < 0x800) 73 : { 74 0 : verify_length(3); 75 : 76 0 : mb[0] = static_cast<char>((wc >> 6) | 0xC0); 77 0 : mb[1] = (wc & 0x3F) | 0x80; 78 0 : mb[2] = '\0'; 79 0 : return 2; 80 : } 81 : 82 : // avoid encoding the UTF-16 surrogate because those code points do not 83 : // represent characters 84 : // 85 220 : if(wc < 0xD800 || wc > 0xDFFF) 86 : { 87 220 : if(wc < 0x10000) 88 : { 89 21 : verify_length(4); 90 : 91 21 : mb[0] = static_cast<char>((wc >> 12) | 0xE0); 92 21 : mb[1] = ((wc >> 6) & 0x3F) | 0x80; 93 21 : mb[2] = (wc & 0x3F) | 0x80; 94 21 : mb[3] = '\0'; 95 21 : return 3; 96 : } 97 199 : if(wc < 0x110000) 98 : { 99 199 : verify_length(5); 100 : 101 199 : mb[0] = static_cast<char>((wc >> 18) | 0xF0); 102 199 : mb[1] = ((wc >> 12) & 0x3F) | 0x80; 103 199 : mb[2] = ((wc >> 6) & 0x3F) | 0x80; 104 199 : mb[3] = (wc & 0x3F) | 0x80; 105 199 : mb[4] = '\0'; 106 199 : 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 10132 : for(int j(0); j < length; ++j) 212 : { 213 9132 : int value(rand() % 256); 214 9132 : ss << std::setw(2) << std::setfill('0') << std::hex << value; 215 9132 : 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 1 : 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 48 : 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 222 : while(left < 25 || right < 25 || !small_char) 272 : { 273 221 : std::stringstream ss; 274 221 : ss << std::hex << rand(); 275 221 : if((ss.str().length() & 1) != 0) 276 : { 277 25 : ++right; 278 : } 279 : else 280 : { 281 196 : ++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 221 : char32_t invalid(U'\0'); 289 : for(;;) 290 : { 291 222 : invalid = rand() % (0x110000 - 1) + 1; 292 222 : if(!small_char && left > 10 && right > 10) 293 : { 294 1 : invalid = (invalid - 1) % 0x7F + 1; 295 : } 296 202 : if((invalid < 0xD800 || invalid >= 0xE000) // don't try with UTF-16 surrogates 297 424 : && !snapdev::is_hexdigit(invalid)) 298 : { 299 221 : if(invalid < 0x80) 300 : { 301 1 : small_char = true; 302 : } 303 221 : char buf[16]; 304 221 : wctombs(buf, invalid, sizeof(buf)); 305 221 : ss << buf; 306 221 : break; 307 : } 308 1 : } 309 : 310 : // make sure the length is even 311 : // 312 221 : if((ss.str().length() & 1) != 0) 313 : { 314 41 : ss << std::hex << rand() % 16; 315 : } 316 : 317 221 : if(invalid >= 0x80) 318 : { 319 : // in this case the character is not available so we cannot 320 : // just add it cleanly to the exception message 321 : // 322 660 : 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 an hexadecimal digit.")); 327 : } 328 : else 329 : { 330 3 : 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 an hexadecimal digit.")); 337 : } 338 221 : } 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 3003 : 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 an 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