// 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;
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
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
// 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) {
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