Commit 0b5aab31 authored by Marcin Siodelski's avatar Marcin Siodelski

[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
parent 3c6c6734
......@@ -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
......@@ -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
......
......@@ -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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment