diff --git a/src/lib/util/strutil.cc b/src/lib/util/strutil.cc index 0bcdea154007868f9f7f73e4056a5634c73b8525..208d1583469de877224eb0274db2e6e4410b61a9 100644 --- a/src/lib/util/strutil.cc +++ b/src/lib/util/strutil.cc @@ -4,10 +4,17 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -#include +#include +#include +#include +#include +#include + +#include +#include #include -#include + using namespace std; @@ -152,6 +159,94 @@ quotedStringToBinary(const std::string& quoted_string) { return (binary); } +void +decodeColonSeparatedHexString(const std::string& hex_string, + std::vector& binary) { + std::vector split_text; + boost::split(split_text, hex_string, boost::is_any_of(":"), + boost::algorithm::token_compress_off); + + std::vector binary_vec; + for (size_t i = 0; i < split_text.size(); ++i) { + + // If there are multiple tokens and the current one is empty, it + // means that two consecutive colons were specified. This is not + // allowed. + if ((split_text.size() > 1) && split_text[i].empty()) { + isc_throw(isc::BadValue, "two consecutive colons specified in" + " a decoded string '" << hex_string << "'"); + + // Between a colon we expect at most two characters. + } else if (split_text[i].size() > 2) { + isc_throw(isc::BadValue, "invalid format of the decoded string" + << " '" << hex_string << "'"); + + } else if (!split_text[i].empty()) { + std::stringstream s; + s << "0x"; + + for (unsigned int j = 0; j < split_text[i].length(); ++j) { + // Check if we're dealing with hexadecimal digit. + if (!isxdigit(split_text[i][j])) { + isc_throw(isc::BadValue, "'" << split_text[i][j] + << "' is not a valid hexadecimal digit in" + << " decoded string '" << hex_string << "'"); + } + s << split_text[i][j]; + } + + // The stream should now have one or two hexadecimal digits. + // Let's convert it to a number and store in a temporary + // vector. + unsigned int binary_value; + s >> std::hex >> binary_value; + + binary_vec.push_back(static_cast(binary_value)); + } + + } + + // All ok, replace the data in the output vector with a result. + binary.swap(binary_vec); +} + +void +decodeFormattedHexString(const std::string& hex_string, + std::vector& binary) { + // If there is at least one colon we assume that the string + // comprises octets separated by colons (e.g. MAC address notation). + if (hex_string.find(':') != std::string::npos) { + decodeColonSeparatedHexString(hex_string, binary); + + } else { + std::ostringstream s; + + // If we have odd number of digits we'll have to prepend '0'. + if (hex_string.length() % 2 != 0) { + s << "0"; + } + + // It is ok to use '0x' prefix in a string. + if ((hex_string.length() > 2) && (hex_string.substr(0, 2) == "0x")) { + // Exclude '0x' from the decoded string. + s << hex_string.substr(2); + + } else { + // No '0x', so decode the whole string. + s << hex_string; + } + + try { + // Decode the hex string. + encode::decodeHex(s.str(), binary); + + } catch (...) { + isc_throw(isc::BadValue, "'" << hex_string << "' is not a valid" + " string of hexadecimal digits"); + } + } +} + } // namespace str } // namespace util } // namespace isc diff --git a/src/lib/util/strutil.h b/src/lib/util/strutil.h index 0c05ae2c4169a5ddf0ed6dfa8d2c5f2c0ea90e7a..bacc828de591a2988a70222d0e56d26c57f0a79a 100644 --- a/src/lib/util/strutil.h +++ b/src/lib/util/strutil.h @@ -214,6 +214,44 @@ tokenToNum(const std::string& num_token) { std::vector quotedStringToBinary(const std::string& quoted_string); +/// \brief Converts a string of hexadecimal digits with colons into +/// a vector. +/// +/// This function supports the following formats: +/// - yy:yy:yy:yy:yy +/// - y:y:y:y:y +/// - y:yy:yy:y:y +/// +/// If the decoded string doesn't match any of the supported formats, +/// an exception is thrown. +/// +/// \param hex_string Input string. +/// \param binary Vector receiving converted string into binary. +/// \throw isc::BadValue if the format of the input string is invalid. +void +decodeColonSeparatedHexString(const std::string& hex_string, + std::vector& binary); + +/// \brief Converts a formatted string of hexadecimal digits into +/// a vector. +/// +/// This function supports formats supported by +/// @ref decodeColonSeparatedHexString and the following additional +/// formats: +/// - yyyyyyyyyy +/// - 0xyyyyyyyyyy +/// +/// If there is an odd number of hexadecimal digits in the input +/// string, the '0' is prepended to the string before decoding. +/// +/// \param hex_string Input string. +/// \param binary Vector receiving converted string into binary. +/// \throw isc::BadValue if the format of the input string is invalid. +void +decodeFormattedHexString(const std::string& hex_string, + std::vector& binary); + + } // namespace str } // namespace util } // namespace isc diff --git a/src/lib/util/tests/strutil_unittest.cc b/src/lib/util/tests/strutil_unittest.cc index b99d2a120be74ae5a9a13d123c19b2951c7590a2..887057d38e8b81dbea880703bb29a0fe3f1bfee4 100644 --- a/src/lib/util/tests/strutil_unittest.cc +++ b/src/lib/util/tests/strutil_unittest.cc @@ -4,18 +4,22 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -#include - -#include +#include +#include +#include #include -#include +#include +#include using namespace isc; using namespace isc::util; +using namespace isc::util::str; using namespace std; +namespace { + // Check for slash replacement TEST(StringUtilTest, Slash) { @@ -297,3 +301,128 @@ TEST(StringUtilTest, quotedStringToBinary) { EXPECT_EQ("'", testQuoted("'''")); EXPECT_EQ("''", testQuoted("''''")); } + +/// @brief Test that hex string with colons can be decoded. +/// +/// @param input Input string to be decoded. +/// @param reference A string without colons representing the +/// decoded data. +void testColonSeparated(const std::string& input, + const std::string& reference) { + // Create a reference vector. + std::vector reference_vector; + ASSERT_NO_THROW(encode::decodeHex(reference, reference_vector)); + + // Fill the output vector with some garbage to make sure that + // the data is erased when a string is decoded successfully. + std::vector decoded(1, 10); + ASSERT_NO_THROW(decodeColonSeparatedHexString(input, decoded)); + + // Get the string representation of the decoded data for logging + // purposes. + std::string encoded; + ASSERT_NO_THROW(encoded = encode::encodeHex(decoded)); + + // Check if the decoded data matches the reference. + EXPECT_TRUE(decoded == reference_vector) + << "decoded data don't match the reference, input='" + << input << "', reference='" << reference << "'" + ", decoded='" << encoded << "'"; +} + +TEST(StringUtilTest, decodeColonSeparatedHexString) { + // Test valid strings. + testColonSeparated("A1:02:C3:d4:e5:F6", "A102C3D4E5F6"); + testColonSeparated("A:02:3:d:E5:F6", "0A02030DE5F6"); + testColonSeparated("A:B:C:D", "0A0B0C0D"); + testColonSeparated("1", "01"); + testColonSeparated("1e", "1E"); + testColonSeparated("", ""); + + // Test invalid strings. + std::vector decoded; + // Whitespaces. + EXPECT_THROW(decodeColonSeparatedHexString(" ", decoded), + isc::BadValue); + // Whitespace before digits. + EXPECT_THROW(decodeColonSeparatedHexString(" A1", decoded), + isc::BadValue); + // Two consecutive colons. + EXPECT_THROW(decodeColonSeparatedHexString("A::01", decoded), + isc::BadValue); + // Three consecutive colons. + EXPECT_THROW(decodeColonSeparatedHexString("A:::01", decoded), + isc::BadValue); + // Whitespace within a string. + EXPECT_THROW(decodeColonSeparatedHexString("A :01", decoded), + isc::BadValue); + // Terminating colon. + EXPECT_THROW(decodeColonSeparatedHexString("0A:01:", decoded), + isc::BadValue); + // Opening colon. + EXPECT_THROW(decodeColonSeparatedHexString(":0A:01", decoded), + isc::BadValue); + // Three digits before the colon. + EXPECT_THROW(decodeColonSeparatedHexString("0A1:B1", decoded), + isc::BadValue); +} + +void testFormatted(const std::string& input, + const std::string& reference) { + // Create a reference vector. + std::vector reference_vector; + ASSERT_NO_THROW(encode::decodeHex(reference, reference_vector)); + + // Fill the output vector with some garbage to make sure that + // the data is erased when a string is decoded successfully. + std::vector decoded(1, 10); + ASSERT_NO_THROW(decodeFormattedHexString(input, decoded)); + + // Get the string representation of the decoded data for logging + // purposes. + std::string encoded; + ASSERT_NO_THROW(encoded = encode::encodeHex(decoded)); + + // Check if the decoded data matches the reference. + EXPECT_TRUE(decoded == reference_vector) + << "decoded data don't match the reference, input='" + << input << "', reference='" << reference << "'" + ", decoded='" << encoded << "'"; +} + +TEST(StringUtilTest, decodeFormattedHexString) { + // Colon separated. + testFormatted("1:A7:B5:4:23", "01A7B50423"); + // No colons, even number of digits. + testFormatted("17a534", "17A534"); + // Odd number of digits. + testFormatted("A3A6f78", "0A3A6F78"); + // '0x' prefix. + testFormatted("0xA3A6f78", "0A3A6F78"); + // '0x' prefix with a special value of 0. + testFormatted("0x0", "00"); + // Empty string. + testFormatted("", ""); + + std::vector decoded; + // Whitepspace. + EXPECT_THROW(decodeFormattedHexString("0a ", decoded), + isc::BadValue); + // Whitespace within a string. + EXPECT_THROW(decodeFormattedHexString("01 02", decoded), + isc::BadValue); + // '0x' prefix and colons. + EXPECT_THROW(decodeFormattedHexString("0x01:02", decoded), + isc::BadValue); + // Missing colon. + EXPECT_THROW(decodeFormattedHexString("01:0203", decoded), + isc::BadValue); + // Invalid prefix. + EXPECT_THROW(decodeFormattedHexString("x0102", decoded), + isc::BadValue); + // Invalid prefix again. + EXPECT_THROW(decodeFormattedHexString("1x0102", decoded), + isc::BadValue); +} + +} // end of anonymous namespace