summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrancis Dupont <fdupont@isc.org>2017-02-15 01:21:02 +0100
committerFrancis Dupont <fdupont@isc.org>2017-02-15 01:21:02 +0100
commit2f1e796a2c0d48de239184e73ac841d68461d988 (patch)
treedc21b9439c6f5d1044c756fc882c4245af6b5eda
parent[4070] Began tuple code (diff)
downloadkea-2f1e796a2c0d48de239184e73ac841d68461d988.tar.xz
kea-2f1e796a2c0d48de239184e73ac841d68461d988.zip
[4070] Added OpaqueDataTuple read/write, updated defintions and unit tests
-rw-r--r--src/lib/dhcp/option_custom.cc24
-rw-r--r--src/lib/dhcp/option_custom.h19
-rw-r--r--src/lib/dhcp/option_data_types.cc47
-rw-r--r--src/lib/dhcp/option_data_types.h17
-rw-r--r--src/lib/dhcp/option_definition.cc42
-rw-r--r--src/lib/dhcp/option_definition.h26
-rw-r--r--src/lib/dhcp/std_option_defs.h4
-rw-r--r--src/lib/dhcp/tests/option_custom_unittest.cc306
-rw-r--r--src/lib/dhcp/tests/option_data_types_unittest.cc54
-rw-r--r--src/lib/dhcp/tests/option_definition_unittest.cc209
10 files changed, 720 insertions, 28 deletions
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index ed921b18ee..bb45e3bb03 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -76,6 +76,15 @@ OptionCustom::addArrayDataField(const std::string& value) {
}
void
+OptionCustom::addArrayDataField(const OpaqueDataTuple& value) {
+ checkArrayType();
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeTuple(value, buf);
+ buffers_.push_back(buf);
+}
+
+void
OptionCustom::addArrayDataField(const bool value) {
checkArrayType();
@@ -560,6 +569,13 @@ OptionCustom::readTuple(const uint32_t index) const {
}
void
+OptionCustom::readTuple(OpaqueDataTuple& tuple,
+ const uint32_t index) const {
+ checkIndex(index);
+ OptionDataTypeUtil::readTuple(buffers_[index], tuple);
+}
+
+void
OptionCustom::writeTuple(const std::string& value, const uint32_t index) {
checkIndex(index);
@@ -569,6 +585,14 @@ OptionCustom::writeTuple(const std::string& value, const uint32_t index) {
OptionDataTypeUtil::writeTuple(value, lft, buffers_[index]);
}
+void
+OptionCustom::writeTuple(const OpaqueDataTuple& value, const uint32_t index) {
+ checkIndex(index);
+
+ buffers_[index].clear();
+ OptionDataTypeUtil::writeTuple(value, buffers_[index]);
+}
+
bool
OptionCustom::readBoolean(const uint32_t index) const {
checkIndex(index);
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index 4329e48bd4..14ab22916d 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.h
@@ -118,6 +118,11 @@ public:
/// @param value value to be stored as a tuple in the created buffer.
void addArrayDataField(const std::string& value);
+ /// @brief Create new buffer and store tuple value in it
+ ///
+ /// @param value value to be stored as a tuple in the created buffer.
+ void addArrayDataField(const OpaqueDataTuple& value);
+
/// @brief Create new buffer and store variable length prefix in it.
///
/// @param prefix_len Prefix length.
@@ -176,12 +181,26 @@ public:
/// @return string read from a buffer.
std::string readTuple(const uint32_t index = 0) const;
+ /// @brief Read a buffer into a length and string tuple.
+ ///
+ /// @param tuple tuple to fill.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void readTuple(OpaqueDataTuple& tuple, const uint32_t index = 0) const;
+
/// @brief Write a length and string tuple into a buffer.
///
/// @param value value to be written.
/// @param index buffer index.
void writeTuple(const std::string& value, const uint32_t index = 0);
+ /// @brief Write a length and string tuple into a buffer.
+ ///
+ /// @param value value to be written.
+ /// @param index buffer index.
+ void writeTuple(const OpaqueDataTuple& value, const uint32_t index = 0);
+
/// @brief Read a buffer as boolean value.
///
/// @param index buffer index.
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
index 2d2e232ff9..ff3a6ae19b 100644
--- a/src/lib/dhcp/option_data_types.cc
+++ b/src/lib/dhcp/option_data_types.cc
@@ -216,9 +216,19 @@ OptionDataTypeUtil::readTuple(const std::vector<uint8_t>& buf,
}
void
-OptionDataTypeUtil:: writeTuple(const std::string& value,
- OpaqueDataTuple::LengthFieldType lengthfieldtype,
- std::vector<uint8_t>& buf) {
+OptionDataTypeUtil::readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple& tuple) {
+ try {
+ tuple.unpack(buf.begin(), buf.end());
+ } catch (const OpaqueDataTupleError& ex) {
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writeTuple(const std::string& value,
+ OpaqueDataTuple::LengthFieldType lengthfieldtype,
+ std::vector<uint8_t>& buf) {
if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) {
if (value.size() > std::numeric_limits<uint8_t>::max()) {
isc_throw(BadDataTypeCast, "invalid tuple value (size "
@@ -244,6 +254,37 @@ OptionDataTypeUtil:: writeTuple(const std::string& value,
buf.insert(buf.end(), value.begin(), value.end());
}
+void
+OptionDataTypeUtil::writeTuple(const OpaqueDataTuple& tuple,
+ std::vector<uint8_t>& buf) {
+ if (tuple.getLength() == 0) {
+ isc_throw(BadDataTypeCast, "invalid empty tuple value");
+ }
+ if (tuple.getLengthFieldType() == OpaqueDataTuple::LENGTH_1_BYTE) {
+ if (tuple.getLength() > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << tuple.getLength() << " larger than "
+ << std::numeric_limits<uint8_t>::max() << ")");
+ }
+ buf.push_back(static_cast<uint8_t>(tuple.getLength()));
+
+ } else if (tuple.getLengthFieldType() == OpaqueDataTuple::LENGTH_2_BYTES) {
+ if (tuple.getLength() > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << tuple.getLength() << " larger than "
+ << std::numeric_limits<uint16_t>::max() << ")");
+ }
+ buf.resize(buf.size() + 2);
+ isc::util::writeUint16(static_cast<uint16_t>(tuple.getLength()),
+ &buf[buf.size() - 2], 2);
+ } else {
+ isc_throw(BadDataTypeCast, "unable to write data to the buffer as"
+ << " tuple. Invalid length type field: "
+ << tuple.getLengthFieldType());
+ }
+ buf.insert(buf.end(), tuple.getData().begin(), tuple.getData().end());
+}
+
bool
OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
if (buf.empty()) {
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
index 6962e08abe..ffc6669dcb 100644
--- a/src/lib/dhcp/option_data_types.h
+++ b/src/lib/dhcp/option_data_types.h
@@ -394,6 +394,16 @@ public:
static std::string readTuple(const std::vector<uint8_t>& buf,
OpaqueDataTuple::LengthFieldType lengthfieldtype);
+ /// @brief Read length and string tuple from a buffer.
+ ///
+ /// @param buf input buffer.
+ /// @param tuple reference of the tuple to read into
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated.
+ /// @return tuple being read.
+ static void readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple& tuple);
+
/// @brief Append length and string tuple to a buffer
///
/// @param value length and string tuple
@@ -403,6 +413,13 @@ public:
OpaqueDataTuple::LengthFieldType lengthfieldtype,
std::vector<uint8_t>& buf);
+ /// @brief Append length and string tuple to a buffer
+ ///
+ /// @param tuple length and string tuple
+ /// @param [out] buf output buffer.
+ static void writeTuple(const OpaqueDataTuple& tuple,
+ std::vector<uint8_t>& buf);
+
/// @brief Read boolean value from a buffer.
///
/// @param buf input buffer.
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index f657511ce4..d55cdc9aac 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -205,6 +205,14 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
case OPT_STRING_TYPE:
return (OptionPtr(new OptionString(u, type, begin, end)));
+ case OPT_TUPLE_TYPE:
+ // Handle array type only here (see comments for
+ // OPT_IPV4_ADDRESS_TYPE case).
+ if (array_type_) {
+ return (factoryOpaqueDataTuples(u, type, begin, end));
+ }
+ break;
+
default:
// Do nothing. We will return generic option a few lines down.
;
@@ -231,11 +239,11 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
isc_throw(InvalidOptionValue, "no option value specified");
}
} else {
- writeToBuffer(util::str::trim(values[0]), type_, buf);
+ writeToBuffer(u, util::str::trim(values[0]), type_, buf);
}
} else if (array_type_ && type_ != OPT_RECORD_TYPE) {
for (size_t i = 0; i < values.size(); ++i) {
- writeToBuffer(util::str::trim(values[i]), type_, buf);
+ writeToBuffer(u, util::str::trim(values[i]), type_, buf);
}
} else if (type_ == OPT_RECORD_TYPE) {
const RecordFieldsCollection& records = getRecordFields();
@@ -245,8 +253,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
<< " of values provided.");
}
for (size_t i = 0; i < records.size(); ++i) {
- writeToBuffer(util::str::trim(values[i]),
- records[i], buf);
+ writeToBuffer(u, util::str::trim(values[i]), records[i], buf);
}
}
return (optionFactory(u, type, buf.begin(), buf.end()));
@@ -433,7 +440,7 @@ OptionDefinition::haveStatusCodeFormat() const {
bool
OptionDefinition::haveOpaqueDataTuplesFormat() const {
- return (getType() == OPT_BINARY_TYPE);
+ return (haveType(OPT_TUPLE_TYPE) && getArrayType());
}
bool
@@ -507,7 +514,8 @@ OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
}
void
-OptionDefinition::writeToBuffer(const std::string& value,
+OptionDefinition::writeToBuffer(Option::Universe u,
+ const std::string& value,
const OptionDataType type,
OptionBuffer& buf) const {
// We are going to write value given by value argument to the buffer.
@@ -653,6 +661,13 @@ OptionDefinition::writeToBuffer(const std::string& value,
case OPT_FQDN_TYPE:
OptionDataTypeUtil::writeFqdn(value, buf);
return;
+ case OPT_TUPLE_TYPE:
+ {
+ OpaqueDataTuple::LengthFieldType lft = u == Option::V4 ?
+ OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES;
+ OptionDataTypeUtil::writeTuple(value, lft, buf);
+ return;
+ }
default:
// We hit this point because invalid option data type has been specified
// This may be the case because 'empty' or 'record' data type has been
@@ -740,6 +755,17 @@ OptionDefinition::factoryIAPrefix6(uint16_t type,
}
OptionPtr
+OptionDefinition::factoryOpaqueDataTuples(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ boost::shared_ptr<OptionOpaqueDataTuples>
+ option(new OptionOpaqueDataTuples(u, type, begin, end));
+
+ return (option);
+}
+
+OptionPtr
OptionDefinition::factorySpecialFormatOption(Option::Universe u,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
@@ -778,7 +804,7 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u,
return (OptionPtr(new Option6StatusCode(begin, end)));
} else if (getCode() == D6O_BOOTFILE_PARAM && haveOpaqueDataTuplesFormat()) {
// Bootfile params (option code 60)
- return (OptionPtr(new OptionOpaqueDataTuples(Option::V6, getCode(), begin, end)));
+ return (factoryOpaqueDataTuples(Option::V6, getCode(), begin, end));
} else if ((getCode() == D6O_PD_EXCLUDE) && haveType(OPT_IPV6_PREFIX_TYPE)) {
// Prefix Exclude (option code 67)
return (OptionPtr(new Option6PDExclude(begin, end)));
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 63d9834d18..711ec63fbd 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -85,7 +85,9 @@ class OptionIntArray;
/// value. For example, DHCPv6 option 8 comprises a two-byte option code, a
/// two-byte option length and two-byte field that carries a uint16 value
/// (RFC 3315 - http://ietf.org/rfc/rfc3315.txt). In such a case, the option
-/// type is defined as "uint16".
+/// type is defined as "uint16". Length and string tuples are a length
+/// on one (DHCPv4) or two (DHCPv6) bytes followed by a string of
+/// the given length.
///
/// When the option has a more complex structure, the option type may be
/// defined as "array", "record" or even "array of records".
@@ -123,6 +125,7 @@ class OptionIntArray;
/// - "psid" (PSID length / value)
/// - "string"
/// - "fqdn" (fully qualified name)
+/// - "tuple" (length and string)
/// - "record" (set of data fields of different types)
///
/// @todo Extend the comment to describe "generic factories".
@@ -518,6 +521,20 @@ public:
OptionBufferConstIter begin,
OptionBufferConstIter end);
+ /// @brief Factory to create option with tuple list.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param begin iterator pointing to the beginning of the buffer
+ /// with a list of tuples.
+ /// @param end iterator pointing to the end of the buffer with
+ /// a list of tuples.
+ ///
+ /// @return instance of the DHCP option.
+ static OptionPtr factoryOpaqueDataTuples(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
/// @brief Factory function to create option with integer value.
///
/// @param u universe (V4 or V6).
@@ -644,13 +661,14 @@ private:
/// if it is successful it will store the data in the buffer
/// in a binary format.
///
+ /// @param u option universe (V4 or V6).
/// @param value string representation of the value to be written.
/// @param type the actual data type to be stored.
/// @param [in, out] buf buffer where the value is to be stored.
///
/// @throw BadDataTypeCast if data write was unsuccessful.
- void writeToBuffer(const std::string& value, const OptionDataType type,
- OptionBuffer& buf) const;
+ void writeToBuffer(Option::Universe u, const std::string& value,
+ const OptionDataType type, OptionBuffer& buf) const;
/// Option name.
std::string name_;
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index 457d00b61b..1d28e9e163 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -355,7 +355,7 @@ const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = {
{ "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
NO_RECORD_DEF, "" },
{ "bootfile-url", D6O_BOOTFILE_URL, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
- { "bootfile-param", D6O_BOOTFILE_PARAM, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "bootfile-param", D6O_BOOTFILE_PARAM, OPT_TUPLE_TYPE, true, NO_RECORD_DEF, "" },
{ "client-arch-type", D6O_CLIENT_ARCH_TYPE, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
{ "nii", D6O_NII, OPT_RECORD_TYPE, false, RECORD_DEF(CLIENT_NII_RECORDS), "" },
{ "erp-local-domain-name", D6O_ERP_LOCAL_DOMAIN_NAME, OPT_FQDN_TYPE, false,
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index 98ac281862..5b03646f3c 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -298,6 +298,109 @@ TEST_F(OptionCustomTest, booleanData) {
}
// The purpose of this test is to verify that the data from a buffer
+// can be read as a DHCPv4 tuple.
+TEST_F(OptionCustomTest, tupleData4) {
+ OptionDefinition opt_def("option-foo", 232, "tuple", "option-foo-space");
+
+ const char data[] = {
+ 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Append suboption. It should be present in the parsed packet.
+ appendV4Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Check it
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("foobar", value);
+
+ // Now as a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ EXPECT_NO_THROW(option->readTuple(tuple, 0));
+ EXPECT_EQ("foobar", tuple.getText());
+
+ // There should be one suboption present.
+ EXPECT_TRUE(hasV4Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4,
+ buf.begin(), buf.begin() + 6)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Check that the option with "no data" is rejected.
+ buf.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4,
+ buf.begin(), buf.end())),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as a DHCPv6 tuple.
+TEST_F(OptionCustomTest, tupleData6) {
+ OptionDefinition opt_def("option-foo", 1000, "tuple", "option-foo-space");
+
+ const char data[] = {
+ 0, 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Append suboption. It should be present in the parsed packet.
+ appendV6Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Check it
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("foobar", value);
+
+ // Now as a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ EXPECT_NO_THROW(option->readTuple(tuple, 0));
+ EXPECT_EQ("foobar", tuple.getText());
+
+ // There should be one suboption present.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 1)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 7)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+}
+
+// The purpose of this test is to verify that the data from a buffer
// can be read as FQDN.
TEST_F(OptionCustomTest, fqdnData) {
OptionDefinition opt_def("option-foo", 1000, "fqdn", "option-foo-space");
@@ -956,6 +1059,96 @@ TEST_F(OptionCustomTest, psidDataArray) {
EXPECT_EQ(0x01, psid2.second.asUint16());
}
+// The purpose of this test is to verify that the data from a buffer
+// can be read as DHCPv4 tuples.
+TEST_F(OptionCustomTest, tupleDataArray4) {
+ OptionDefinition opt_def("option-foo", 232, "tuple", true);
+
+ const char data[] = {
+ 5, 104, 101, 108, 108, 111, // "hello"
+ 1, 32, // " "
+ 5, 119, 111, 114, 108, 100 // "world"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check them
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("hello", value);
+ ASSERT_NO_THROW(value = option->readTuple(1));
+ EXPECT_EQ(" ", value);
+ ASSERT_NO_THROW(value = option->readTuple(2));
+ EXPECT_EQ("world", value);
+
+ // There should be no suboption present.
+ EXPECT_FALSE(hasV4Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4,
+ buf.begin(), buf.begin() + 12)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as DHCPv6 tuples.
+TEST_F(OptionCustomTest, tupleDataArray6) {
+ OptionDefinition opt_def("option-foo", 1000, "tuple", true);
+
+ const char data[] = {
+ 0, 5, 104, 101, 108, 108, 111, // "hello"
+ 0, 1, 32, // " "
+ 0, 5, 119, 111, 114, 108, 100 // "world"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check them
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("hello", value);
+ ASSERT_NO_THROW(value = option->readTuple(1));
+ EXPECT_EQ(" ", value);
+ ASSERT_NO_THROW(value = option->readTuple(2));
+ EXPECT_EQ("world", value);
+
+ // There should be no suboption present.
+ EXPECT_FALSE(hasV6Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 8)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 16)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
// The purpose of this test is to verify that the opton definition comprising
// a record of fixed-size fields can be used to create an option with a
// suboption.
@@ -1626,6 +1819,96 @@ TEST_F(OptionCustomTest, setIPv6PrefixDataArray) {
});
}
+/// The purpose of this test is to verify that an option comprising an
+/// array of DHCPv4 tuples can be created with no tuples and that
+/// tuples can be later added after the option has been created.
+TEST_F(OptionCustomTest, setTupleDataArray4) {
+ OptionDefinition opt_def("option-foo", 232, "tuple", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new DHCPv4 tuple into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(std::string("hello")));
+ ASSERT_NO_THROW(option->addArrayDataField(std::string(" ")));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple.append("world");
+ ASSERT_NO_THROW(option->addArrayDataField(tuple));
+
+ // We should have now 3 tuples added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(0);
+ EXPECT_EQ("hello", value);
+ });
+
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(1);
+ EXPECT_EQ(" ", value);
+ });
+
+ ASSERT_NO_THROW({
+ OpaqueDataTuple value(OpaqueDataTuple::LENGTH_1_BYTE);
+ option->readTuple(value, 2);
+ EXPECT_EQ("world", value.getText());
+ });
+}
+
+/// The purpose of this test is to verify that an option comprising an
+/// array of DHCPv6 tuples can be created with no tuples and that
+/// tuples can be later added after the option has been created.
+TEST_F(OptionCustomTest, setTupleDataArray6) {
+ OptionDefinition opt_def("option-foo", 1000, "tuple", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new DHCPv6 tuple into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(std::string("hello")));
+ ASSERT_NO_THROW(option->addArrayDataField(std::string(" ")));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple.append("world");
+ ASSERT_NO_THROW(option->addArrayDataField(tuple));
+
+ // We should have now 3 tuples added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(0);
+ EXPECT_EQ("hello", value);
+ });
+
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(1);
+ EXPECT_EQ(" ", value);
+ });
+
+ ASSERT_NO_THROW({
+ OpaqueDataTuple value(OpaqueDataTuple::LENGTH_2_BYTES);
+ option->readTuple(value, 2);
+ EXPECT_EQ("world", value.getText());
+ });
+}
+
TEST_F(OptionCustomTest, setRecordData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
@@ -1636,6 +1919,7 @@ TEST_F(OptionCustomTest, setRecordData) {
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
ASSERT_NO_THROW(opt_def.addRecordField("psid"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix"));
+ ASSERT_NO_THROW(opt_def.addRecordField("tuple"));
ASSERT_NO_THROW(opt_def.addRecordField("string"));
// Create an option and let the data field be initialized
@@ -1648,7 +1932,7 @@ TEST_F(OptionCustomTest, setRecordData) {
// The number of elements should be equal to number of elements
// in the record.
- ASSERT_EQ(8, option->getDataFieldsNum());
+ ASSERT_EQ(9, option->getDataFieldsNum());
// Check that the default values have been correctly set.
uint16_t value0;
@@ -1674,9 +1958,12 @@ TEST_F(OptionCustomTest, setRecordData) {
ASSERT_NO_THROW(value6 = option->readPrefix(6));
EXPECT_EQ(0, value6.first.asUnsigned());
EXPECT_EQ("::", value6.second.toText());
- std::string value7 = "xyz";
- ASSERT_NO_THROW(value7 = option->readString(7));
- EXPECT_TRUE(value7.empty());
+ std::string value7 = "abc";
+ // Tuple has no default value
+ EXPECT_THROW(option->readTuple(7), BadDataTypeCast);
+ std::string value8 = "xyz";
+ ASSERT_NO_THROW(value8 = option->readString(8));
+ EXPECT_TRUE(value8.empty());
// Override each value with a new value.
ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
@@ -1687,7 +1974,8 @@ TEST_F(OptionCustomTest, setRecordData) {
ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5));
ASSERT_NO_THROW(option->writePrefix(PrefixLen(48),
IOAddress("2001:db8:1::"), 6));
- ASSERT_NO_THROW(option->writeString("hello world", 7));
+ ASSERT_NO_THROW(option->writeTuple("foobar", 7));
+ ASSERT_NO_THROW(option->writeString("hello world", 8));
// Check that the new values have been correctly set.
ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
@@ -1706,8 +1994,10 @@ TEST_F(OptionCustomTest, setRecordData) {
ASSERT_NO_THROW(value6 = option->readPrefix(6));
EXPECT_EQ(48, value6.first.asUnsigned());
EXPECT_EQ("2001:db8:1::", value6.second.toText());
- ASSERT_NO_THROW(value7 = option->readString(7));
- EXPECT_EQ(value7, "hello world");
+ ASSERT_NO_THROW(value7 = option->readTuple(7));
+ EXPECT_EQ(value7, "foobar");
+ ASSERT_NO_THROW(value8 = option->readString(8));
+ EXPECT_EQ(value8, "hello world");
}
// The purpose of this test is to verify that pack function for
diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc
index e493d9fead..71af5d3167 100644
--- a/src/lib/dhcp/tests/option_data_types_unittest.cc
+++ b/src/lib/dhcp/tests/option_data_types_unittest.cc
@@ -210,6 +210,12 @@ TEST_F(OptionDataTypesTest, readTuple) {
// Check that it is valid.
EXPECT_EQ(value, result);
+ // Read the tuple from the buffer.
+ OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple4));
+ // Check that it is valid.
+ EXPECT_EQ(value, tuple4.getText());
+
buf.clear();
// DHCPv6 tuples use 2 byte length
@@ -222,11 +228,17 @@ TEST_F(OptionDataTypesTest, readTuple) {
);
// Check that it is valid.
EXPECT_EQ(value, result);
+
+ // Read the tuple from the buffer.
+ OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple6));
+ // Check that it is valid.
+ EXPECT_EQ(value, tuple6.getText());
}
// The purpose of this test is to verify that a tuple value
-// are correctly encoded in a buffer
-TEST_F(OptionDataTypesTest, writeTuple) {
+// are correctly encoded in a buffer (string version)
+TEST_F(OptionDataTypesTest, writeTupleString) {
// The string
std::string value = "hello world";
// Create an output buffer.
@@ -255,6 +267,44 @@ TEST_F(OptionDataTypesTest, writeTuple) {
EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
}
+// The purpose of this test is to verify that a tuple value
+// are correctly encoded in a buffer (tuple version)
+TEST_F(OptionDataTypesTest, writeTuple) {
+ // The string
+ std::string value = "hello world";
+ // Create a DHCPv4 tuple
+ OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple4.append(value);
+ // Create an output buffer.
+ std::vector<uint8_t> buf;
+
+ // Encode it in DHCPv4
+ OptionDataTypeUtil::writeTuple(tuple4, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 1, buf.size());
+ std::vector<uint8_t> expected;
+ writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+
+ buf.clear();
+
+ // Create a DHCPv6 tuple
+ OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple6.append(value);
+
+ // Encode it in DHCPv6
+ OptionDataTypeUtil::writeTuple(tuple6, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 2, buf.size());
+ expected.clear();
+ writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+}
+
// The purpose of this test is to verify that the boolean value stored
// in a buffer is correctly read from this buffer.
TEST_F(OptionDataTypesTest, readBool) {
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 9125c8851b..872e80bd72 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,6 +18,7 @@
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_string.h>
+#include <dhcp/option_opaque_data_tuples.h>
#include <exceptions/exceptions.h>
#include <boost/pointer_cast.hpp>
@@ -1510,4 +1511,210 @@ TEST_F(OptionDefinitionTest, psidArrayTokenized) {
EXPECT_EQ(7, psid2.second.asUint16());
}
+// This test verifies that a definition of an option with a single DHCPv4
+// tuple can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, tuple4) {
+ OptionDefinition opt_def("option-tuple", 232, "tuple");
+
+ OptionPtr option;
+
+ // Create a buffer holding tuple
+ const char data[] = {
+ 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+ OptionBuffer buf(data, data + sizeof(data));
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, 232, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with a single DHCPv6
+// tuple can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, tuple6) {
+ OptionDefinition opt_def("option-tuple", 1000, "tuple");
+
+ OptionPtr option;
+
+ // Create a buffer holding tuple
+ const char data[] = {
+ 0, 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+ OptionBuffer buf(data, data + sizeof(data));
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V6, 1000, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with a single DHCPv4
+// tuple can be created and that the instance of this option can be
+// created by specifying tuple value in the textual format.
+TEST_F(OptionDefinitionTest, tuple4Tokenized) {
+ OptionDefinition opt_def("option-tuple", 232, "tuple");
+
+ OptionPtr option;
+ // Specify a single tuple with "foobar" content.
+ std::vector<std::string> values(1, "foobar");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, 232, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with a single DHCPv6
+// tuple can be created and that the instance of this option can be
+// created by specifying tuple value in the textual format.
+TEST_F(OptionDefinitionTest, tuple6Tokenized) {
+ OptionDefinition opt_def("option-tuple", 1000, "tuple");
+
+ OptionPtr option;
+ // Specify a single tuple with "foobar" content.
+ std::vector<std::string> values(1, "foobar");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with an array
+// of DHCPv4 tuples can be created and that the instance of this option
+// can be created by specifying multiple DHCPv4 tuples in the textual format.
+TEST_F(OptionDefinitionTest, tuple4ArrayTokenized) {
+ OptionDefinition opt_def("option-tuple", 232, "tuple", true);
+
+ OptionPtr option;
+
+ // Specify 3 tuples.
+ std::vector<std::string> values;
+ values.push_back("hello");
+ values.push_back("the");
+ values.push_back("world");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, 232, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples));
+
+ // Validate the value.
+ OptionOpaqueDataTuplesPtr option_cast =
+ boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option);
+
+ // There should be 3 tuples in this option.
+ ASSERT_EQ(3, option_cast->getTuplesNum());
+
+ // Check their values.
+ OpaqueDataTuple tuple0 = option_cast->getTuple(0);
+ EXPECT_EQ("hello", tuple0.getText());
+
+ OpaqueDataTuple tuple1 = option_cast->getTuple(1);
+ EXPECT_EQ("the", tuple1.getText());
+
+ OpaqueDataTuple tuple2 = option_cast->getTuple(2);
+ EXPECT_EQ("world", tuple2.getText());
+}
+
+// This test verifies that a definition of an option with an array
+// of DHCPv6 tuples can be created and that the instance of this option
+// can be created by specifying multiple DHCPv6 tuples in the textual format.
+TEST_F(OptionDefinitionTest, tuple6ArrayTokenized) {
+ OptionDefinition opt_def("option-tuple", 1000, "tuple", true);
+
+ OptionPtr option;
+
+ // Specify 3 tuples.
+ std::vector<std::string> values;
+ values.push_back("hello");
+ values.push_back("the");
+ values.push_back("world");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples));
+
+ // Validate the value.
+ OptionOpaqueDataTuplesPtr option_cast =
+ boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option);
+
+ // There should be 3 tuples in this option.
+ ASSERT_EQ(3, option_cast->getTuplesNum());
+
+ // Check their values.
+ OpaqueDataTuple tuple0 = option_cast->getTuple(0);
+ EXPECT_EQ("hello", tuple0.getText());
+
+ OpaqueDataTuple tuple1 = option_cast->getTuple(1);
+ EXPECT_EQ("the", tuple1.getText());
+
+ OpaqueDataTuple tuple2 = option_cast->getTuple(2);
+ EXPECT_EQ("world", tuple2.getText());
+}
+
} // anonymous namespace