LCOV - code coverage report
Current view: top level - tests - catch_hexadecimal_string.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 95.6 % 160 153
Test Date: 2025-07-03 19:05:49 Functions: 100.0 % 5 5
Legend: Lines: hit not hit

            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
        

Generated by: LCOV version 2.0-1

Snap C++ | List of projects | List of versions