From 60feddc3b56d67ebcd07dff2e7d33503be3e55e9 Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Tue, 14 Feb 2017 16:11:13 +0100 Subject: [PATCH 1/3] [4070] Began tuple code --- doc/guide/dhcp4-srv.xml | 1 + doc/guide/dhcp6-srv.xml | 2 +- src/lib/dhcp/option_custom.cc | 74 +++++++++++++++++- src/lib/dhcp/option_custom.h | 21 ++++- src/lib/dhcp/option_data_types.cc | 76 ++++++++++++++++++- src/lib/dhcp/option_data_types.h | 23 +++++- .../dhcp/tests/option_data_types_unittest.cc | 65 +++++++++++++++- 7 files changed, 255 insertions(+), 7 deletions(-) diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml index d1bc86c7b2..586cce1676 100644 --- a/doc/guide/dhcp4-srv.xml +++ b/doc/guide/dhcp4-srv.xml @@ -1250,6 +1250,7 @@ It is merely echoed by the server psidPSID and PSID length separated by a slash, e.g. 3/4 specifies PSID=3 and PSID length=4. In the wire format it is represented by an 8-bit field carrying PSID length (in this case equal to 4) and the 16-bits long PSID value field (in this case equal to "0011000000000000b" using binary notation). Allowed values for a PSID length are 0 to 16. See RFC 7597 for the details about the PSID wire representation recordStructured data that may comprise any types (except "record" and "empty") stringAny text + tupleA length encoded as a 8 (16 for DHCPv6) bit unsigned integer followed by a string of this length uint88 bit unsigned integer with allowed values 0 to 255 uint1616 bit unsigned integer with allowed values 0 to 65535 uint3232 bit unsigned integer with allowed values 0 to 4294967295 diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml index 44f8ddabb3..69dbfa9872 100644 --- a/doc/guide/dhcp6-srv.xml +++ b/doc/guide/dhcp6-srv.xml @@ -1173,7 +1173,7 @@ temporarily override a list of interface names and listen on all interfaces. lq-relay-data47record (ipv6-address, binary)false lq-client-link48ipv6-addresstrue bootfile-url59stringfalse -bootfile-param60binaryfalse +bootfile-param60tupletrue client-arch-type61uint16true nii62record (uint8, uint8, uint8)false aftr-name64fqdnfalse diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc index 69bca1ecec..ed921b18ee 100644 --- a/src/lib/dhcp/option_custom.cc +++ b/src/lib/dhcp/option_custom.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 @@ -64,6 +64,17 @@ OptionCustom::addArrayDataField(const IOAddress& address) { buffers_.push_back(buf); } +void +OptionCustom::addArrayDataField(const std::string& value) { + checkArrayType(); + + OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES; + OptionBuffer buf; + OptionDataTypeUtil::writeTuple(value, lft, buf); + buffers_.push_back(buf); +} + void OptionCustom::addArrayDataField(const bool value) { checkArrayType(); @@ -244,7 +255,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { // that the validate() function in OptionDefinition object // should have checked wheter it is a case for this option. data_size = std::distance(data, data_buf.end()); - } else if (*field == OPT_IPV6_PREFIX_TYPE ) { + } else if (*field == OPT_IPV6_PREFIX_TYPE) { // The size of the IPV6 prefix type is determined as // one byte (which is the size of the prefix in bits) // followed by the prefix bits (right-padded with @@ -252,6 +263,18 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { if (std::distance(data, data_buf.end()) > 0) { data_size = static_cast(sizeof(uint8_t) + (*data + 7) / 8); } + } else if (*field == OPT_TUPLE_TYPE) { + OpaqueDataTuple::LengthFieldType lft = + getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : + OpaqueDataTuple::LENGTH_2_BYTES; + std::string value = + OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()), + lft); + data_size = value.size(); + // The size of the buffer holding a tuple is always + // 1 or 2 byte larger than the size of the string + data_size += getUniverse() == Option::V4 ? 1 : 2; } else { // If we reached the end of buffer we assume that this option is // truncated because there is no remaining data to initialize @@ -314,6 +337,19 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { // Data size comprises 1 byte holding a prefix length and the // prefix length (in bytes) rounded to the nearest byte boundary. data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8; + } else if (data_type == OPT_TUPLE_TYPE) { + OpaqueDataTuple::LengthFieldType lft = + getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : + OpaqueDataTuple::LENGTH_2_BYTES; + std::string value = + OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()), + lft); + data_size = value.size(); + // The size of the buffer holding a tuple is always + // 1 or 2 byte larger than the size of the string + data_size += getUniverse() == Option::V4 ? 1 : 2; + } // We don't perform other checks for data types that can't be // used together with array indicator such as strings, empty field @@ -351,6 +387,19 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { data_size = static_cast (sizeof(uint8_t) + (data_buf[0] + 7) / 8); } + } else if (data_type == OPT_TUPLE_TYPE) { + OpaqueDataTuple::LengthFieldType lft = + getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : + OpaqueDataTuple::LENGTH_2_BYTES; + std::string value = + OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()), + lft); + data_size = value.size(); + // The size of the buffer holding a tuple is always + // 1 or 2 byte larger than the size of the string + data_size += getUniverse() == Option::V4 ? 1 : 2; + } else { data_size = std::distance(data, data_buf.end()); } @@ -415,6 +464,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_type, case OPT_FQDN_TYPE: text << "\"" << readFqdn(index) << "\""; break; + case OPT_TUPLE_TYPE: + text << "\"" << readTuple(index) << "\""; + break; case OPT_STRING_TYPE: text << "\"" << readString(index) << "\""; break; @@ -499,6 +551,24 @@ OptionCustom::writeBinary(const OptionBuffer& buf, buffers_[index] = buf; } +std::string +OptionCustom::readTuple(const uint32_t index) const { + checkIndex(index); + OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES; + return (OptionDataTypeUtil::readTuple(buffers_[index], lft)); +} + +void +OptionCustom::writeTuple(const std::string& value, const uint32_t index) { + checkIndex(index); + + buffers_[index].clear(); + OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES; + OptionDataTypeUtil::writeTuple(value, lft, 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 122da7c9b9..4329e48bd4 100644 --- a/src/lib/dhcp/option_custom.h +++ b/src/lib/dhcp/option_custom.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 @@ -113,6 +113,11 @@ public: buffers_.push_back(buf); } + /// @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 std::string& value); + /// @brief Create new buffer and store variable length prefix in it. /// /// @param prefix_len Prefix length. @@ -163,6 +168,20 @@ public: /// @param index buffer index. void writeBinary(const OptionBuffer& buf, const uint32_t index = 0); + /// @brief Read a buffer as length and string tuple. + /// + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @return string read from a buffer. + std::string readTuple(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 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 7ab72b3a9f..2d2e232ff9 100644 --- a/src/lib/dhcp/option_data_types.cc +++ b/src/lib/dhcp/option_data_types.cc @@ -9,6 +9,7 @@ #include #include #include +#include using namespace isc::asiolink; @@ -30,6 +31,7 @@ OptionDataTypeUtil::OptionDataTypeUtil() { data_types_["ipv6-prefix"] = OPT_IPV6_PREFIX_TYPE; data_types_["psid"] = OPT_PSID_TYPE; data_types_["string"] = OPT_STRING_TYPE; + data_types_["tuple"] = OPT_TUPLE_TYPE; data_types_["fqdn"] = OPT_FQDN_TYPE; data_types_["record"] = OPT_RECORD_TYPE; @@ -47,6 +49,7 @@ OptionDataTypeUtil::OptionDataTypeUtil() { data_type_names_[OPT_IPV6_PREFIX_TYPE] = "ipv6-prefix"; data_type_names_[OPT_PSID_TYPE] = "psid"; data_type_names_[OPT_STRING_TYPE] = "string"; + data_type_names_[OPT_TUPLE_TYPE] = "tuple"; data_type_names_[OPT_FQDN_TYPE] = "fqdn"; data_type_names_[OPT_RECORD_TYPE] = "record"; // The "unknown" data type is declared here so as @@ -141,7 +144,7 @@ OptionDataTypeUtil::readAddress(const std::vector& buf, return (IOAddress::fromBytes(AF_INET6, &buf[0])); } else { isc_throw(BadDataTypeCast, "unable to read data from the buffer as" - "IP address. Invalid family: " << family); + << " IP address. Invalid family: " << family); } } @@ -170,6 +173,77 @@ OptionDataTypeUtil::writeBinary(const std::string& hex_str, buf.insert(buf.end(), binary.begin(), binary.end()); } +std::string +OptionDataTypeUtil::readTuple(const std::vector& buf, + OpaqueDataTuple::LengthFieldType lengthfieldtype) { + if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) { + if (buf.size() < 1) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple (length). Invalid buffer size: " + << buf.size()); + } + uint8_t len = buf[0]; + if (buf.size() < 1 + len) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple (length " << static_cast(len) + << "). Invalid buffer size: " << buf.size()); + } + std::string value; + value.resize(len); + std::memcpy(&value[0], &buf[1], len); + return (value); + } else if (lengthfieldtype == OpaqueDataTuple::LENGTH_2_BYTES) { + if (buf.size() < 2) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple (length). Invalid buffer size: " + << buf.size()); + } + uint16_t len = isc::util::readUint16(&buf[0], 2); + if (buf.size() < 2 + len) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple (length " << len + << "). Invalid buffer size: " << buf.size()); + } + std::string value; + value.resize(len); + std::memcpy(&value[0], &buf[2], len); + return (value); + } else { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " tuple. Invalid length type field: " + << static_cast(lengthfieldtype)); + } +} + +void +OptionDataTypeUtil:: writeTuple(const std::string& value, + OpaqueDataTuple::LengthFieldType lengthfieldtype, + std::vector& buf) { + if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) { + if (value.size() > std::numeric_limits::max()) { + isc_throw(BadDataTypeCast, "invalid tuple value (size " + << value.size() << " larger than " + << std::numeric_limits::max() << ")"); + } + buf.push_back(static_cast(value.size())); + + } else if (lengthfieldtype == OpaqueDataTuple::LENGTH_2_BYTES) { + if (value.size() > std::numeric_limits::max()) { + isc_throw(BadDataTypeCast, "invalid tuple value (size " + << value.size() << " larger than " + << std::numeric_limits::max() << ")"); + } + buf.resize(buf.size() + 2); + isc::util::writeUint16(static_cast(value.size()), + &buf[buf.size() - 2], 2); + } else { + isc_throw(BadDataTypeCast, "unable to write data to the buffer as" + << " tuple. Invalid length type field: " + << static_cast(lengthfieldtype)); + } + buf.insert(buf.end(), value.begin(), value.end()); +} + bool OptionDataTypeUtil::readBool(const std::vector& buf) { if (buf.empty()) { diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h index e99dc13441..6962e08abe 100644 --- a/src/lib/dhcp/option_data_types.h +++ b/src/lib/dhcp/option_data_types.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 @@ -8,6 +8,7 @@ #define OPTION_DATA_TYPES_H #include +#include #include #include #include @@ -57,6 +58,7 @@ enum OptionDataType { OPT_IPV6_PREFIX_TYPE, OPT_PSID_TYPE, OPT_STRING_TYPE, + OPT_TUPLE_TYPE, OPT_FQDN_TYPE, OPT_RECORD_TYPE, OPT_UNKNOWN_TYPE @@ -382,6 +384,25 @@ public: static void writeBinary(const std::string& hex_str, std::vector& buf); + /// @brief Read length and string tuple from a buffer. + /// + /// @param buf input buffer. + /// @param lengthfieldtype LENGTH_1_BYTE (DHCPv4) or LENGTH_2_BYTES (DHCPv6) + /// @throw isc::dhcp::BadDataTypeCast when the data being read + /// is truncated. + /// @return string being read. + static std::string readTuple(const std::vector& buf, + OpaqueDataTuple::LengthFieldType lengthfieldtype); + + /// @brief Append length and string tuple to a buffer + /// + /// @param value length and string tuple + /// @param lengthfieldtype LENGTH_1_BYTE (DHCPv4) or LENGTH_2_BYTES (DHCPv6) + /// @param [out] buf output buffer. + static void writeTuple(const std::string& value, + OpaqueDataTuple::LengthFieldType lengthfieldtype, + std::vector& buf); + /// @brief Read boolean value from a buffer. /// /// @param buf input buffer. diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc index 3661943dfa..e493d9fead 100644 --- a/src/lib/dhcp/tests/option_data_types_unittest.cc +++ b/src/lib/dhcp/tests/option_data_types_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 @@ -192,6 +192,69 @@ TEST_F(OptionDataTypesTest, writeBinary) { EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); } +// The purpose of this test is to verify that the tuple value stored +TEST_F(OptionDataTypesTest, readTuple) { + // The string + std::string value = "hello world"; + // Create an input buffer. + std::vector buf; + // DHCPv4 tuples use 1 byte length + writeInt(static_cast(value.size()), buf); + writeString(value, buf); + + // Read the string from the buffer. + std::string result; + ASSERT_NO_THROW( + result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_1_BYTE); + ); + // Check that it is valid. + EXPECT_EQ(value, result); + + buf.clear(); + + // DHCPv6 tuples use 2 byte length + writeInt(static_cast(value.size()), buf); + writeString(value, buf); + + // Read the string from the buffer. + ASSERT_NO_THROW( + result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_2_BYTES); + ); + // Check that it is valid. + EXPECT_EQ(value, result); +} + +// The purpose of this test is to verify that a tuple value +// are correctly encoded in a buffer +TEST_F(OptionDataTypesTest, writeTuple) { + // The string + std::string value = "hello world"; + // Create an output buffer. + std::vector buf; + + // Encode it in DHCPv4 + OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_1_BYTE, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 1, buf.size()); + std::vector expected; + writeInt(static_cast(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); + + buf.clear(); + + // Encode it in DHCPv6 + OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_2_BYTES, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 2, buf.size()); + expected.clear(); + writeInt(static_cast(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) { From 2f1e796a2c0d48de239184e73ac841d68461d988 Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Wed, 15 Feb 2017 01:21:02 +0100 Subject: [PATCH 2/3] [4070] Added OpaqueDataTuple read/write, updated defintions and unit tests --- src/lib/dhcp/option_custom.cc | 24 ++ src/lib/dhcp/option_custom.h | 19 ++ src/lib/dhcp/option_data_types.cc | 47 ++- src/lib/dhcp/option_data_types.h | 17 + src/lib/dhcp/option_definition.cc | 42 ++- src/lib/dhcp/option_definition.h | 26 +- src/lib/dhcp/std_option_defs.h | 4 +- src/lib/dhcp/tests/option_custom_unittest.cc | 306 +++++++++++++++++- .../dhcp/tests/option_data_types_unittest.cc | 54 +++- .../dhcp/tests/option_definition_unittest.cc | 209 +++++++++++- 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 @@ -75,6 +75,15 @@ OptionCustom::addArrayDataField(const std::string& value) { buffers_.push_back(buf); } +void +OptionCustom::addArrayDataField(const OpaqueDataTuple& value) { + checkArrayType(); + + OptionBuffer buf; + OptionDataTypeUtil::writeTuple(value, buf); + buffers_.push_back(buf); +} + void OptionCustom::addArrayDataField(const bool value) { checkArrayType(); @@ -559,6 +568,13 @@ OptionCustom::readTuple(const uint32_t index) const { return (OptionDataTypeUtil::readTuple(buffers_[index], lft)); } +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& buf, } void -OptionDataTypeUtil:: writeTuple(const std::string& value, - OpaqueDataTuple::LengthFieldType lengthfieldtype, - std::vector& buf) { +OptionDataTypeUtil::readTuple(const std::vector& 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& buf) { if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) { if (value.size() > std::numeric_limits::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& 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::max()) { + isc_throw(BadDataTypeCast, "invalid tuple value (size " + << tuple.getLength() << " larger than " + << std::numeric_limits::max() << ")"); + } + buf.push_back(static_cast(tuple.getLength())); + + } else if (tuple.getLengthFieldType() == OpaqueDataTuple::LENGTH_2_BYTES) { + if (tuple.getLength() > std::numeric_limits::max()) { + isc_throw(BadDataTypeCast, "invalid tuple value (size " + << tuple.getLength() << " larger than " + << std::numeric_limits::max() << ")"); + } + buf.resize(buf.size() + 2); + isc::util::writeUint16(static_cast(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& 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& 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& 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& 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& 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 @@ -739,6 +754,17 @@ OptionDefinition::factoryIAPrefix6(uint16_t type, return (option); } +OptionPtr +OptionDefinition::factoryOpaqueDataTuples(Option::Universe u, + uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + boost::shared_ptr + option(new OptionOpaqueDataTuples(u, type, begin, end)); + + return (option); +} + OptionPtr OptionDefinition::factorySpecialFormatOption(Option::Universe u, OptionBufferConstIter begin, @@ -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 @@ -297,6 +297,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 buf(data, data + sizeof(data)); + + // Append suboption. It should be present in the parsed packet. + appendV4Suboption(buf); + + boost::scoped_ptr 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 buf(data, data + sizeof(data)); + + // Append suboption. It should be present in the parsed packet. + appendV6Suboption(buf); + + boost::scoped_ptr 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) { @@ -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 buf(data, data + sizeof(data)); + + boost::scoped_ptr 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 buf(data, data + sizeof(data)); + + boost::scoped_ptr 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 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 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(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(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 buf; + + // Encode it in DHCPv4 + OptionDataTypeUtil::writeTuple(tuple4, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 1, buf.size()); + std::vector expected; + writeInt(static_cast(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(static_cast(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 #include #include +#include #include #include @@ -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(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(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 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(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 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(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 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(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 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(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 From bbf2a0b67ad37a8de52a3635401acfe2ec1a8c98 Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Tue, 18 Apr 2017 07:11:37 +0200 Subject: [PATCH 3/3] [4070] Added a bootfile-param example (from a Lunix doc) --- doc/examples/kea6/multiple-options.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/examples/kea6/multiple-options.json b/doc/examples/kea6/multiple-options.json index 3ee58ef0de..6080ef15e4 100644 --- a/doc/examples/kea6/multiple-options.json +++ b/doc/examples/kea6/multiple-options.json @@ -69,6 +69,13 @@ // only so for instance '\x' is translated into '\x'. But // as it works on a JSON string value each of these '\' // characters must be doubled on JSON input. + }, + { + // A few options are encoded in (length, string) tuples + // which can be defined using only strings as the CSV + // processing computes lengths. + "name": "bootfile-param", + "data": "root=/dev/sda2, quiet, splash" } ], "pools": [