From: Francis Dupont Date: Tue, 14 Feb 2017 15:11:13 +0000 (+0100) Subject: [4070] Began tuple code X-Git-Tag: trac5087_base~5^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=60feddc3b56d67ebcd07dff2e7d33503be3e55e9;p=thirdparty%2Fkea.git [4070] Began tuple code --- 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) {