mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-29 13:07:50 +00:00
[master] Merged trac4070 (option tuple)
This commit is contained in:
commit
7fa24b7daf
@ -69,6 +69,13 @@
|
|||||||
// only so for instance '\x' is translated into '\x'. But
|
// only so for instance '\x' is translated into '\x'. But
|
||||||
// as it works on a JSON string value each of these '\'
|
// as it works on a JSON string value each of these '\'
|
||||||
// characters must be doubled on JSON input.
|
// 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": [
|
"pools": [
|
||||||
|
@ -1284,6 +1284,7 @@ It is merely echoed by the server
|
|||||||
<row><entry>psid</entry><entry>PSID 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 <ulink url="http://tools.ietf.org/html/rfc7597">RFC 7597</ulink> for the details about the PSID wire representation</entry></row>
|
<row><entry>psid</entry><entry>PSID 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 <ulink url="http://tools.ietf.org/html/rfc7597">RFC 7597</ulink> for the details about the PSID wire representation</entry></row>
|
||||||
<row><entry>record</entry><entry>Structured data that may comprise any types (except "record" and "empty")</entry></row>
|
<row><entry>record</entry><entry>Structured data that may comprise any types (except "record" and "empty")</entry></row>
|
||||||
<row><entry>string</entry><entry>Any text</entry></row>
|
<row><entry>string</entry><entry>Any text</entry></row>
|
||||||
|
<row><entry>tuple</entry><entry>A length encoded as a 8 (16 for DHCPv6) bit unsigned integer followed by a string of this length</entry></row>
|
||||||
<row><entry>uint8</entry><entry>8 bit unsigned integer with allowed values 0 to 255</entry></row>
|
<row><entry>uint8</entry><entry>8 bit unsigned integer with allowed values 0 to 255</entry></row>
|
||||||
<row><entry>uint16</entry><entry>16 bit unsigned integer with allowed values 0 to 65535</entry></row>
|
<row><entry>uint16</entry><entry>16 bit unsigned integer with allowed values 0 to 65535</entry></row>
|
||||||
<row><entry>uint32</entry><entry>32 bit unsigned integer with allowed values 0 to 4294967295</entry></row>
|
<row><entry>uint32</entry><entry>32 bit unsigned integer with allowed values 0 to 4294967295</entry></row>
|
||||||
|
@ -1204,7 +1204,7 @@ temporarily override a list of interface names and listen on all interfaces.
|
|||||||
<row><entry>lq-relay-data</entry><entry>47</entry><entry>record (ipv6-address, binary)</entry><entry>false</entry></row>
|
<row><entry>lq-relay-data</entry><entry>47</entry><entry>record (ipv6-address, binary)</entry><entry>false</entry></row>
|
||||||
<row><entry>lq-client-link</entry><entry>48</entry><entry>ipv6-address</entry><entry>true</entry></row>
|
<row><entry>lq-client-link</entry><entry>48</entry><entry>ipv6-address</entry><entry>true</entry></row>
|
||||||
<row><entry>bootfile-url</entry><entry>59</entry><entry>string</entry><entry>false</entry></row>
|
<row><entry>bootfile-url</entry><entry>59</entry><entry>string</entry><entry>false</entry></row>
|
||||||
<row><entry>bootfile-param</entry><entry>60</entry><entry>binary</entry><entry>false</entry></row>
|
<row><entry>bootfile-param</entry><entry>60</entry><entry>tuple</entry><entry>true</entry></row>
|
||||||
<row><entry>client-arch-type</entry><entry>61</entry><entry>uint16</entry><entry>true</entry></row>
|
<row><entry>client-arch-type</entry><entry>61</entry><entry>uint16</entry><entry>true</entry></row>
|
||||||
<row><entry>nii</entry><entry>62</entry><entry>record (uint8, uint8, uint8)</entry><entry>false</entry></row>
|
<row><entry>nii</entry><entry>62</entry><entry>record (uint8, uint8, uint8)</entry><entry>false</entry></row>
|
||||||
<row><entry>aftr-name</entry><entry>64</entry><entry>fqdn</entry><entry>false</entry></row>
|
<row><entry>aftr-name</entry><entry>64</entry><entry>fqdn</entry><entry>false</entry></row>
|
||||||
|
@ -64,6 +64,26 @@ OptionCustom::addArrayDataField(const IOAddress& address) {
|
|||||||
buffers_.push_back(buf);
|
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 OpaqueDataTuple& value) {
|
||||||
|
checkArrayType();
|
||||||
|
|
||||||
|
OptionBuffer buf;
|
||||||
|
OptionDataTypeUtil::writeTuple(value, buf);
|
||||||
|
buffers_.push_back(buf);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
OptionCustom::addArrayDataField(const bool value) {
|
OptionCustom::addArrayDataField(const bool value) {
|
||||||
checkArrayType();
|
checkArrayType();
|
||||||
@ -244,7 +264,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
|
|||||||
// that the validate() function in OptionDefinition object
|
// that the validate() function in OptionDefinition object
|
||||||
// should have checked wheter it is a case for this option.
|
// should have checked wheter it is a case for this option.
|
||||||
data_size = std::distance(data, data_buf.end());
|
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
|
// The size of the IPV6 prefix type is determined as
|
||||||
// one byte (which is the size of the prefix in bits)
|
// one byte (which is the size of the prefix in bits)
|
||||||
// followed by the prefix bits (right-padded with
|
// followed by the prefix bits (right-padded with
|
||||||
@ -252,6 +272,18 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
|
|||||||
if (std::distance(data, data_buf.end()) > 0) {
|
if (std::distance(data, data_buf.end()) > 0) {
|
||||||
data_size = static_cast<size_t>(sizeof(uint8_t) + (*data + 7) / 8);
|
data_size = static_cast<size_t>(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 {
|
} else {
|
||||||
// If we reached the end of buffer we assume that this option is
|
// If we reached the end of buffer we assume that this option is
|
||||||
// truncated because there is no remaining data to initialize
|
// truncated because there is no remaining data to initialize
|
||||||
@ -314,6 +346,19 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
|
|||||||
// Data size comprises 1 byte holding a prefix length and the
|
// Data size comprises 1 byte holding a prefix length and the
|
||||||
// prefix length (in bytes) rounded to the nearest byte boundary.
|
// prefix length (in bytes) rounded to the nearest byte boundary.
|
||||||
data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8;
|
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
|
// We don't perform other checks for data types that can't be
|
||||||
// used together with array indicator such as strings, empty field
|
// used together with array indicator such as strings, empty field
|
||||||
@ -351,6 +396,19 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
|
|||||||
data_size = static_cast<size_t>
|
data_size = static_cast<size_t>
|
||||||
(sizeof(uint8_t) + (data_buf[0] + 7) / 8);
|
(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 {
|
} else {
|
||||||
data_size = std::distance(data, data_buf.end());
|
data_size = std::distance(data, data_buf.end());
|
||||||
}
|
}
|
||||||
@ -415,6 +473,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
|
|||||||
case OPT_FQDN_TYPE:
|
case OPT_FQDN_TYPE:
|
||||||
text << "\"" << readFqdn(index) << "\"";
|
text << "\"" << readFqdn(index) << "\"";
|
||||||
break;
|
break;
|
||||||
|
case OPT_TUPLE_TYPE:
|
||||||
|
text << "\"" << readTuple(index) << "\"";
|
||||||
|
break;
|
||||||
case OPT_STRING_TYPE:
|
case OPT_STRING_TYPE:
|
||||||
text << "\"" << readString(index) << "\"";
|
text << "\"" << readString(index) << "\"";
|
||||||
break;
|
break;
|
||||||
@ -499,6 +560,39 @@ OptionCustom::writeBinary(const OptionBuffer& buf,
|
|||||||
buffers_[index] = 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::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);
|
||||||
|
|
||||||
|
buffers_[index].clear();
|
||||||
|
OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ?
|
||||||
|
OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES;
|
||||||
|
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
|
bool
|
||||||
OptionCustom::readBoolean(const uint32_t index) const {
|
OptionCustom::readBoolean(const uint32_t index) const {
|
||||||
checkIndex(index);
|
checkIndex(index);
|
||||||
|
@ -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
|
// 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
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
@ -113,6 +113,16 @@ public:
|
|||||||
buffers_.push_back(buf);
|
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 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.
|
/// @brief Create new buffer and store variable length prefix in it.
|
||||||
///
|
///
|
||||||
/// @param prefix_len Prefix length.
|
/// @param prefix_len Prefix length.
|
||||||
@ -163,6 +173,34 @@ public:
|
|||||||
/// @param index buffer index.
|
/// @param index buffer index.
|
||||||
void writeBinary(const OptionBuffer& buf, const uint32_t index = 0);
|
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 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.
|
/// @brief Read a buffer as boolean value.
|
||||||
///
|
///
|
||||||
/// @param index buffer index.
|
/// @param index buffer index.
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include <dns/name.h>
|
#include <dns/name.h>
|
||||||
#include <util/encode/hex.h>
|
#include <util/encode/hex.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
using namespace isc::asiolink;
|
using namespace isc::asiolink;
|
||||||
|
|
||||||
@ -30,6 +31,7 @@ OptionDataTypeUtil::OptionDataTypeUtil() {
|
|||||||
data_types_["ipv6-prefix"] = OPT_IPV6_PREFIX_TYPE;
|
data_types_["ipv6-prefix"] = OPT_IPV6_PREFIX_TYPE;
|
||||||
data_types_["psid"] = OPT_PSID_TYPE;
|
data_types_["psid"] = OPT_PSID_TYPE;
|
||||||
data_types_["string"] = OPT_STRING_TYPE;
|
data_types_["string"] = OPT_STRING_TYPE;
|
||||||
|
data_types_["tuple"] = OPT_TUPLE_TYPE;
|
||||||
data_types_["fqdn"] = OPT_FQDN_TYPE;
|
data_types_["fqdn"] = OPT_FQDN_TYPE;
|
||||||
data_types_["record"] = OPT_RECORD_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_IPV6_PREFIX_TYPE] = "ipv6-prefix";
|
||||||
data_type_names_[OPT_PSID_TYPE] = "psid";
|
data_type_names_[OPT_PSID_TYPE] = "psid";
|
||||||
data_type_names_[OPT_STRING_TYPE] = "string";
|
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_FQDN_TYPE] = "fqdn";
|
||||||
data_type_names_[OPT_RECORD_TYPE] = "record";
|
data_type_names_[OPT_RECORD_TYPE] = "record";
|
||||||
// The "unknown" data type is declared here so as
|
// The "unknown" data type is declared here so as
|
||||||
@ -141,7 +144,7 @@ OptionDataTypeUtil::readAddress(const std::vector<uint8_t>& buf,
|
|||||||
return (IOAddress::fromBytes(AF_INET6, &buf[0]));
|
return (IOAddress::fromBytes(AF_INET6, &buf[0]));
|
||||||
} else {
|
} else {
|
||||||
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
|
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
|
||||||
"IP address. Invalid family: " << family);
|
<< " IP address. Invalid family: " << family);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +173,118 @@ OptionDataTypeUtil::writeBinary(const std::string& hex_str,
|
|||||||
buf.insert(buf.end(), binary.begin(), binary.end());
|
buf.insert(buf.end(), binary.begin(), binary.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
OptionDataTypeUtil::readTuple(const std::vector<uint8_t>& 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<unsigned>(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<unsigned>(lengthfieldtype));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
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 "
|
||||||
|
<< value.size() << " larger than "
|
||||||
|
<< std::numeric_limits<uint8_t>::max() << ")");
|
||||||
|
}
|
||||||
|
buf.push_back(static_cast<uint8_t>(value.size()));
|
||||||
|
|
||||||
|
} else if (lengthfieldtype == OpaqueDataTuple::LENGTH_2_BYTES) {
|
||||||
|
if (value.size() > std::numeric_limits<uint16_t>::max()) {
|
||||||
|
isc_throw(BadDataTypeCast, "invalid tuple value (size "
|
||||||
|
<< value.size() << " larger than "
|
||||||
|
<< std::numeric_limits<uint16_t>::max() << ")");
|
||||||
|
}
|
||||||
|
buf.resize(buf.size() + 2);
|
||||||
|
isc::util::writeUint16(static_cast<uint16_t>(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<unsigned>(lengthfieldtype));
|
||||||
|
}
|
||||||
|
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
|
bool
|
||||||
OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
|
OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
|
||||||
if (buf.empty()) {
|
if (buf.empty()) {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#define OPTION_DATA_TYPES_H
|
#define OPTION_DATA_TYPES_H
|
||||||
|
|
||||||
#include <asiolink/io_address.h>
|
#include <asiolink/io_address.h>
|
||||||
|
#include <dhcp/opaque_data_tuple.h>
|
||||||
#include <dhcp/option.h>
|
#include <dhcp/option.h>
|
||||||
#include <exceptions/exceptions.h>
|
#include <exceptions/exceptions.h>
|
||||||
#include <util/io_utilities.h>
|
#include <util/io_utilities.h>
|
||||||
@ -57,6 +58,7 @@ enum OptionDataType {
|
|||||||
OPT_IPV6_PREFIX_TYPE,
|
OPT_IPV6_PREFIX_TYPE,
|
||||||
OPT_PSID_TYPE,
|
OPT_PSID_TYPE,
|
||||||
OPT_STRING_TYPE,
|
OPT_STRING_TYPE,
|
||||||
|
OPT_TUPLE_TYPE,
|
||||||
OPT_FQDN_TYPE,
|
OPT_FQDN_TYPE,
|
||||||
OPT_RECORD_TYPE,
|
OPT_RECORD_TYPE,
|
||||||
OPT_UNKNOWN_TYPE
|
OPT_UNKNOWN_TYPE
|
||||||
@ -382,6 +384,42 @@ public:
|
|||||||
static void writeBinary(const std::string& hex_str,
|
static void writeBinary(const std::string& hex_str,
|
||||||
std::vector<uint8_t>& buf);
|
std::vector<uint8_t>& 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<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
|
||||||
|
/// @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<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.
|
/// @brief Read boolean value from a buffer.
|
||||||
///
|
///
|
||||||
/// @param buf input buffer.
|
/// @param buf input buffer.
|
||||||
|
@ -205,6 +205,14 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
|
|||||||
case OPT_STRING_TYPE:
|
case OPT_STRING_TYPE:
|
||||||
return (OptionPtr(new OptionString(u, type, begin, end)));
|
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:
|
default:
|
||||||
// Do nothing. We will return generic option a few lines down.
|
// 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");
|
isc_throw(InvalidOptionValue, "no option value specified");
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
} else if (array_type_ && type_ != OPT_RECORD_TYPE) {
|
||||||
for (size_t i = 0; i < values.size(); ++i) {
|
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) {
|
} else if (type_ == OPT_RECORD_TYPE) {
|
||||||
const RecordFieldsCollection& records = getRecordFields();
|
const RecordFieldsCollection& records = getRecordFields();
|
||||||
@ -245,8 +253,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
|
|||||||
<< " of values provided.");
|
<< " of values provided.");
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < records.size(); ++i) {
|
for (size_t i = 0; i < records.size(); ++i) {
|
||||||
writeToBuffer(util::str::trim(values[i]),
|
writeToBuffer(u, util::str::trim(values[i]), records[i], buf);
|
||||||
records[i], buf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (optionFactory(u, type, buf.begin(), buf.end()));
|
return (optionFactory(u, type, buf.begin(), buf.end()));
|
||||||
@ -433,7 +440,7 @@ OptionDefinition::haveStatusCodeFormat() const {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
OptionDefinition::haveOpaqueDataTuplesFormat() const {
|
OptionDefinition::haveOpaqueDataTuplesFormat() const {
|
||||||
return (getType() == OPT_BINARY_TYPE);
|
return (haveType(OPT_TUPLE_TYPE) && getArrayType());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -507,7 +514,8 @@ OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
OptionDefinition::writeToBuffer(const std::string& value,
|
OptionDefinition::writeToBuffer(Option::Universe u,
|
||||||
|
const std::string& value,
|
||||||
const OptionDataType type,
|
const OptionDataType type,
|
||||||
OptionBuffer& buf) const {
|
OptionBuffer& buf) const {
|
||||||
// We are going to write value given by value argument to the buffer.
|
// 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:
|
case OPT_FQDN_TYPE:
|
||||||
OptionDataTypeUtil::writeFqdn(value, buf);
|
OptionDataTypeUtil::writeFqdn(value, buf);
|
||||||
return;
|
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:
|
default:
|
||||||
// We hit this point because invalid option data type has been specified
|
// 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
|
// This may be the case because 'empty' or 'record' data type has been
|
||||||
@ -739,6 +754,17 @@ OptionDefinition::factoryIAPrefix6(uint16_t type,
|
|||||||
return (option);
|
return (option);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
OptionPtr
|
||||||
OptionDefinition::factorySpecialFormatOption(Option::Universe u,
|
OptionDefinition::factorySpecialFormatOption(Option::Universe u,
|
||||||
OptionBufferConstIter begin,
|
OptionBufferConstIter begin,
|
||||||
@ -778,7 +804,7 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u,
|
|||||||
return (OptionPtr(new Option6StatusCode(begin, end)));
|
return (OptionPtr(new Option6StatusCode(begin, end)));
|
||||||
} else if (getCode() == D6O_BOOTFILE_PARAM && haveOpaqueDataTuplesFormat()) {
|
} else if (getCode() == D6O_BOOTFILE_PARAM && haveOpaqueDataTuplesFormat()) {
|
||||||
// Bootfile params (option code 60)
|
// 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)) {
|
} else if ((getCode() == D6O_PD_EXCLUDE) && haveType(OPT_IPV6_PREFIX_TYPE)) {
|
||||||
// Prefix Exclude (option code 67)
|
// Prefix Exclude (option code 67)
|
||||||
return (OptionPtr(new Option6PDExclude(begin, end)));
|
return (OptionPtr(new Option6PDExclude(begin, end)));
|
||||||
|
@ -85,7 +85,9 @@ class OptionIntArray;
|
|||||||
/// value. For example, DHCPv6 option 8 comprises a two-byte option code, a
|
/// 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
|
/// 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
|
/// (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
|
/// When the option has a more complex structure, the option type may be
|
||||||
/// defined as "array", "record" or even "array of records".
|
/// defined as "array", "record" or even "array of records".
|
||||||
@ -123,6 +125,7 @@ class OptionIntArray;
|
|||||||
/// - "psid" (PSID length / value)
|
/// - "psid" (PSID length / value)
|
||||||
/// - "string"
|
/// - "string"
|
||||||
/// - "fqdn" (fully qualified name)
|
/// - "fqdn" (fully qualified name)
|
||||||
|
/// - "tuple" (length and string)
|
||||||
/// - "record" (set of data fields of different types)
|
/// - "record" (set of data fields of different types)
|
||||||
///
|
///
|
||||||
/// @todo Extend the comment to describe "generic factories".
|
/// @todo Extend the comment to describe "generic factories".
|
||||||
@ -518,6 +521,20 @@ public:
|
|||||||
OptionBufferConstIter begin,
|
OptionBufferConstIter begin,
|
||||||
OptionBufferConstIter end);
|
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.
|
/// @brief Factory function to create option with integer value.
|
||||||
///
|
///
|
||||||
/// @param u universe (V4 or V6).
|
/// @param u universe (V4 or V6).
|
||||||
@ -644,13 +661,14 @@ private:
|
|||||||
/// if it is successful it will store the data in the buffer
|
/// if it is successful it will store the data in the buffer
|
||||||
/// in a binary format.
|
/// in a binary format.
|
||||||
///
|
///
|
||||||
|
/// @param u option universe (V4 or V6).
|
||||||
/// @param value string representation of the value to be written.
|
/// @param value string representation of the value to be written.
|
||||||
/// @param type the actual data type to be stored.
|
/// @param type the actual data type to be stored.
|
||||||
/// @param [in, out] buf buffer where the value is to be stored.
|
/// @param [in, out] buf buffer where the value is to be stored.
|
||||||
///
|
///
|
||||||
/// @throw BadDataTypeCast if data write was unsuccessful.
|
/// @throw BadDataTypeCast if data write was unsuccessful.
|
||||||
void writeToBuffer(const std::string& value, const OptionDataType type,
|
void writeToBuffer(Option::Universe u, const std::string& value,
|
||||||
OptionBuffer& buf) const;
|
const OptionDataType type, OptionBuffer& buf) const;
|
||||||
|
|
||||||
/// Option name.
|
/// Option name.
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
@ -355,7 +355,7 @@ const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = {
|
|||||||
{ "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
|
{ "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
|
||||||
NO_RECORD_DEF, "" },
|
NO_RECORD_DEF, "" },
|
||||||
{ "bootfile-url", D6O_BOOTFILE_URL, OPT_STRING_TYPE, false, 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, "" },
|
{ "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), "" },
|
{ "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,
|
{ "erp-local-domain-name", D6O_ERP_LOCAL_DOMAIN_NAME, OPT_FQDN_TYPE, false,
|
||||||
|
@ -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<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
|
// The purpose of this test is to verify that the data from a buffer
|
||||||
// can be read as FQDN.
|
// can be read as FQDN.
|
||||||
TEST_F(OptionCustomTest, fqdnData) {
|
TEST_F(OptionCustomTest, fqdnData) {
|
||||||
@ -956,6 +1059,96 @@ TEST_F(OptionCustomTest, psidDataArray) {
|
|||||||
EXPECT_EQ(0x01, psid2.second.asUint16());
|
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
|
// 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
|
// a record of fixed-size fields can be used to create an option with a
|
||||||
// suboption.
|
// 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) {
|
TEST_F(OptionCustomTest, setRecordData) {
|
||||||
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
|
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("ipv6-address"));
|
||||||
ASSERT_NO_THROW(opt_def.addRecordField("psid"));
|
ASSERT_NO_THROW(opt_def.addRecordField("psid"));
|
||||||
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix"));
|
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix"));
|
||||||
|
ASSERT_NO_THROW(opt_def.addRecordField("tuple"));
|
||||||
ASSERT_NO_THROW(opt_def.addRecordField("string"));
|
ASSERT_NO_THROW(opt_def.addRecordField("string"));
|
||||||
|
|
||||||
// Create an option and let the data field be initialized
|
// 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
|
// The number of elements should be equal to number of elements
|
||||||
// in the record.
|
// in the record.
|
||||||
ASSERT_EQ(8, option->getDataFieldsNum());
|
ASSERT_EQ(9, option->getDataFieldsNum());
|
||||||
|
|
||||||
// Check that the default values have been correctly set.
|
// Check that the default values have been correctly set.
|
||||||
uint16_t value0;
|
uint16_t value0;
|
||||||
@ -1674,9 +1958,12 @@ TEST_F(OptionCustomTest, setRecordData) {
|
|||||||
ASSERT_NO_THROW(value6 = option->readPrefix(6));
|
ASSERT_NO_THROW(value6 = option->readPrefix(6));
|
||||||
EXPECT_EQ(0, value6.first.asUnsigned());
|
EXPECT_EQ(0, value6.first.asUnsigned());
|
||||||
EXPECT_EQ("::", value6.second.toText());
|
EXPECT_EQ("::", value6.second.toText());
|
||||||
std::string value7 = "xyz";
|
std::string value7 = "abc";
|
||||||
ASSERT_NO_THROW(value7 = option->readString(7));
|
// Tuple has no default value
|
||||||
EXPECT_TRUE(value7.empty());
|
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.
|
// Override each value with a new value.
|
||||||
ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
|
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->writePsid(PSIDLen(4), PSID(8), 5));
|
||||||
ASSERT_NO_THROW(option->writePrefix(PrefixLen(48),
|
ASSERT_NO_THROW(option->writePrefix(PrefixLen(48),
|
||||||
IOAddress("2001:db8:1::"), 6));
|
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.
|
// Check that the new values have been correctly set.
|
||||||
ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
|
ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
|
||||||
@ -1706,8 +1994,10 @@ TEST_F(OptionCustomTest, setRecordData) {
|
|||||||
ASSERT_NO_THROW(value6 = option->readPrefix(6));
|
ASSERT_NO_THROW(value6 = option->readPrefix(6));
|
||||||
EXPECT_EQ(48, value6.first.asUnsigned());
|
EXPECT_EQ(48, value6.first.asUnsigned());
|
||||||
EXPECT_EQ("2001:db8:1::", value6.second.toText());
|
EXPECT_EQ("2001:db8:1::", value6.second.toText());
|
||||||
ASSERT_NO_THROW(value7 = option->readString(7));
|
ASSERT_NO_THROW(value7 = option->readTuple(7));
|
||||||
EXPECT_EQ(value7, "hello world");
|
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
|
// The purpose of this test is to verify that pack function for
|
||||||
|
@ -192,6 +192,119 @@ TEST_F(OptionDataTypesTest, writeBinary) {
|
|||||||
EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
|
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<uint8_t> buf;
|
||||||
|
// DHCPv4 tuples use 1 byte length
|
||||||
|
writeInt<uint8_t>(static_cast<uint8_t>(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);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
writeInt<uint16_t>(static_cast<uint16_t>(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);
|
||||||
|
|
||||||
|
// 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 (string version)
|
||||||
|
TEST_F(OptionDataTypesTest, writeTupleString) {
|
||||||
|
// The string
|
||||||
|
std::string value = "hello world";
|
||||||
|
// Create an output buffer.
|
||||||
|
std::vector<uint8_t> 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<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();
|
||||||
|
|
||||||
|
// 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<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 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
|
// The purpose of this test is to verify that the boolean value stored
|
||||||
// in a buffer is correctly read from this buffer.
|
// in a buffer is correctly read from this buffer.
|
||||||
TEST_F(OptionDataTypesTest, readBool) {
|
TEST_F(OptionDataTypesTest, readBool) {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include <dhcp/option_int.h>
|
#include <dhcp/option_int.h>
|
||||||
#include <dhcp/option_int_array.h>
|
#include <dhcp/option_int_array.h>
|
||||||
#include <dhcp/option_string.h>
|
#include <dhcp/option_string.h>
|
||||||
|
#include <dhcp/option_opaque_data_tuples.h>
|
||||||
#include <exceptions/exceptions.h>
|
#include <exceptions/exceptions.h>
|
||||||
|
|
||||||
#include <boost/pointer_cast.hpp>
|
#include <boost/pointer_cast.hpp>
|
||||||
@ -1510,4 +1511,210 @@ TEST_F(OptionDefinitionTest, psidArrayTokenized) {
|
|||||||
EXPECT_EQ(7, psid2.second.asUint16());
|
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
|
} // anonymous namespace
|
||||||
|
Loading…
x
Reference in New Issue
Block a user