Parcourir la source

[4301] Implemented utility functions to convert from hex strings.

The supported hex strings may have different formats:
- colon separated values,
- with '0x'prefix
- no prefix, no colons
Marcin Siodelski il y a 9 ans
Parent
commit
0b5aab318e
3 fichiers modifiés avec 268 ajouts et 6 suppressions
  1. 97 2
      src/lib/util/strutil.cc
  2. 38 0
      src/lib/util/strutil.h
  3. 133 4
      src/lib/util/tests/strutil_unittest.cc

+ 97 - 2
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 <numeric>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
 
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+#include <numeric>
+#include <sstream>
 #include <string.h>
-#include <util/strutil.h>
+
 
 using namespace std;
 
@@ -152,6 +159,94 @@ quotedStringToBinary(const std::string& quoted_string) {
     return (binary);
 }
 
+void
+decodeColonSeparatedHexString(const std::string& hex_string,
+                              std::vector<uint8_t>& binary) {
+    std::vector<std::string> split_text;
+    boost::split(split_text, hex_string, boost::is_any_of(":"),
+                 boost::algorithm::token_compress_off);
+
+    std::vector<uint8_t> 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<uint8_t>(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<uint8_t>& 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

+ 38 - 0
src/lib/util/strutil.h

@@ -214,6 +214,44 @@ tokenToNum(const std::string& num_token) {
 std::vector<uint8_t>
 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<uint8_t>& 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<uint8_t>& binary);
+
+
 } // namespace str
 } // namespace util
 } // namespace isc

+ 133 - 4
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 <stdint.h>
-
-#include <string>
+#include <exceptions/exceptions.h>
+#include <util/strutil.h>
+#include <util/encode/hex.h>
 
 #include <gtest/gtest.h>
 
-#include <util/strutil.h>
+#include <stdint.h>
+#include <string>
 
 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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