Line data Source code
1 : // Copyright (c) 2011-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 : #pragma once 19 : 20 : /** \file 21 : * \brief Convert binary to a string in hexadecimal. 22 : * 23 : * C++ offers the std::to_string() function which converts numbers to 24 : * decimal. However, we often want to convert strings to hexadecimal and 25 : * using std::stringstream is not as efficient as using these functions. 26 : * 27 : * Here we can convert a buffer of any length to hexadecimal and vice 28 : * versa. You can also use this implementation to covert just one byte. 29 : */ 30 : 31 : // libexcept 32 : // 33 : #include "libexcept/exception.h" 34 : 35 : 36 : // C++ 37 : // 38 : #include <algorithm> 39 : #include <climits> 40 : #include <iomanip> 41 : 42 : 43 : 44 : namespace snapdev 45 : { 46 : 47 : 48 : 49 237 : DECLARE_MAIN_EXCEPTION(hexadecimal_string_exception); 50 : 51 1001 : DECLARE_OUT_OF_RANGE(hexadecimal_string_out_of_range); 52 : 53 237 : DECLARE_EXCEPTION(hexadecimal_string_exception, hexadecimal_string_invalid_parameter); 54 : 55 : 56 : 57 : /** \brief Check whether character is an hexadecimal digit. 58 : * 59 : * This function checks whether the given \p c character represents 60 : * an hexadecimal digit. 61 : * 62 : * The function accepts upper and lower case characters (A-F or a-f) 63 : * as the same set of digits. 64 : * 65 : * \rparam charT The type of character to be tested. 66 : * \param[in] c The character to be tested. 67 : * 68 : * \return true if the the character represents an hexadecimal digit. 69 : */ 70 : template<class charT> 71 1114333 : bool is_hexdigit(charT c) 72 : { 73 1114285 : return (c >= '0' && c <= '9') 74 1114323 : || (c >= 'a' && c <= 'f') 75 2228666 : || (c >= 'A' && c <= 'F'); 76 : } 77 : 78 : 79 : /** \brief Convert one hexadecimal digit to a character. 80 : * 81 : * This function converts an hexadecimal number from 0 to 15 into 82 : * an hexadecimal character: '0' to '9' or 'a' to 'f'. 83 : * 84 : * To get uppercase, simple set the \p uppercase parameter to true. 85 : * 86 : * \param[in] d The hexadecimal digit convert. 87 : * 88 : * \return An hexadecimal character. 89 : */ 90 : template<class intT> 91 279896 : char to_hex(intT d, bool uppercase = false) 92 : { 93 279896 : if(static_cast<typename std::make_unsigned<intT>::type>(d) >= 16) 94 : { 95 0 : throw hexadecimal_string_out_of_range( 96 : "input number (" 97 : + std::to_string(d) 98 : + ") is negative or too large to represent one hexadecimal digit."); 99 : } 100 279896 : return d < 10 ? d + '0' : d + ((uppercase ? 'A' : 'a') - 10); 101 : } 102 : 103 : 104 : /** \brief Convert an hexadecimal character in a number. 105 : * 106 : * This function converts an hexadecimal character that represents a valid 107 : * digit in an integer. 108 : * 109 : * \exception hexadecimal_string_invalid_parameter 110 : * If the input is not a valid hexadecimal character, then this error is 111 : * raised. 112 : * 113 : * \tparam charT The type of the character (char, char32_t, etc.) 114 : * \param[in] c The character to convert to a number. 115 : * 116 : * \return The converted character as an integer. 117 : */ 118 : template<class charT> 119 1064902 : int hexdigit_to_number(charT c) 120 : { 121 1064902 : if(c >= '0' && c <= '9') 122 : { 123 663926 : return c - '0'; 124 : } 125 400976 : if(c >= 'a' && c <= 'f') 126 : { 127 302451 : return c - ('a' - 10); 128 : } 129 98525 : if(c >= 'A' && c <= 'F') 130 : { 131 98304 : return c - ('A' - 10); 132 : } 133 221 : if(static_cast<typename std::make_unsigned<charT>::type>(c) < 0x80) 134 : { 135 3 : throw hexadecimal_string_invalid_parameter( 136 2 : std::string("input character '") 137 : + static_cast<char>(c) 138 : + "' is not an hexadecimal digit."); 139 : } 140 : // if character represents a UTF-8 charater, we do not have all the 141 : // bytes to convert it so just use a plan error message 142 : // 143 220 : throw hexadecimal_string_invalid_parameter( 144 : "input character is not an hexadecimal digit."); 145 : } 146 : 147 : 148 : /** \brief Transform a binary string to hexadecimal. 149 : * 150 : * This function transforms a string of binary bytes (any value from 0x00 151 : * to 0xFF) to a string of hexadecimal digits. 152 : * 153 : * The output string will be exactly 2x the size of the input string. 154 : * 155 : * You can request uppercase instead of lowercase for the letters a to f. 156 : * The default is to use lowercase characters. 157 : * 158 : * \param[in] binary The input binary string to convert. 159 : * \param[in] uppercase If true, use uppercase letters for a-f. 160 : * 161 : * \return The hexademical representation of the input string. 162 : */ 163 66538 : inline std::string bin_to_hex(std::string const & binary, bool uppercase = false) 164 : { 165 66538 : if(binary.empty()) 166 : { 167 2 : return std::string(); 168 : } 169 : 170 66536 : std::string result; 171 : 172 66536 : result.reserve(binary.length() * 2); 173 : 174 66536 : std::for_each( 175 : binary.begin() 176 : , binary.end() 177 279896 : , [&result,uppercase](char const & c) 178 : { 179 139948 : result.push_back(to_hex((c >> 4) & 15, uppercase)); 180 139948 : result.push_back(to_hex(c & 15, uppercase)); 181 139948 : }); 182 : 183 66536 : return result; 184 66536 : } 185 : 186 : 187 : /** \brief Convert an hexadecimal string to a binary string. 188 : * 189 : * This function is the inverse of the bin_to_hex() function. It 190 : * converts a text string of hexadecimal numbers (exactly 2 digits 191 : * each) into a binary string (a string of any bytes from 0x00 to 192 : * 0xFF.) 193 : * 194 : * The output will be exactly half the size of the input. 195 : * 196 : * \note 197 : * The output is saved in a string in big endian format. 198 : * 199 : * \exception hexadecimal_string_invalid_parameter 200 : * If the input string is not considered valid, then this exception is 201 : * raised. To be valid every single character must be an hexadecimal 202 : * digit (0-9, a-f, A-F) and the length of the string must be even. 203 : * 204 : * \param[in] hex The hexadecimal string of characters. 205 : * 206 : * \return The converted value in a binary string. 207 : */ 208 132309 : inline std::string hex_to_bin(std::string const & hex) 209 : { 210 132309 : if((hex.length() & 1) != 0) 211 : { 212 16 : throw hexadecimal_string_invalid_parameter("the hex parameter must have an even size."); 213 : } 214 : 215 132293 : std::string result; 216 : 217 403916 : for(char const * s(hex.c_str()); *s != '\0'; s += 2) 218 : { 219 271844 : char value = static_cast<char>(hexdigit_to_number(s[0]) * 16 220 271648 : + hexdigit_to_number(s[1])); 221 271623 : result.push_back(value); 222 : } 223 : 224 132072 : return result; 225 221 : } 226 : 227 : 228 : /** \brief Transform an integer to a string of hexadecimal digits. 229 : * 230 : * This function transforms an integer to a string of hexadecimal digits. 231 : * 232 : * The output string is optimized to not include any unnecessary leading 233 : * zeroes. 234 : * 235 : * The input value is considered to be positive or zero. Negative numbers 236 : * are viewed as their unsigned corresponding number (i.e. if the input 237 : * is an int32_t is viewed as uint32_t). 238 : * 239 : * \note 240 : * The function does not add an introducer (so no "0x" at the start of 241 : * the resulting string). 242 : * 243 : * \tparam T The type of integer to convert to a string. 244 : * \param[in] value The input integer to convert. 245 : * \param[in] uppercase Whether to use upper (true) or lower (false) case 246 : * letters for the hexadecimal digits A to F. 247 : * \param[in] width The minimum width (prepend '0' to reach this width). 248 : * 249 : * \return The hexademical representation of the input integer. 250 : */ 251 : template<class T> 252 196864 : inline std::string int_to_hex( 253 : T value 254 : , bool uppercase = false 255 : , std::uint32_t width = 1) 256 : { 257 : typedef typename std::make_unsigned<T>::type unsigned_value_t; 258 : 259 196864 : char buf[sizeof(T) * 2 + 1]; 260 196864 : char * d(buf + sizeof(T) * 2); 261 196864 : char const * const e(d); 262 196864 : *d = '\0'; 263 : 264 196864 : char const letter_digit(uppercase ? 'A' - 10 : 'a' - 10); 265 : 266 196864 : for(unsigned_value_t unsigned_value(value); 267 970684 : unsigned_value != 0; 268 773820 : unsigned_value >>= 4) 269 : { 270 773820 : --d; 271 773820 : int const v(unsigned_value & 15); 272 773820 : *d = v < 10 ? v + '0' : v + letter_digit; 273 : } 274 : 275 196864 : if(*d == '\0') 276 : { 277 4 : --d; 278 4 : *d = '0'; 279 : } 280 : 281 196864 : if(width > sizeof(buf) - 1) 282 : { 283 256 : width = sizeof(buf) - 1; 284 : } 285 204592 : while(e - d < width) 286 : { 287 7728 : --d; 288 7728 : *d = '0'; 289 : } 290 : 291 393728 : return std::string(d, e - d); 292 : } 293 : 294 : 295 : /** \brief Convert a string to an integer. 296 : * 297 : * This function converts a string that represents an hexadecimal number 298 : * in an integer. 299 : * 300 : * \exception out_of_range 301 : * If the number is too large for the integer, then this exception is 302 : * raised. 303 : * 304 : * \exception hexadecimal_string_invalid_parameter 305 : * If the input string is not considered valid, then this exception is 306 : * raised. To be valid every single character must be an hexadecimal 307 : * digit (0-9, a-f, A-F) and the length of the string must be even. 308 : * 309 : * \tparam T The type of integer to return. 310 : * \param[in] hex The hexadecimal string to be converted. 311 : * 312 : * \return The converted \p hex parameter as an integer. 313 : */ 314 : template<class T> 315 132073 : T hex_to_int(std::string const & hex) 316 : { 317 132073 : T value(0); 318 653483 : for(auto const c : hex) 319 : { 320 522411 : if((value >> (sizeof(T) * CHAR_BIT - 4)) != 0) 321 : { 322 1001 : throw hexadecimal_string_out_of_range("input string has an hexadecimal number which is too large for the output integer type."); 323 : } 324 521410 : value *= 16; 325 521410 : value += hexdigit_to_number(c); 326 : } 327 131072 : return value; 328 : } 329 : 330 : 331 : } // namespace snapdev 332 : // vim: ts=4 sw=4 et