BIND 10 master, updated. 0a4faa07777189ed9c25211987a1a9b574015a95 [master] Merge branch 'trac2491'
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Dec 11 11:17:01 UTC 2012
The branch, master has been updated
via 0a4faa07777189ed9c25211987a1a9b574015a95 (commit)
via 8627b6eb8915407f8bf29786b70916b4b6ac0567 (commit)
via 8ad7de967cfee9788fa269640f1ce9e0d302ee04 (commit)
via eb5b276de3dc4c556c818ebfab63a14bf2f562eb (commit)
via 1572782282cd6b5835ad20f5d8538511616a7e2b (commit)
via 8afd799648279d29c8fd9f2505383bf6ebbf0730 (commit)
via 9430b1fef5b2cc99e97b4cfaf6ccd518bcd76198 (commit)
via c831a1e66e88bceb6a317dd8de0796b315681f4f (commit)
via b1d0e42774ef51b404be44ee53cb930a630a63bb (commit)
via fca9ec102de26345c4dafb193f85a0560322f295 (commit)
via ee0836a4c450db59764158e9f9c58acad7a1c4a4 (commit)
via 7c0a4abbb4cce4460c8b0cfd9f1447e0cc37644d (commit)
via 9b8747917cc300904783b403a64c2a11ebdb5ec0 (commit)
via 45c04beca6f7d07f1a2c779baf3a7a8ba26763a2 (commit)
via bc44ee8bf3c35cacaad05751d75e8789aa5c9108 (commit)
via b561ee0919676f6e9c34329151c2b46d60f1824b (commit)
via 372f3a19f5bad9ed51e50d083063fccbe1ff6900 (commit)
via a0662ae5601ee75f3e99b2b53d4f3376b5e6a037 (commit)
via 10e87104e0b7977804c8c55275a403602dcbb2e5 (commit)
via 6b59d1bc4fe7976ea6c1cf7820649824b4135e07 (commit)
via 5c50504446f431198d329d31b62e076d65e3eacb (commit)
via 594916b524ff40cd66ce7891ce743c70a9d0aa68 (commit)
via 59903362374b1fca9612fe5a872579dd4a4d9873 (commit)
via 1dc0f0871d7045e8fb57bb0767fc31a2f7e3fe80 (commit)
via 1bbe13152cea9e759bb0423750f244be58eff578 (commit)
via 8a21830a250996c93b1b053c56a4dc9bb7bb4750 (commit)
via ac111116c588c5b611e1363d4fe11c7ee8f4e495 (commit)
via 84e441c75606aff87131e7d84010679a11f242ce (commit)
via 22a33dfddc0b29a506b57c3f76bace8d15ec6df6 (commit)
via c806c0c3184caf046e124f36028122a965078408 (commit)
from 99b2935b21ba5ae3dd524ad5b781e70e4914f0e4 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 0a4faa07777189ed9c25211987a1a9b574015a95
Merge: 99b2935 8627b6e
Author: Marcin Siodelski <marcin at isc.org>
Date: Tue Dec 11 12:16:53 2012 +0100
[master] Merge branch 'trac2491'
-----------------------------------------------------------------------
Summary of changes:
src/bin/dhcp6/dhcp6_srv.cc | 37 +-
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 21 +-
src/lib/dhcp/Makefile.am | 2 +
src/lib/dhcp/libdhcp++.cc | 68 +--
src/lib/dhcp/libdhcp++.h | 13 +-
src/lib/dhcp/option_custom.cc | 334 +++++++++++--
src/lib/dhcp/option_custom.h | 192 ++++++--
src/lib/dhcp/option_data_types.cc | 41 +-
src/lib/dhcp/option_data_types.h | 44 +-
src/lib/dhcp/option_definition.cc | 95 ++--
src/lib/dhcp/option_definition.h | 2 +-
src/lib/dhcp/std_option_defs.h | 155 ++++++
src/lib/dhcp/tests/Makefile.am | 1 +
src/lib/dhcp/tests/libdhcp++_unittest.cc | 172 ++++++-
src/lib/dhcp/tests/option_custom_unittest.cc | 553 +++++++++++++++++++++-
src/lib/dhcp/tests/option_data_types_unittest.cc | 491 +++++++++++++++++++
src/lib/dhcp/tests/option_definition_unittest.cc | 3 +-
17 files changed, 2015 insertions(+), 209 deletions(-)
create mode 100644 src/lib/dhcp/std_option_defs.h
create mode 100644 src/lib/dhcp/tests/option_data_types_unittest.cc
-----------------------------------------------------------------------
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 02c0dd0..33bab99 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -24,6 +24,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <dhcp/pkt6.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
@@ -348,13 +349,29 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
}
OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
-
- // @todo: Implement Option6_StatusCode and rewrite this code here
- vector<uint8_t> data(text.c_str(), text.c_str() + text.length());
- data.insert(data.begin(), static_cast<uint8_t>(code % 256));
- data.insert(data.begin(), static_cast<uint8_t>(code >> 8));
- OptionPtr status(new Option(Option::V6, D6O_STATUS_CODE, data));
- return (status);
+ // @todo This function uses OptionCustom class to manage contents
+ // of the data fields. Since this this option is frequently used
+ // it may be good to implement dedicated class to avoid performance
+ // impact.
+
+ // Get the definition of the option holding status code.
+ OptionDefinitionPtr status_code_def =
+ LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
+ // This definition is assumed to be initialized in LibDHCP.
+ assert(status_code_def);
+
+ // As there is no dedicated class to represent Status Code
+ // the OptionCustom class should be returned here.
+ boost::shared_ptr<OptionCustom> option_status =
+ boost::dynamic_pointer_cast<
+ OptionCustom>(status_code_def->optionFactory(Option::V6, D6O_STATUS_CODE));
+ assert(option_status);
+
+ // Set status code to 'code' (0 - means data field #0).
+ option_status->writeInteger(code, 0);
+ // Set a message (1 - means data field #1).
+ option_status->writeString(text, 1);
+ return (option_status);
}
Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
@@ -430,6 +447,10 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
// but different wording below)
if (!subnet) {
// Create empty IA_NA option with IAID matching the request.
+ // Note that we don't use OptionDefinition class to create this option.
+ // This is because we prefer using a constructor of Option6IA that
+ // initializes IAID. Otherwise we would have to use setIAID() after
+ // creation of the option which has some performance implications.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
// Insert status code NoAddrsAvail.
@@ -471,6 +492,8 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
hint, fake_allocation);
// Create IA_NA that we will put in the response.
+ // Do not use OptionDefinition to create option's instance so
+ // as we can initialize IAID using a constructor.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
if (lease) {
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 335fb7b..6a598eb 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -801,12 +801,23 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
// a dummy content for client-id
- uint8_t expected[] = {0x0, 0x3, 0x41, 0x42, 0x43, 0x44, 0x45};
- OptionBuffer exp(expected, expected + sizeof(expected));
-
+ uint8_t expected[] = {
+ 0x0, 0xD, // option code = 14
+ 0x0, 0x7, // option length = 7
+ 0x0, 0x3, // status code = 3
+ 0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
+ };
+ // Create the option.
OptionPtr status = srv->createStatusCode(3, "ABCDE");
-
- EXPECT_TRUE(status->getData() == exp);
+ // Allocate an output buffer. We will store the option
+ // in wire format here.
+ OutputBuffer buf(sizeof(expected));
+ // Prepare the wire format.
+ ASSERT_NO_THROW(status->pack(buf));
+ // Check that the option buffer has valid length (option header + data).
+ ASSERT_EQ(sizeof(expected), buf.getLength());
+ // Verify the contents of the option.
+ EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected)));
}
// This test verifies if the selectSubnet() method works as expected.
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 85048b0..f41ce53 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -33,10 +33,12 @@ libb10_dhcp___la_SOURCES += option6_int_array.h
libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libb10_dhcp___la_SOURCES += std_option_defs.h
libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcp___la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
libb10_dhcp___la_LDFLAGS = -no-undefined -version-info 2:0:0
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index a997d31..2773d09 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -22,6 +22,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int_array.h>
#include <dhcp/option_definition.h>
+#include <dhcp/std_option_defs.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
@@ -45,7 +46,7 @@ OptionDefContainer LibDHCP::v4option_defs_;
OptionDefContainer LibDHCP::v6option_defs_;
const OptionDefContainer&
-LibDHCP::getOptionDefs(Option::Universe u) {
+LibDHCP::getOptionDefs(const Option::Universe u) {
switch (u) {
case Option::V4:
initStdOptionDefs4();
@@ -60,6 +61,17 @@ LibDHCP::getOptionDefs(Option::Universe u) {
}
}
+OptionDefinitionPtr
+LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
+ const OptionDefContainer& defs = getOptionDefs(u);
+ const OptionDefContainerTypeIndex& idx = defs.get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+ return (OptionDefinitionPtr());
+}
+
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
uint16_t type,
@@ -254,52 +266,16 @@ void
LibDHCP::initStdOptionDefs6() {
v6option_defs_.clear();
- struct OptionParams {
- std::string name;
- uint16_t code;
- OptionDataType type;
- bool array;
- };
- OptionParams params[] = {
- { "CLIENTID", D6O_CLIENTID, OPT_BINARY_TYPE, false },
- { "SERVERID", D6O_SERVERID, OPT_BINARY_TYPE, false },
- { "IA_NA", D6O_IA_NA, OPT_RECORD_TYPE, false },
- { "IAADDR", D6O_IAADDR, OPT_RECORD_TYPE, false },
- { "ORO", D6O_ORO, OPT_UINT16_TYPE, true },
- { "ELAPSED_TIME", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false },
- { "STATUS_CODE", D6O_STATUS_CODE, OPT_RECORD_TYPE, false },
- { "RAPID_COMMIT", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false },
- { "DNS_SERVERS", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
- { "IA_PD", D6O_IA_PD, OPT_RECORD_TYPE, false }
- };
- const int params_size = sizeof(params) / sizeof(params[0]);
-
- for (int i = 0; i < params_size; ++i) {
- OptionDefinitionPtr definition(new OptionDefinition(params[i].name,
- params[i].code,
- params[i].type,
- params[i].array));
- switch(params[i].code) {
- case D6O_IA_NA:
- case D6O_IA_PD:
- for (int j = 0; j < 3; ++j) {
- definition->addRecordField(OPT_UINT32_TYPE);
- }
- break;
- case D6O_IAADDR:
- definition->addRecordField(OPT_IPV6_ADDRESS_TYPE);
- definition->addRecordField(OPT_UINT32_TYPE);
- definition->addRecordField(OPT_UINT32_TYPE);
- break;
- case D6O_STATUS_CODE:
- definition->addRecordField(OPT_UINT16_TYPE);
- definition->addRecordField(OPT_STRING_TYPE);
- break;
- default:
- // The default case is intentionally left empty
- // as it does not need any processing.
- ;
+ for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) {
+ OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
+ OPTION_DEF_PARAMS6[i].code,
+ OPTION_DEF_PARAMS6[i].type,
+ OPTION_DEF_PARAMS6[i].array));
+
+ for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
+ definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);
}
+
try {
definition->validate();
} catch (const Exception& ex) {
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index cab7928..e855070 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -42,7 +42,18 @@ public:
/// @param u universe of the options (V4 or V6).
///
/// @return collection of option definitions.
- static const OptionDefContainer& getOptionDefs(Option::Universe u);
+ static const OptionDefContainer& getOptionDefs(const Option::Universe u);
+
+ /// @brief Return the first option definition matching a
+ /// particular option code.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param code option code.
+ ///
+ /// @return reference to an option definition being requested
+ /// or NULL pointer if option definition has not been found.
+ static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+ const uint16_t code);
/// @brief Factory function to create instance of option.
///
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index 8b3ef11..0c3cb81 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -21,11 +21,28 @@ namespace isc {
namespace dhcp {
OptionCustom::OptionCustom(const OptionDefinition& def,
+ Universe u)
+ : Option(u, def.getCode(), OptionBuffer()),
+ definition_(def) {
+ createBuffers();
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
Universe u,
const OptionBuffer& data)
: Option(u, def.getCode(), data.begin(), data.end()),
definition_(def) {
- createBuffers();
+ // It is possible that no data is provided if an option
+ // is being created on a server side. In such case a bunch
+ // of buffers with default values is first created and then
+ // the values are replaced using writeXXX functions. Thus
+ // we need to detect that no data has been specified and
+ // take a different code path.
+ if (!data_.empty()) {
+ createBuffers(data_);
+ } else {
+ createBuffers();
+ }
}
OptionCustom::OptionCustom(const OptionDefinition& def,
@@ -34,25 +51,156 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
OptionBufferConstIter last)
: Option(u, def.getCode(), first, last),
definition_(def) {
- createBuffers();
+ createBuffers(data_);
+}
+
+void
+OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+ checkArrayType();
+
+ if ((address.getFamily() == AF_INET &&
+ definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
+ (address.getFamily() == AF_INET6 &&
+ definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) {
+ isc_throw(BadDataTypeCast, "invalid address specified "
+ << address.toText() << ". Expected a valid IPv"
+ << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ? "4" : "6")
+ << " address.");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeAddress(address, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const bool value) {
+ checkArrayType();
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeBool(value, buf);
+ buffers_.push_back(buf);
}
void
OptionCustom::checkIndex(const uint32_t index) const {
if (index >= buffers_.size()) {
isc_throw(isc::OutOfRange, "specified data field index " << index
- << " is out of rangex.");
+ << " is out of range.");
+ }
+}
+
+template<typename T>
+void
+OptionCustom::checkDataType(const uint32_t index) const {
+ // Check that the requested return type is a supported integer.
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(isc::dhcp::InvalidDataType, "specified data type"
+ " is not a supported integer type.");
+ }
+
+ // Get the option definition type.
+ OptionDataType data_type = definition_.getType();
+ if (data_type == OPT_RECORD_TYPE) {
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ definition_.getRecordFields();
+ // When we initialized buffers we have already checked that
+ // the number of these buffers is equal to number of option
+ // fields in the record so the condition below should be met.
+ assert(index < record_fields.size());
+ // Get the data type to be returned.
+ data_type = record_fields[index];
+ }
+
+ if (OptionDataTypeTraits<T>::type != data_type) {
+ isc_throw(isc::dhcp::InvalidDataType,
+ "specified data type " << data_type << " does not"
+ " match the data type in an option definition for field"
+ " index " << index);
}
}
void
OptionCustom::createBuffers() {
+ definition_.validate();
+
+ std::vector<OptionBuffer> buffers;
+
+ OptionDataType data_type = definition_.getType();
+ // This function is called when an empty data buffer has been
+ // passed to the constructor. In such cases values for particular
+ // data fields will be set using modifier functions but for now
+ // we need to initialize a set of buffers that are specified
+ // for an option by its definition. Since there is no data yet,
+ // we are going to fill these buffers with default values.
+ if (data_type == OPT_RECORD_TYPE) {
+ // For record types we need to iterate over all data fields
+ // specified in option definition and create corresponding
+ // buffers for each of them.
+ const OptionDefinition::RecordFieldsCollection fields =
+ definition_.getRecordFields();
+
+ for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+ field != fields.end(); ++field) {
+ OptionBuffer buf;
+
+ // For data types that have a fixed size we can use the
+ // utility function to get the buffer's size.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+ // For variable data sizes the utility function returns zero.
+ // It is ok for string values because the default string
+ // is 'empty'. However for FQDN the empty value is not valid
+ // so we initialize it to '.'.
+ if (data_size == 0 &&
+ *field == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buf);
+ } else {
+ // At this point we can resize the buffer. Note that
+ // for string values we are setting the empty buffer
+ // here.
+ buf.resize(data_size);
+ }
+ // We have the buffer with default value prepared so we
+ // add it to the set of buffers.
+ buffers.push_back(buf);
+ }
+ } else if (!definition_.getArrayType() &&
+ data_type != OPT_EMPTY_TYPE) {
+ // For either 'empty' options we don't have to create any buffers
+ // for obvious reason. For arrays we also don't create any buffers
+ // yet because the set of fields that belong to the array is open
+ // ended so we can't allocate required buffers until we know how
+ // many of them are needed.
+ // For non-arrays we have a single value being held by the option
+ // so we have to allocate exactly one buffer.
+ OptionBuffer buf;
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+ if (data_size == 0 &&
+ data_type == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buf);
+ } else {
+ // Note that if our option holds a string value then
+ // we are making empty buffer here.
+ buf.resize(data_size);
+ }
+ // Add a buffer that we have created and leave.
+ buffers.push_back(buf);
+ }
+ // The 'swap' is used here because we want to make sure that we
+ // don't touch buffers_ until we successfully allocate all
+ // buffers to be stored there.
+ std::swap(buffers, buffers_);
+}
+
+void
+OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// Check that the option definition is correct as we are going
// to use it to split the data_ buffer into set of sub buffers.
definition_.validate();
std::vector<OptionBuffer> buffers;
- OptionBuffer::iterator data = data_.begin();
+ OptionBuffer::const_iterator data = data_buf.begin();
OptionDataType data_type = definition_.getType();
if (data_type == OPT_RECORD_TYPE) {
@@ -68,17 +216,31 @@ OptionCustom::createBuffers() {
// For fixed-size data type such as boolean, integer, even
// IP address we can use the utility function to get the required
// buffer size.
- int data_size = OptionDataTypeUtil::getDataTypeLen(*field);
-
- // For variable size types (such as string) the function above
- // will return 0 so we need to do a runtime check. Since variable
- // length data fields may be laid only at the end of an option we
- // consume the rest of this option. Note that validate() function
- // in OptionDefinition object should have checked whether the
- // data fields layout is correct (that the variable string fields
- // are laid at the end).
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+ // For variable size types (e.g. string) the function above will
+ // return 0 so we need to do a runtime check of the length.
if (data_size == 0) {
- data_size = std::distance(data, data_.end());
+ // FQDN is a special data type as it stores variable length data
+ // but the data length is encoded in the buffer. The easiest way
+ // to obtain the length of the data is to read the FQDN. The
+ // utility function will return the size of the buffer on success.
+ if (*field == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+ // The size of the buffer holding an FQDN is always
+ // 1 byte larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ } else {
+ // In other case we are dealing with string or binary value
+ // which size can't be determined. Thus we consume the
+ // remaining part of the buffer for it. Note that variable
+ // size data can be laid at the end of the option only and
+ // 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());
+ }
if (data_size == 0) {
// If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize
@@ -90,7 +252,7 @@ OptionCustom::createBuffers() {
} else {
// Our data field requires that there is a certain chunk of
// data left in the buffer. If not, option is truncated.
- if (std::distance(data, data_.end()) < data_size) {
+ if (std::distance(data, data_buf.end()) < data_size) {
isc_throw(OutOfRange, "option buffer truncated");
}
}
@@ -105,39 +267,64 @@ OptionCustom::createBuffers() {
// data fields of the same type. The type of those fields
// is held in the data_type variable so let's use it to determine
// a size of buffers.
- int data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
// The check below will fail if the input buffer is too short
// for the data size being held by this option.
// Note that data_size returned by getDataTypeLen may be zero
// if variable length data is being held by the option but
// this will not cause this check to throw exception.
- if (std::distance(data, data_.end()) < data_size) {
+ if (std::distance(data, data_buf.end()) < data_size) {
isc_throw(OutOfRange, "option buffer truncated");
}
// For an array of values we are taking different path because
// we have to handle multiple buffers.
if (definition_.getArrayType()) {
- // We don't perform other checks for data types that can't be
- // used together with array indicator such as strings, empty field
- // etc. This is because OptionDefinition::validate function should
- // have checked this already. Thus data_size must be greater than
- // zero.
- assert(data_size > 0);
- // Get equal chunks of data and store as collection of buffers.
- // Truncate any remaining part which length is not divisible by
- // data_size. Note that it is ok to truncate the data if and only
- // if the data buffer is long enough to keep at least one value.
- // This has been checked above already.
- do {
+ while (data != data_buf.end()) {
+ // FQDN is a special case because it is of a variable length.
+ // The actual length for a particular FQDN is encoded within
+ // a buffer so we have to actually read the FQDN from a buffer
+ // to get it.
+ if (data_type == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+ // The size of the buffer holding an FQDN is always
+ // 1 byte larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ }
+ // We don't perform other checks for data types that can't be
+ // used together with array indicator such as strings, empty field
+ // etc. This is because OptionDefinition::validate function should
+ // have checked this already. Thus data_size must be greater than
+ // zero.
+ assert(data_size > 0);
+ // Get chunks of data and store as a collection of buffers.
+ // Truncate any remaining part which length is not divisible by
+ // data_size. Note that it is ok to truncate the data if and only
+ // if the data buffer is long enough to keep at least one value.
+ // This has been checked above already.
+ if (std::distance(data, data_buf.end()) < data_size) {
+ break;
+ }
buffers.push_back(OptionBuffer(data, data + data_size));
data += data_size;
- } while (std::distance(data, data_.end()) >= data_size);
+ }
} else {
// For non-arrays the data_size can be zero because
// getDataTypeLen returns zero for variable size data types
// such as strings. Simply take whole buffer.
if (data_size == 0) {
- data_size = std::distance(data, data_.end());
+ // For FQDN we get the size by actually reading the FQDN.
+ if (data_type == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+ // The size of the buffer holding an FQDN is always
+ // 1 bytes larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ } else {
+ data_size = std::distance(data, data_buf.end());
+ }
}
if (data_size > 0) {
buffers.push_back(OptionBuffer(data, data + data_size));
@@ -185,6 +372,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
case OPT_IPV6_ADDRESS_TYPE:
text << readAddress(index).toText();
break;
+ case OPT_FQDN_TYPE:
+ text << readFqdn(index);
+ break;
case OPT_STRING_TYPE:
text << readString(index);
break;
@@ -252,8 +442,31 @@ OptionCustom::readAddress(const uint32_t index) const {
return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
} else {
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
- << " IP address. Invalid buffer length " << buffers_[index].size());
+ << " IP address. Invalid buffer length "
+ << buffers_[index].size() << ".");
+ }
+}
+
+void
+OptionCustom::writeAddress(const asiolink::IOAddress& address,
+ const uint32_t index) {
+ using namespace isc::asiolink;
+
+ checkIndex(index);
+
+ if ((address.getFamily() == AF_INET &&
+ buffers_[index].size() != V4ADDRESS_LEN) ||
+ (address.getFamily() == AF_INET6 &&
+ buffers_[index].size() != V6ADDRESS_LEN)) {
+ isc_throw(BadDataTypeCast, "invalid address specified "
+ << address.toText() << ". Expected a valid IPv"
+ << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
+ << " address.");
}
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeAddress(address, buf);
+ std::swap(buf, buffers_[index]);
}
const OptionBuffer&
@@ -262,12 +475,50 @@ OptionCustom::readBinary(const uint32_t index) const {
return (buffers_[index]);
}
+void
+OptionCustom::writeBinary(const OptionBuffer& buf,
+ const uint32_t index) {
+ checkIndex(index);
+ buffers_[index] = buf;
+}
+
bool
OptionCustom::readBoolean(const uint32_t index) const {
checkIndex(index);
return (OptionDataTypeUtil::readBool(buffers_[index]));
}
+void
+OptionCustom::writeBoolean(const bool value, const uint32_t index) {
+ checkIndex(index);
+
+ buffers_[index].clear();
+ OptionDataTypeUtil::writeBool(value, buffers_[index]);
+}
+
+std::string
+OptionCustom::readFqdn(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readFqdn(buffers_[index]));
+}
+
+void
+OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
+ checkIndex(index);
+
+ // Create a temporay buffer where the FQDN will be written.
+ OptionBuffer buf;
+ // Try to write to the temporary buffer rather than to the
+ // buffers_ member directly guarantees that we don't modify
+ // (clear) buffers_ until we are sure that the provided FQDN
+ // is valid.
+ OptionDataTypeUtil::writeFqdn(fqdn, buf);
+ // If we got to this point it means that the FQDN is valid.
+ // We can move the contents of the teporary buffer to the
+ // target buffer.
+ std::swap(buffers_[index], buf);
+}
+
std::string
OptionCustom::readString(const uint32_t index) const {
checkIndex(index);
@@ -275,11 +526,26 @@ OptionCustom::readString(const uint32_t index) const {
}
void
+OptionCustom::writeString(const std::string& text, const uint32_t index) {
+ checkIndex(index);
+
+ // Let's clear a buffer as we want to replace the value of the
+ // whole buffer. If we fail to clear the buffer the data will
+ // be appended.
+ buffers_[index].clear();
+ // If the text value is empty we can leave because the buffer
+ // is already empty.
+ if (!text.empty()) {
+ OptionDataTypeUtil::writeString(text, buffers_[index]);
+ }
+}
+
+void
OptionCustom::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
data_ = OptionBuffer(begin, end);
// Chop the buffer stored in data_ into set of sub buffers.
- createBuffers();
+ createBuffers(data_);
}
uint16_t
@@ -311,7 +577,7 @@ void OptionCustom::setData(const OptionBufferConstIter first,
// Chop the data_ buffer into set of buffers that represent
// option fields data.
- createBuffers();
+ createBuffers(data_);
}
std::string OptionCustom::toText(int indent) {
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index ad248c5..d9a4c18 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.h
@@ -40,6 +40,17 @@ public:
/// @brief Constructor, used for options to be sent.
///
+ /// This constructor creates an instance of an option with default
+ /// data set for all data fields. The option buffers are allocated
+ /// according to data size being stored in particular data fields.
+ /// For variable size data empty buffers are created.
+ ///
+ /// @param def option definition.
+ /// @param u specifies universe (V4 or V6)
+ OptionCustom(const OptionDefinition& def, Universe u);
+
+ /// @brief Constructor, used for options to be sent.
+ ///
/// This constructor creates an instance of an option from the whole
/// supplied buffer. This constructor is mainly used to create an
/// instances of options to be stored in outgoing DHCP packets.
@@ -76,6 +87,37 @@ public:
OptionCustom(const OptionDefinition& def, Universe u,
OptionBufferConstIter first, OptionBufferConstIter last);
+ /// @brief Create new buffer and set its value as an IP address.
+ ///
+ /// @param address IPv4 or IPv6 address to be written to
+ /// a buffer being created.
+ void addArrayDataField(const asiolink::IOAddress& address);
+
+ /// @brief Create new buffer and store boolean value in it.
+ ///
+ /// @param value value to be stored in the created buffer.
+ void addArrayDataField(const bool value);
+
+ /// @brief Create new buffer and store integer value in it.
+ ///
+ /// @param value value to be stored in the created buffer.
+ /// @tparam T integer type of the value being stored.
+ template<typename T>
+ void addArrayDataField(const T value) {
+ checkArrayType();
+
+ OptionDataType data_type = definition_.getType();
+ if (OptionDataTypeTraits<T>::type != data_type) {
+ isc_throw(isc::dhcp::InvalidDataType,
+ "specified data type " << data_type << " does not"
+ " match the data type in an option definition");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeInt<T>(value, buf);
+ buffers_.push_back(buf);
+ }
+
/// @brief Return a number of the data fields.
///
/// @return number of data fields held by the option.
@@ -87,7 +129,17 @@ public:
///
/// @return IP address read from a buffer.
/// @throw isc::OutOfRange if index is out of range.
- asiolink::IOAddress readAddress(const uint32_t index) const;
+ asiolink::IOAddress readAddress(const uint32_t index = 0) const;
+
+ /// @brief Write an IP address into a buffer.
+ ///
+ /// @param address IP address being written.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::BadDataTypeCast if IP address is invalid.
+ void writeAddress(const asiolink::IOAddress& address,
+ const uint32_t index = 0);
/// @brief Read a buffer as binary data.
///
@@ -95,7 +147,13 @@ public:
///
/// @throw isc::OutOfRange if index is out of range.
/// @return read buffer holding binary data.
- const OptionBuffer& readBinary(const uint32_t index) const;
+ const OptionBuffer& readBinary(const uint32_t index = 0) const;
+
+ /// @brief Write binary data into a buffer.
+ ///
+ /// @param buf buffer holding binary data to be written.
+ /// @param index buffer index.
+ void writeBinary(const OptionBuffer& buf, const uint32_t index = 0);
/// @brief Read a buffer as boolean value.
///
@@ -103,7 +161,34 @@ public:
///
/// @throw isc::OutOfRange if index is out of range.
/// @return read boolean value.
- bool readBoolean(const uint32_t index) const;
+ bool readBoolean(const uint32_t index = 0) const;
+
+ /// @brief Write a boolean value into a buffer.
+ ///
+ /// @param value boolean value to be written.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writeBoolean(const bool value, const uint32_t index = 0);
+
+ /// @brief Read a buffer as FQDN.
+ ///
+ /// @param index buffer index.
+ /// @param [out] len number of bytes read from a buffer.
+ ///
+ /// @throw isc::OutOfRange if buffer index is out of range.
+ /// @throw isc::dhcp::BadDataTypeCast if a buffer being read
+ /// does not hold a valid FQDN.
+ /// @return string representation if FQDN.
+ std::string readFqdn(const uint32_t index = 0) const;
+
+ /// @brief Write an FQDN into a buffer.
+ ///
+ /// @param fqdn text representation of FQDN.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writeFqdn(const std::string& fqdn, const uint32_t index = 0);
/// @brief Read a buffer as integer value.
///
@@ -111,38 +196,15 @@ public:
/// @tparam integer type of a value being returned.
///
/// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::InvalidDataType if T is invalid.
/// @return read integer value.
template<typename T>
- T readInteger(const uint32_t index) const {
+ T readInteger(const uint32_t index = 0) const {
+ // Check that the index is not out of range.
checkIndex(index);
-
- // Check that the requested return type is a supported integer.
- if (!OptionDataTypeTraits<T>::integer_type) {
- isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
- " by readInteger is not supported integer type");
- }
-
- // Get the option definition type.
- OptionDataType data_type = definition_.getType();
- if (data_type == OPT_RECORD_TYPE) {
- const OptionDefinition::RecordFieldsCollection& record_fields =
- definition_.getRecordFields();
- // When we initialized buffers we have already checked that
- // the number of these buffers is equal to number of option
- // fields in the record so the condition below should be met.
- assert(index < record_fields.size());
- // Get the data type to be returned.
- data_type = record_fields[index];
- }
-
- // Requested data type must match the data type in a record.
- if (OptionDataTypeTraits<T>::type != data_type) {
- isc_throw(isc::dhcp::InvalidDataType,
- "unable to read option field with index " << index
- << " as integer value. The field's data type"
- << data_type << " does not match the integer type"
- << "returned by the readInteger function.");
- }
+ // Check that T points to a valid integer type and this type
+ // is consistent with an option definition.
+ checkDataType<T>(index);
// When we created the buffer we have checked that it has a
// valid size so this condition here should be always fulfiled.
assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
@@ -150,13 +212,43 @@ public:
return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
}
+ /// @brief Write an integer value into a buffer.
+ ///
+ /// @param value integer value to be written.
+ /// @param index buffer index.
+ /// @tparam T integer type of a value being written.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::InvalidDataType if T is invalid.
+ template<typename T>
+ void writeInteger(const T value, const uint32_t index = 0) {
+ // Check that the index is not out of range.
+ checkIndex(index);
+ // Check that T points to a valid integer type and this type
+ // is consistent with an option definition.
+ checkDataType<T>(index);
+ // Get some temporary buffer.
+ OptionBuffer buf;
+ // Try to write to the buffer.
+ OptionDataTypeUtil::writeInt<T>(value, buf);
+ // If successful, replace the old buffer with new one.
+ std::swap(buffers_[index], buf);
+ }
+
/// @brief Read a buffer as string value.
///
/// @param index buffer index.
///
/// @return string value read from buffer.
/// @throw isc::OutOfRange if index is out of range.
- std::string readString(const uint32_t index) const;
+ std::string readString(const uint32_t index = 0) const;
+
+ /// @brief Write a string value into a buffer.
+ ///
+ /// @param text the string value to be written.
+ /// @param buffer index.
+ void writeString(const std::string& text,
+ const uint32_t index = 0);
/// @brief Parses received buffer.
///
@@ -201,6 +293,33 @@ protected:
private:
+ /// @brief Verify that the option comprises an array of values.
+ ///
+ /// This helper function is used by createArrayEntry functions
+ /// and throws an exception if the particular option is not
+ /// an array.
+ ///
+ /// @throw isc::InvalidOperation if option is not an array.
+ inline void checkArrayType() const {
+ if (!definition_.getArrayType()) {
+ isc_throw(InvalidOperation, "failed to add new array entry to an"
+ << " option. The option is not an array.");
+ }
+ }
+
+ /// @brief Verify that the integer type is consistent with option
+ /// field type.
+ ///
+ /// This convenience function checks that the data type specified as T
+ /// is consistent with a type of a data field identified by index.
+ ///
+ /// @param index data field index.
+ /// @tparam data type to be validated.
+ ///
+ /// @throw isc::dhcp::InvalidDataType if the type is invalid.
+ template<typename T>
+ void checkDataType(const uint32_t index) const;
+
/// @brief Check if data field index is valid.
///
/// @param index Data field index to check.
@@ -208,9 +327,14 @@ private:
/// @throw isc::OutOfRange if index is out of range.
void checkIndex(const uint32_t index) const;
- /// @brief Create collection of buffers representing data field values.
+ /// @brief Create a collection of non initialized buffers.
void createBuffers();
+ /// @brief Create collection of buffers representing data field values.
+ ///
+ /// @param data_buf a buffer to be parsed.
+ void createBuffers(const OptionBuffer& data_buf);
+
/// @brief Return a text representation of a data field.
///
/// @param data_type data type of a field.
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
index 46aa663..0c512d7 100644
--- a/src/lib/dhcp/option_data_types.cc
+++ b/src/lib/dhcp/option_data_types.cc
@@ -13,6 +13,8 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/option_data_types.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
#include <util/encode/hex.h>
namespace isc {
@@ -207,9 +209,46 @@ OptionDataTypeUtil::writeBool(const bool value,
}
std::string
+OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
+ // If buffer is empty emit an error.
+ if (buf.empty()) {
+ isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer."
+ << " The buffer is empty.");
+ }
+ // Set up an InputBuffer so as we can use isc::dns::Name object to get the FQDN.
+ isc::util::InputBuffer in_buf(static_cast<const void*>(&buf[0]), buf.size());
+ try {
+ // Try to create an object from the buffer. If exception is thrown
+ // it means that the buffer doesn't hold a valid domain name (invalid
+ // syntax).
+ isc::dns::Name name(in_buf);
+ return (name.toText());
+ } catch (const isc::Exception& ex) {
+ // Unable to convert the data in the buffer into FQDN.
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
+ std::vector<uint8_t>& buf) {
+ try {
+ isc::dns::Name name(fqdn);
+ isc::dns::LabelSequence labels(name);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ buf.insert(buf.end(), data, data + read_len);
+ }
+ } catch (const isc::Exception& ex) {
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+std::string
OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
std::string value;
- if (buf.size() > 0) {
+ if (!buf.empty()) {
value.insert(value.end(), buf.begin(), buf.end());
}
return (value);
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
index afc6d93..ff5789d 100644
--- a/src/lib/dhcp/option_data_types.h
+++ b/src/lib/dhcp/option_data_types.h
@@ -225,11 +225,13 @@ public:
/// @return data type size or zero for variable length types.
static int getDataTypeLen(const OptionDataType data_type);
- /// @brief Read IPv4 or IPv6 addres from a buffer.
+ /// @brief Read IPv4 or IPv6 address from a buffer.
///
/// @param buf input buffer.
/// @param family address family: AF_INET or AF_INET6.
///
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated.
/// @return address being read.
static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf,
const short family);
@@ -252,6 +254,9 @@ public:
/// @brief Read boolean value from a buffer.
///
/// @param buf input buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated or the value is invalid (neither 1 nor 0).
/// @return boolean value read from a buffer.
static bool readBool(const std::vector<uint8_t>& buf);
@@ -268,6 +273,9 @@ public:
///
/// @param buf input buffer.
/// @tparam integer type of the returned value.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data in the buffer
+ /// is truncated.
/// @return integer value being read.
template<typename T>
static T readInt(const std::vector<uint8_t>& buf) {
@@ -276,7 +284,12 @@ public:
" by readInteger is unsupported integer type");
}
- assert(buf.size() == OptionDataTypeTraits<T>::len);
+ if (buf.size() < OptionDataTypeTraits<T>::len) {
+ isc_throw(isc::dhcp::BadDataTypeCast,
+ "failed to read an integer value from a buffer"
+ << " - buffer is truncated.");
+ }
+
T value;
switch (OptionDataTypeTraits<T>::len) {
case 1:
@@ -332,6 +345,33 @@ public:
}
}
+ /// @brief Read FQDN from a buffer as a string value.
+ ///
+ /// The format of an FQDN within a buffer complies with RFC1035,
+ /// section 3.1.
+ ///
+ /// @param buf input buffer holding a FQDN.
+ ///
+ /// @throw BadDataTypeCast if a FQDN stored within a buffer is
+ /// invalid (e.g. empty, contains invalid characters, truncated).
+ /// @return fully qualified domain name in a text form.
+ static std::string readFqdn(const std::vector<uint8_t>& buf);
+
+ /// @brief Append FQDN into a buffer.
+ ///
+ /// This method appends the Fully Qualified Domain Name (FQDN)
+ /// represented as string value into a buffer. The format of
+ /// the FQDN being stored into a buffer complies with RFC1035,
+ /// section 3.1.
+ ///
+ /// @param fqdn fully qualified domain name to be written.
+ /// @param [out] buf output buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if provided FQDN
+ /// is invalid.
+ static void writeFqdn(const std::string& fqdn,
+ std::vector<uint8_t>& buf);
+
/// @brief Read string 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 58d0c4b..2248bd7 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -19,6 +19,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <dhcp/option_definition.h>
#include <util/encode/hex.h>
@@ -78,53 +79,81 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
validate();
-
+
try {
- if (type_ == OPT_BINARY_TYPE) {
+ switch(type_) {
+ case OPT_EMPTY_TYPE:
+ return (factoryEmpty(u, type));
+
+ case OPT_BINARY_TYPE:
return (factoryGeneric(u, type, begin, end));
- } else if (type_ == OPT_IPV6_ADDRESS_TYPE && array_type_) {
- return (factoryAddrList6(type, begin, end));
+ case OPT_UINT8_TYPE:
+ return (array_type_ ? factoryGeneric(u, type, begin, end) :
+ factoryInteger<uint8_t>(u, type, begin, end));
- } else if (type_ == OPT_IPV4_ADDRESS_TYPE && array_type_) {
- return (factoryAddrList4(type, begin, end));
+ case OPT_INT8_TYPE:
+ return (array_type_ ? factoryGeneric(u, type, begin, end) :
+ factoryInteger<int8_t>(u, type, begin, end));
- } else if (type_ == OPT_EMPTY_TYPE) {
- return (factoryEmpty(u, type));
+ case OPT_UINT16_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+ factoryInteger<uint16_t>(u, type, begin, end));
- } else if (u == Option::V6 &&
- code_ == D6O_IA_NA &&
- haveIA6Format()) {
- return (factoryIA6(type, begin, end));
+ case OPT_INT16_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+ factoryInteger<int16_t>(u, type, begin, end));
- } else if (u == Option::V6 &&
- code_ == D6O_IAADDR &&
- haveIAAddr6Format()) {
- return (factoryIAAddr6(type, begin, end));
+ case OPT_UINT32_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+ factoryInteger<uint32_t>(u, type, begin, end));
- } else if (type_ == OPT_UINT8_TYPE) {
- if (array_type_) {
- return (factoryGeneric(u, type, begin, end));
- } else {
- return (factoryInteger<uint8_t>(u, type, begin, end));
- }
+ case OPT_INT32_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+ factoryInteger<int32_t>(u, type, begin, end));
- } else if (type_ == OPT_UINT16_TYPE) {
+ case OPT_IPV4_ADDRESS_TYPE:
+ // If definition specifies that an option is an array
+ // of IPv4 addresses we return an instance of specialized
+ // class (OptionAddrLst4). For non-array types there is no
+ // specialized class yet implemented so we drop through
+ // to return an instance of OptionCustom.
if (array_type_) {
- return (factoryIntegerArray<uint16_t>(type, begin, end));
- } else {
- return (factoryInteger<uint16_t>(u, type, begin, end));
+ return (factoryAddrList4(type, begin, end));
}
+ break;
- } else if (type_ == OPT_UINT32_TYPE) {
+ case OPT_IPV6_ADDRESS_TYPE:
+ // Handle array type only here (see comments for
+ // OPT_IPV4_ADDRESS_TYPE case).
if (array_type_) {
- return (factoryIntegerArray<uint32_t>(type, begin, end));
- } else {
- return (factoryInteger<uint32_t>(u, type, begin, end));
+ return (factoryAddrList6(type, begin, end));
+ }
+ break;
+
+ default:
+ if (u == Option::V6) {
+ if ((code_ == D6O_IA_NA || code_ == D6O_IA_PD) &&
+ haveIA6Format()) {
+ // Return Option6IA instance for IA_PD and IA_NA option
+ // types only. We don't want to return Option6IA for other
+ // options that comprise 3 UINT32 data fields because
+ // Option6IA accessors' and modifiers' names are derived
+ // from the IA_NA and IA_PD options' field names: IAID,
+ // T1, T2. Using functions such as getIAID, getT1 etc. for
+ // options other than IA_NA and IA_PD would be bad practice
+ // and cause confusion.
+ return (factoryIA6(type, begin, end));
+
+ } else if (code_ == D6O_IAADDR && haveIAAddr6Format()) {
+ // Rerurn Option6IAAddr option instance for the IAADDR
+ // option only for the same reasons as described in
+ // for IA_NA and IA_PD above.
+ return (factoryIAAddr6(type, begin, end));
+ }
}
-
}
- return (factoryGeneric(u, type, begin, end));
+ return (OptionPtr(new OptionCustom(*this, u, OptionBuffer(begin, end))));
} catch (const Exception& ex) {
isc_throw(InvalidOptionValue, ex.what());
@@ -144,7 +173,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionBuffer buf;
if (!array_type_ && type_ != OPT_RECORD_TYPE) {
- if (values.size() == 0) {
+ if (values.empty()) {
isc_throw(InvalidOptionValue, "no option value specified");
}
writeToBuffer(values[0], type_, buf);
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 3d48ef2..ca40428 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -246,7 +246,7 @@ public:
/// @throw MalformedOptionDefinition if option definition is invalid.
/// @throw InvalidOptionValue if data for the option is invalid.
OptionPtr optionFactory(Option::Universe u, uint16_t type,
- const OptionBuffer& buf) const;
+ const OptionBuffer& buf = OptionBuffer()) const;
/// @brief Option factory.
///
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
new file mode 100644
index 0000000..f604ea3
--- /dev/null
+++ b/src/lib/dhcp/std_option_defs.h
@@ -0,0 +1,155 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef STD_OPTION_DEFS_H
+#define STD_OPTION_DEFS_H
+
+#include <dhcp/option_data_types.h>
+
+namespace {
+
+/// @brief Declare an array holding parameters used to create instance
+/// of a definition for option comprising a record of data fields.
+///
+/// @param name name of the array being declared.
+/// @param types data types of fields that belong to the record.
+#ifndef RECORD_DECL
+#define RECORD_DECL(name, types...) static OptionDataType name[] = { types }
+#endif
+
+/// @brief A pair of values: one pointing to the array holding types of
+/// data fields belonging to the record, and size of this array.
+///
+/// @param name name of the array holding data fields' types.
+#ifndef RECORD_DEF
+#define RECORD_DEF(name) name, sizeof(name) / sizeof(name[0])
+#endif
+
+using namespace isc::dhcp;
+
+/// @brief Parameters being used to make up an option definition.
+struct OptionDefParams {
+ const char* name; // option name
+ uint16_t code; // option code
+ OptionDataType type; // data type
+ bool array; // is array
+ OptionDataType* records; // record fields
+ size_t records_size; // number of fields in a record
+};
+
+// client-fqdn
+RECORD_DECL(clientFqdnRecords, OPT_UINT8_TYPE, OPT_FQDN_TYPE);
+// geoconf-civic
+RECORD_DECL(geoconfCivicRecords, OPT_UINT8_TYPE, OPT_UINT16_TYPE,
+ OPT_BINARY_TYPE);
+// iaddr
+RECORD_DECL(iaaddrRecords, OPT_IPV6_ADDRESS_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT32_TYPE);
+// ia-na
+RECORD_DECL(ianaRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-pd
+RECORD_DECL(iapdRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-prefix
+RECORD_DECL(iaPrefixRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT8_TYPE, OPT_BINARY_TYPE);
+// lq-query
+RECORD_DECL(lqQueryRecords, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE);
+// lq-relay-data
+RECORD_DECL(lqRelayData, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE);
+// remote-id
+RECORD_DECL(remoteIdRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// status-code
+RECORD_DECL(statusCodeRecords, OPT_UINT16_TYPE, OPT_STRING_TYPE);
+// vendor-class
+RECORD_DECL(vendorClassRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// vendor-opts
+RECORD_DECL(vendorOptsRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+
+/// Stdandard DHCPv6 option definitions.
+static const OptionDefParams OPTION_DEF_PARAMS6[] = {
+ { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false },
+ { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false },
+ { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(ianaRecords) },
+ { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false },
+ { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(iaaddrRecords) },
+ { "oro", D6O_ORO, OPT_UINT16_TYPE, true },
+ { "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false },
+ { "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false },
+ { "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false },
+ // Unfortunatelly the AUTH option contains a 64-bit data field
+ // called 'replay-detection' that can't be added as a record
+ // field to a custom option. Also, there is no dedicated
+ // option class to handle it so we simply return binary
+ // option type for now.
+ // @todo implement a class to handle AUTH option.
+ { "auth", D6O_AUTH, OPT_BINARY_TYPE, false },
+ { "unicast", D6O_UNICAST, OPT_IPV6_ADDRESS_TYPE, false },
+ { "status-code", D6O_STATUS_CODE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(statusCodeRecords) },
+ { "rapid-commit", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false },
+ { "user-class", D6O_USER_CLASS, OPT_BINARY_TYPE, false },
+ { "vendor-class", D6O_VENDOR_CLASS, OPT_RECORD_TYPE, false,
+ RECORD_DEF(vendorClassRecords) },
+ { "vendor-opts", D6O_VENDOR_OPTS, OPT_RECORD_TYPE, false,
+ RECORD_DEF(vendorOptsRecords) },
+ { "interface-id", D6O_INTERFACE_ID, OPT_BINARY_TYPE, false },
+ { "reconf-msg", D6O_RECONF_MSG, OPT_UINT8_TYPE, false },
+ { "reconf-accept", D6O_RECONF_ACCEPT, OPT_EMPTY_TYPE, false },
+ { "sip-server-dns", D6O_SIP_SERVERS_DNS, OPT_FQDN_TYPE, true },
+ { "sip-server-addr", D6O_SIP_SERVERS_ADDR, OPT_IPV6_ADDRESS_TYPE, true },
+ { "dns-servers", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
+ { "domain-search", D6O_DOMAIN_SEARCH, OPT_FQDN_TYPE, true },
+ { "ia-pd", D6O_IA_PD, OPT_RECORD_TYPE, false, RECORD_DEF(iapdRecords) },
+ { "iaprefix", D6O_IAPREFIX, OPT_RECORD_TYPE, false,
+ RECORD_DEF(iaPrefixRecords) },
+ { "nis-servers", D6O_NIS_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
+ { "nisp-servers", D6O_NISP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
+ { "nis-domain-name", D6O_NIS_DOMAIN_NAME, OPT_FQDN_TYPE, true },
+ { "nisp-domain-name", D6O_NISP_DOMAIN_NAME, OPT_FQDN_TYPE, true },
+ { "sntp-servers", D6O_SNTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
+ { "information-refresh-time", D6O_INFORMATION_REFRESH_TIME,
+ OPT_UINT32_TYPE, false },
+ { "bcmcs-server-dns", D6O_BCMCS_SERVER_D, OPT_FQDN_TYPE, true },
+ { "bcmcs-server-addr", D6O_BCMCS_SERVER_A, OPT_IPV6_ADDRESS_TYPE, true },
+ { "geoconf-civic", D6O_GEOCONF_CIVIC, OPT_RECORD_TYPE, false,
+ RECORD_DEF(geoconfCivicRecords) },
+ { "remote-id", D6O_REMOTE_ID, OPT_RECORD_TYPE, false,
+ RECORD_DEF(remoteIdRecords) },
+ { "subscriber-id", D6O_SUBSCRIBER_ID, OPT_BINARY_TYPE, false },
+ { "client-fqdn", D6O_CLIENT_FQDN, OPT_RECORD_TYPE, false,
+ RECORD_DEF(clientFqdnRecords) },
+ { "pana-agent", D6O_PANA_AGENT, OPT_IPV6_ADDRESS_TYPE, true },
+ { "new-posix-timezone", D6O_NEW_POSIX_TIMEZONE, OPT_STRING_TYPE, false },
+ { "new-tzdb-timezone", D6O_NEW_TZDB_TIMEZONE, OPT_STRING_TYPE, false },
+ { "ero", D6O_ERO, OPT_UINT16_TYPE, true },
+ { "lq-query", D6O_LQ_QUERY, OPT_RECORD_TYPE, false,
+ RECORD_DEF(lqQueryRecords) },
+ { "client-data", D6O_CLIENT_DATA, OPT_EMPTY_TYPE, false },
+ { "clt-time", D6O_CLT_TIME, OPT_UINT32_TYPE, false },
+ { "lq-relay-data", D6O_LQ_RELAY_DATA, OPT_RECORD_TYPE, false,
+ RECORD_DEF(lqRelayData) },
+ { "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true }
+
+ // @todo There is still a bunch of options for which we have to provide
+ // definitions but we don't do it because they are not really
+ // critical right now.
+};
+
+/// Number of option definitions defined.
+const int OPTION_DEF_PARAMS_SIZE6 =
+ sizeof(OPTION_DEF_PARAMS6) / sizeof(OPTION_DEF_PARAMS6[0]);
+
+}; // anonymous namespace
+
+#endif // STD_OPTION_DEFS_H
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index e66d700..5799d58 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -35,6 +35,7 @@ libdhcp___unittests_SOURCES += option6_ia_unittest.cc
libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
libdhcp___unittests_SOURCES += option6_int_array_unittest.cc
libdhcp___unittests_SOURCES += option6_int_unittest.cc
+libdhcp___unittests_SOURCES += option_data_types_unittest.cc
libdhcp___unittests_SOURCES += option_definition_unittest.cc
libdhcp___unittests_SOURCES += option_custom_unittest.cc
libdhcp___unittests_SOURCES += option_unittest.cc
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index af3d6ca..8ead521 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -22,6 +22,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <util/buffer.h>
#include <gtest/gtest.h>
@@ -68,8 +69,8 @@ public:
const std::type_info& expected_type) {
// Get all option definitions, we will use them to extract
// the definition for a particular option code.
- // We don't have to initialize option deinitions here because they
- // are initialized in the class'es constructor.
+ // We don't have to initialize option definitions here because they
+ // are initialized in the class's constructor.
OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
// Get the container index #1. This one allows for searching
// option definitions using option code.
@@ -90,13 +91,15 @@ public:
ASSERT_NO_THROW(def->validate());
OptionPtr option;
// Create the option.
- ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf));
+ ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf))
+ << "Option creation failed to option code " << code;
// Make sure it is not NULL.
ASSERT_TRUE(option);
// And the actual object type is the one that we expect.
// Note that for many options there are dedicated classes
// derived from Option class to represent them.
- EXPECT_TRUE(typeid(*option) == expected_type);
+ EXPECT_TRUE(typeid(*option) == expected_type)
+ << "Invalid class returned for option code " << code;
}
};
@@ -399,24 +402,149 @@ TEST_F(LibDhcpTest, unpackOptions4) {
// This test have to be extended once all option definitions are
// created.
TEST_F(LibDhcpTest, stdOptionDefs6) {
- LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
- typeid(Option6IA));
- LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
- typeid(Option6IAAddr));
- LibDhcpTest::testStdOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
- typeid(Option6IntArray<uint16_t>));
- LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
- typeid(Option6Int<uint16_t>));
- LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
- typeid(Option6AddrLst));
+
+ // Create a buffer that holds dummy option data.
+ // It will be used to create most of the options.
+ std::vector<uint8_t> buf(48, 1);
+
+ // Prepare buffer holding an array of FQDNs.
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn_buf(data, data + sizeof(data));
+
+ // The CLIENT_FQDN holds a uint8_t value and FQDN. We have
+ // to add the uint8_t value to it and then append the buffer
+ // holding some valid FQDN.
+ std::vector<uint8_t> client_fqdn_buf(1);
+ client_fqdn_buf.insert(client_fqdn_buf.end(), fqdn_buf.begin(),
+ fqdn_buf.end());
+
+ // The actual test starts here for all supported option codes.
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, buf, typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, buf,
+ typeid(Option6Int<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, buf, typeid(Option6IAAddr));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ORO, buf,
+ typeid(Option6IntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, buf,
+ typeid(Option6Int<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, buf,
+ typeid(Option6Int<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, buf,
+ typeid(Option6Int<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, buf, typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME,
+ buf, typeid(Option6Int<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, buf,typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ERO, buf,
+ typeid(Option6IntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, buf,
+ typeid(Option6Int<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, buf,
+ typeid(Option6AddrLst));
}
}
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index 34969ee..044279b 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -106,6 +106,17 @@ TEST_F(OptionCustomTest, constructor) {
EXPECT_EQ(Option::V4, option->getUniverse());
EXPECT_EQ(232, option->getType());
+
+ // Try to create an option using 'empty data' constructor
+ OptionDefinition opt_def3("OPTION_FOO", 1000, "uint32");
+
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def3, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(1000, option->getType());
}
// The purpose of this test is to verify that 'empty' option definition can
@@ -113,9 +124,10 @@ TEST_F(OptionCustomTest, constructor) {
TEST_F(OptionCustomTest, emptyData) {
OptionDefinition opt_def("OPTION_FOO", 232, "empty");
+ OptionBuffer buf;
boost::scoped_ptr<OptionCustom> option;
ASSERT_NO_THROW(
- option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer()));
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
);
ASSERT_TRUE(option);
@@ -159,8 +171,10 @@ TEST_F(OptionCustomTest, binaryData) {
EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
// Check that option with "no data" is rejected.
+ buf_in.clear();
EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V4, buf_in.begin(),
+ buf_in.end())),
isc::OutOfRange
);
}
@@ -197,12 +211,46 @@ TEST_F(OptionCustomTest, booleanData) {
EXPECT_FALSE(value);
// Check that the option with "no data" is rejected.
+ buf.clear();
EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
isc::OutOfRange
);
}
+// The purpose of this test is to verify that the data from a buffer
+// can be read as FQDN.
+TEST_F(OptionCustomTest, fqdnData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ };
+
+ 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.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ std::string domain0 = option->readFqdn(0);
+ EXPECT_EQ("mydomain.example.com.", domain0);
+
+ // 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() + 4)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
// The purpose of this test is to verify that the option definition comprising
// 16-bit signed integer value can be used to create an instance of custom option.
TEST_F(OptionCustomTest, int16Data) {
@@ -338,6 +386,7 @@ TEST_F(OptionCustomTest, ipv6AddressData) {
);
}
+
// The purpose of this test is to verify that the option definition comprising
// string value can be used to create an instance of custom option.
TEST_F(OptionCustomTest, stringData) {
@@ -365,8 +414,9 @@ TEST_F(OptionCustomTest, stringData) {
EXPECT_EQ("hello world!", value);
// Check that option will not be created if empty buffer is provided.
+ buf.clear();
EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
isc::OutOfRange
);
}
@@ -419,8 +469,9 @@ TEST_F(OptionCustomTest, booleanDataArray) {
// Check that empty buffer can't be used to create option holding
// array of boolean values.
+ buf.clear();
EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
isc::OutOfRange
);
}
@@ -472,7 +523,6 @@ TEST_F(OptionCustomTest, uint32DataArray) {
buf.begin() + 3)),
isc::OutOfRange
);
-
}
// The purpose of this test is to verify that the option definition comprising
@@ -575,6 +625,45 @@ TEST_F(OptionCustomTest, ipv6AddressDataArray) {
);
}
+// The purpose of this test is to verify that the option comprising
+// an array of FQDN values can be created from a buffer which holds
+// multiple FQDN values encoded as described in the RFC1035, section
+// 3.1
+TEST_F(OptionCustomTest, fqdnDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn", true);
+
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+
+ // Create a buffer that holds two FQDNs.
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Create an option from using a buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We expect that two FQDN values have been extracted
+ // from a buffer.
+ ASSERT_EQ(2, option->getDataFieldsNum());
+
+ // Validate both values.
+ std::string domain0 = option->readFqdn(0);
+ EXPECT_EQ("mydomain.example.com.", domain0);
+
+ std::string domain1 = option->readFqdn(1);
+ EXPECT_EQ("example.com.", domain1);
+}
+
// The purpose of this test is to verify that the option definition comprising
// a record of various data fields can be used to create an instance of
// custom option.
@@ -584,20 +673,30 @@ TEST_F(OptionCustomTest, recordData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
ASSERT_NO_THROW(opt_def.addRecordField("string"));
+ const char fqdn_data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ };
+
OptionBuffer buf;
- // Initialize field 0.
+ // Initialize field 0 to 8712.
writeInt<uint16_t>(8712, buf);
// Initialize field 1 to 'true'
buf.push_back(static_cast<unsigned short>(1));
- // Initialize field 2 to IPv4 address.
+ // Initialize field 2 to 'mydomain.example.com'.
+ buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data));
+ // Initialize field 3 to IPv4 address.
writeAddress(IOAddress("192.168.0.1"), buf);
- // Initialize field 3 to IPv6 address.
+ // Initialize field 4 to IPv6 address.
writeAddress(IOAddress("2001:db8:1::1"), buf);
- // Initialize field 4 to string value.
+ // Initialize field 5 to string value.
writeString("ABCD", buf);
boost::scoped_ptr<OptionCustom> option;
@@ -606,8 +705,8 @@ TEST_F(OptionCustomTest, recordData) {
);
ASSERT_TRUE(option);
- // We should have 5 data fields.
- ASSERT_EQ(5, option->getDataFieldsNum());
+ // We should have 6 data fields.
+ ASSERT_EQ(6, option->getDataFieldsNum());
// Verify value in the field 0.
uint16_t value0 = 0;
@@ -620,19 +719,24 @@ TEST_F(OptionCustomTest, recordData) {
EXPECT_TRUE(value1);
// Verify value in the field 2.
- IOAddress value2("127.0.0.1");
- ASSERT_NO_THROW(value2 = option->readAddress(2));
- EXPECT_EQ("192.168.0.1", value2.toText());
+ std::string value2 = "";
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("mydomain.example.com.", value2);
// Verify value in the field 3.
- IOAddress value3("::1");
+ IOAddress value3("127.0.0.1");
ASSERT_NO_THROW(value3 = option->readAddress(3));
- EXPECT_EQ("2001:db8:1::1", value3.toText());
+ EXPECT_EQ("192.168.0.1", value3.toText());
// Verify value in the field 4.
- std::string value4;
- ASSERT_NO_THROW(value4 = option->readString(4));
- EXPECT_EQ("ABCD", value4);
+ IOAddress value4("::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::1", value4.toText());
+
+ // Verify value in the field 5.
+ std::string value5;
+ ASSERT_NO_THROW(value5 = option->readString(5));
+ EXPECT_EQ("ABCD", value5);
}
// The purpose of this test is to verify that truncated buffer
@@ -683,6 +787,413 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
);
}
+// The purpose of this test is to verify that an option comprising
+// single data field with binary data can be used and that this
+// binary data is properly initialized to a default value. This
+// test also checks that it is possible to override this default
+// value.
+TEST_F(OptionCustomTest, setBinaryData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "binary");
+
+ // 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);
+
+ // Get the default binary value.
+ OptionBuffer buf;
+ ASSERT_NO_THROW(option->readBinary());
+ // The buffer is by default empty.
+ EXPECT_TRUE(buf.empty());
+ // Prepare input buffer with some dummy data.
+ OptionBuffer buf_in(10);
+ for (int i = 0; i < buf_in.size(); ++i) {
+ buf_in[i] = i;
+ }
+ // Try to override the default binary buffer.
+ ASSERT_NO_THROW(option->writeBinary(buf_in));
+ // And check that it has been actually overriden.
+ ASSERT_NO_THROW(buf = option->readBinary());
+ ASSERT_EQ(buf_in.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that an option comprising
+// single boolean data field can be created and that its default
+// value can be overriden by a new value.
+TEST_F(OptionCustomTest, setBooleanData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
+
+ // 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);
+ // Check that the default boolean value is false.
+ bool value = false;
+ ASSERT_NO_THROW(value = option->readBoolean());
+ EXPECT_FALSE(value);
+ // Check that we can override the default value.
+ ASSERT_NO_THROW(option->writeBoolean(true));
+ // Finally, check that it has been actually overriden.
+ ASSERT_NO_THROW(value = option->readBoolean());
+ EXPECT_TRUE(value);
+}
+
+/// The purpose of this test is to verify that the data field value
+/// can be overriden by a new value.
+TEST_F(OptionCustomTest, setUint32Data) {
+ // Create a definition of an option that holds single
+ // uint32 value.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "uint32");
+
+ // 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);
+
+ // The default value for integer data fields is 0.
+ uint32_t value = 0;
+ ASSERT_NO_THROW(option->readInteger<uint32_t>());
+ EXPECT_EQ(0, value);
+
+ // Try to set the data field value to something different
+ // than 0.
+ ASSERT_NO_THROW(option->writeInteger<uint32_t>(1234));
+
+ // Verify that it has been set.
+ ASSERT_NO_THROW(value = option->readInteger<uint32_t>());
+ EXPECT_EQ(1234, value);
+}
+
+// The purpose of this test is to verify that an option comprising
+// single IPv4 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv4AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address");
+
+ // 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);
+
+ asiolink::IOAddress address("127.0.0.1");
+ ASSERT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("0.0.0.0", address.toText());
+
+ EXPECT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1")));
+
+ EXPECT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("192.168.0.1", address.toText());
+}
+
+// The purpose of this test is to verify that an opton comprising
+// single IPv6 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv6AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
+
+ // 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);
+
+ asiolink::IOAddress address("::1");
+ ASSERT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("::", address.toText());
+
+ EXPECT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::1")));
+
+ EXPECT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("2001:db8:1::1", address.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// single string value can be created and that this value
+// is initialized to the default value. Also, this test checks that
+// this value can be overwritten by a new value.
+TEST_F(OptionCustomTest, setStringData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "string");
+
+ // 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);
+
+ // Get the default value of the option.
+ std::string value;
+ ASSERT_NO_THROW(value = option->readString());
+ // By default the string data field is empty.
+ EXPECT_TRUE(value.empty());
+ // Write some text to this field.
+ ASSERT_NO_THROW(option->writeString("hello world"));
+ // Check that it has been actually written.
+ EXPECT_NO_THROW(value = option->readString());
+ EXPECT_EQ("hello world", value);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// a default FQDN value can be created and that this value can be
+/// overriden after the option has been created.
+TEST_F(OptionCustomTest, setFqdnData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+ // 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);
+ // Read a default FQDN value from the option.
+ std::string fqdn;
+ ASSERT_NO_THROW(fqdn = option->readFqdn());
+ EXPECT_EQ(".", fqdn);
+ // Try override the default FQDN value.
+ ASSERT_NO_THROW(option->writeFqdn("example.com"));
+ // Check that the value has been actually overriden.
+ ASSERT_NO_THROW(fqdn = option->readFqdn());
+ EXPECT_EQ("example.com.", fqdn);
+}
+
+// The purpose of this test is to verify that an option carrying
+// an array of boolean values can be created with no values
+// initially and that values can be later added to it.
+TEST_F(OptionCustomTest, setBooleanDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "boolean", 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 should contain no values.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add some boolean values to it.
+ ASSERT_NO_THROW(option->addArrayDataField(true));
+ ASSERT_NO_THROW(option->addArrayDataField(false));
+ ASSERT_NO_THROW(option->addArrayDataField(true));
+
+ // Verify that the new data fields can be added.
+ bool value0 = false;
+ ASSERT_NO_THROW(value0 = option->readBoolean(0));
+ EXPECT_TRUE(value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ bool value2 = false;
+ ASSERT_NO_THROW(value2 = option->readBoolean(2));
+ EXPECT_TRUE(value2);
+}
+
+// The purpose of this test is to verify that am option carying
+// an array of 16-bit signed integer values can be created with
+// no values initially and that the values can be later added to it.
+TEST_F(OptionCustomTest, setUint16DataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "uint16", 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 should contain no values.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new data fields holding integer values.
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(67));
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(876));
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(32222));
+
+ // We should now have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that the values have been correctly set.
+ uint16_t value0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(67, value0);
+ uint16_t value1;
+ ASSERT_NO_THROW(value1 = option->readInteger<uint16_t>(1));
+ EXPECT_EQ(876, value1);
+ uint16_t value2;
+ ASSERT_NO_THROW(value2 = option->readInteger<uint16_t>(2));
+ EXPECT_EQ(32222, value2);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv4 address can be created with no addresses and that
+/// multiple IPv4 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv4AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address", 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);
+
+ // Expect that the array does not contain any data fields yet.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 IPv4 addresses.
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.1")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.2")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.3")));
+
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that all IP addresses have been set correctly.
+ IOAddress address0("127.0.0.1");
+ ASSERT_NO_THROW(address0 = option->readAddress(0));
+ EXPECT_EQ("192.168.0.1", address0.toText());
+ IOAddress address1("127.0.0.1");
+ ASSERT_NO_THROW(address1 = option->readAddress(1));
+ EXPECT_EQ("192.168.0.2", address1.toText());
+ IOAddress address2("127.0.0.1");
+ ASSERT_NO_THROW(address2 = option->readAddress(2));
+ EXPECT_EQ("192.168.0.3", address2.toText());
+
+ // Add invalid address (IPv6 instead of IPv4).
+ EXPECT_THROW(
+ option->addArrayDataField(IOAddress("2001:db8:1::1")),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv6 address can be created with no addresses and that
+/// multiple IPv6 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv6AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", 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 IPv6 addresses into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::1")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::2")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::3")));
+
+ // We should have now 3 addresses added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that they have correct values set.
+ IOAddress address0("::1");
+ ASSERT_NO_THROW(address0 = option->readAddress(0));
+ EXPECT_EQ("2001:db8:1::1", address0.toText());
+ IOAddress address1("::1");
+ ASSERT_NO_THROW(address1 = option->readAddress(1));
+ EXPECT_EQ("2001:db8:1::2", address1.toText());
+ IOAddress address2("::1");
+ ASSERT_NO_THROW(address2 = option->readAddress(2));
+ EXPECT_EQ("2001:db8:1::3", address2.toText());
+
+ // Add invalid address (IPv4 instead of IPv6).
+ EXPECT_THROW(
+ option->addArrayDataField(IOAddress("192.168.0.1")),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+TEST_F(OptionCustomTest, setRecordData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ // 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);
+
+ // The number of elements should be equal to number of elements
+ // in the record.
+ ASSERT_EQ(6, option->getDataFieldsNum());
+
+ // Check that the default values have been correctly set.
+ uint16_t value0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(0, value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ std::string value2;
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ(".", value2);
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("0.0.0.0", value3.toText());
+ IOAddress value4("2001:db8:1::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("::", value4.toText());
+ std::string value5 = "xyz";
+ ASSERT_NO_THROW(value5 = option->readString(5));
+ EXPECT_TRUE(value5.empty());
+
+ // Override each value with a new value.
+ ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
+ ASSERT_NO_THROW(option->writeBoolean(true, 1));
+ ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
+ ASSERT_NO_THROW(option->writeString("hello world", 5));
+
+ // Check that the new values have been correctly set.
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(1234, value0);
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("example.com.", value2);
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::100", value4.toText());
+ ASSERT_NO_THROW(value5 = option->readString(5));
+ EXPECT_EQ(value5, "hello world");
+}
+
// The purpose of this test is to verify that pack function for
// DHCPv4 custom option works correctly.
TEST_F(OptionCustomTest, pack4) {
@@ -901,6 +1412,4 @@ TEST_F(OptionCustomTest, invalidIndex) {
EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
}
-
-
} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc
new file mode 100644
index 0000000..748a84b
--- /dev/null
+++ b/src/lib/dhcp/tests/option_data_types_unittest.cc
@@ -0,0 +1,491 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/option_data_types.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test class for option data type utilities.
+class OptionDataTypesTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ OptionDataTypesTest() { }
+
+ /// @brief Write IP address into a buffer.
+ ///
+ /// @param address address to be written.
+ /// @param [out] buf output buffer.
+ void writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf) {
+ short family = address.getFamily();
+ if (family == AF_INET) {
+ asio::ip::address_v4::bytes_type buf_addr =
+ address.getAddress().to_v4().to_bytes();
+ buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+ } else if (family == AF_INET6) {
+ asio::ip::address_v6::bytes_type buf_addr =
+ address.getAddress().to_v6().to_bytes();
+ buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+ }
+ }
+
+ /// @brief Write integer (signed or unsigned) into a buffer.
+ ///
+ /// @param value integer value.
+ /// @param [out] buf output buffer.
+ /// @tparam integer type.
+ template<typename T>
+ void writeInt(T value, std::vector<uint8_t>& buf) {
+ for (int i = 0; i < sizeof(T); ++i) {
+ buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ }
+ }
+
+ /// @brief Write a string into a buffer.
+ ///
+ /// @param value string to be written into a buffer.
+ /// @param buf output buffer.
+ void writeString(const std::string& value,
+ std::vector<uint8_t>& buf) {
+ buf.resize(buf.size() + value.size());
+ std::copy_backward(value.c_str(), value.c_str() + value.size(),
+ buf.end());
+ }
+};
+
+// The goal of this test is to verify that an IPv4 address being
+// stored in a buffer (wire format) can be read into IOAddress
+// object.
+TEST_F(OptionDataTypesTest, readAddress) {
+ // Create some IPv4 address.
+ asiolink::IOAddress address("192.168.0.1");
+ // And store it in a buffer in a wire format.
+ std::vector<uint8_t> buf;
+ writeAddress(address, buf);
+
+ // Now, try to read the IP address with a utility function
+ // being under test.
+ asiolink::IOAddress address_out("127.0.0.1");
+ EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET));
+
+ // Check that the read address matches address that
+ // we used as input.
+ EXPECT_EQ(address.toText(), address_out.toText());
+
+ // Check that an attempt to read the buffer as IPv6 address
+ // causes an error as the IPv6 address needs at least 16 bytes
+ // long buffer.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readAddress(buf, AF_INET6),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ buf.clear();
+
+ // Do another test like this for IPv6 address.
+ address = asiolink::IOAddress("2001:db8:1:0::1");
+ writeAddress(address, buf);
+ EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6));
+ EXPECT_EQ(address.toText(), address_out.toText());
+
+ // Truncate the buffer and expect an error to be reported when
+ // trying to read it.
+ buf.resize(buf.size() - 1);
+ EXPECT_THROW(
+ OptionDataTypeUtil::readAddress(buf, AF_INET6),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The goal of this test is to verify that an IPv6 address
+// is properly converted to wire format and stored in a
+// buffer.
+TEST_F(OptionDataTypesTest, writeAddress) {
+ // Encode an IPv6 address 2001:db8:1::1 in wire format.
+ // This will be used as reference data to validate if
+ // an IPv6 address is stored in a buffer properly.
+ const uint8_t data[] = {
+ 0x20, 0x01, 0x0d, 0xb8, 0x0, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1
+ };
+ std::vector<uint8_t> buf_in(data, data + sizeof(data));
+
+ // Create IPv6 address object.
+ asiolink::IOAddress address("2001:db8:1::1");
+ // Define the output buffer to write IP address to.
+ std::vector<uint8_t> buf_out;
+ // Write the address to the buffer.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+ // Make sure that input and output buffers have the same size
+ // so we can compare them.
+ ASSERT_EQ(buf_in.size(), buf_out.size());
+ // And finally compare them.
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+ buf_out.clear();
+
+ // Do similar test for IPv4 address.
+ address = asiolink::IOAddress("192.168.0.1");
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+ ASSERT_EQ(4, buf_out.size());
+ // Verify that the IP address has been written correctly.
+ EXPECT_EQ(192, buf_out[0]);
+ EXPECT_EQ(168, buf_out[1]);
+ EXPECT_EQ(0, buf_out[2]);
+ EXPECT_EQ(1, buf_out[3]);
+}
+
+// The purpose of this test is to verify that binary data represented
+// as a string of hexadecimal digits can be written to a buffer.
+TEST_F(OptionDataTypesTest, writeBinary) {
+ // Prepare the reference data.
+ const char data[] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
+ 0x6, 0x7, 0x8, 0x9, 0xA, 0xB
+ };
+ std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+ // Create empty vector where binary data will be written to.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(
+ OptionDataTypeUtil::writeBinary("000102030405060708090A0B", buf)
+ );
+ // Verify that the buffer contains valid data.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// 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) {
+ // Create an input buffer.
+ std::vector<uint8_t> buf;
+ // 'true' value is encoded as 1 ('false' is encoded as 0)
+ buf.push_back(1);
+
+ // Read the value from the buffer.
+ bool value = false;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readBool(buf);
+ );
+ // Verify the value.
+ EXPECT_TRUE(value);
+ // Check if 'false' is read correctly either.
+ buf[0] = 0;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readBool(buf);
+ );
+ EXPECT_FALSE(value);
+
+ // Check that invalid value causes exception.
+ buf[0] = 5;
+ ASSERT_THROW(
+ OptionDataTypeUtil::readBool(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that boolean values
+// are correctly encoded in a buffer as '1' for 'true' and
+// '0' for 'false' values.
+TEST_F(OptionDataTypesTest, writeBool) {
+ // Create a buffer we will write to.
+ std::vector<uint8_t> buf;
+ // Write the 'true' value to the buffer.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(true, buf));
+ // We should now have 'true' value stored in a buffer.
+ ASSERT_EQ(1, buf.size());
+ EXPECT_EQ(buf[0], 1);
+ // Let's append another value to make sure that it is not always
+ // 'true' value being written.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(false, buf));
+ ASSERT_EQ(2, buf.size());
+ // Check that the first value has not changed.
+ EXPECT_EQ(buf[0], 1);
+ // Check the the second value is correct.
+ EXPECT_EQ(buf[1], 0);
+}
+
+// The purpose of this test is to verify that the integer values
+// of different types are correctly read from a buffer.
+TEST_F(OptionDataTypesTest, readInt) {
+ std::vector<uint8_t> buf;
+
+ // Write an 8-bit unsigned integer value to the buffer.
+ writeInt<uint8_t>(129, buf);
+ uint8_t valueUint8 = 0;
+ // Read the value and check that it is valid.
+ ASSERT_NO_THROW(
+ valueUint8 = OptionDataTypeUtil::readInt<uint8_t>(buf);
+ );
+ EXPECT_EQ(129, valueUint8);
+
+ // Try to read 16-bit value from a buffer holding 8-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<uint16_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Clear the buffer for the next check we are going to do.
+ buf.clear();
+
+ // Test uint16_t value.
+ writeInt<uint16_t>(1234, buf);
+ uint16_t valueUint16 = 0;
+ ASSERT_NO_THROW(
+ valueUint16 = OptionDataTypeUtil::readInt<uint16_t>(buf);
+ );
+ EXPECT_EQ(1234, valueUint16);
+
+ // Try to read 32-bit value from a buffer holding 16-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<uint32_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ buf.clear();
+
+ // Test uint32_t value.
+ writeInt<uint32_t>(56789, buf);
+ uint32_t valueUint32 = 0;
+ ASSERT_NO_THROW(
+ valueUint32 = OptionDataTypeUtil::readInt<uint32_t>(buf);
+ );
+ EXPECT_EQ(56789, valueUint32);
+ buf.clear();
+
+ // Test int8_t value.
+ writeInt<int8_t>(-65, buf);
+ int8_t valueInt8 = 0;
+ ASSERT_NO_THROW(
+ valueInt8 = OptionDataTypeUtil::readInt<int8_t>(buf);
+ );
+ EXPECT_EQ(-65, valueInt8);
+ buf.clear();
+
+ // Try to read 16-bit value from a buffer holding 8-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<int16_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Test int16_t value.
+ writeInt<int16_t>(2345, buf);
+ int32_t valueInt16 = 0;
+ ASSERT_NO_THROW(
+ valueInt16 = OptionDataTypeUtil::readInt<int16_t>(buf);
+ );
+ EXPECT_EQ(2345, valueInt16);
+ buf.clear();
+
+ // Try to read 32-bit value from a buffer holding 16-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<int32_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Test int32_t value.
+ writeInt<int32_t>(-16543, buf);
+ int32_t valueInt32 = 0;
+ ASSERT_NO_THROW(
+ valueInt32 = OptionDataTypeUtil::readInt<int32_t>(buf);
+ );
+ EXPECT_EQ(-16543, valueInt32);
+
+ buf.clear();
+}
+
+// The purpose of this test is to verify that integer values of different
+// types are correctly written to a buffer.
+TEST_F(OptionDataTypesTest, writeInt) {
+ // Prepare the reference buffer.
+ const uint8_t data[] = {
+ 0x7F, // 127
+ 0x03, 0xFF, // 1023
+ 0x00, 0x00, 0x10, 0x00, // 4096
+ 0xFF, 0xFF, 0xFC, 0x00, // -1024
+ 0x02, 0x00, // 512
+ 0x81 // -127
+ };
+ std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+
+ // Fill in the buffer with data. Each write operation appends an
+ // integer value. Eventually the buffer holds all values and should
+ // match with the reference buffer.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint8_t>(127, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint16_t>(1023, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint32_t>(4096, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int32_t>(-1024, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int16_t>(512, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int8_t>(-127, buf));
+
+ // Make sure that the buffer has the same size as the reference
+ // buffer.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ // Compare buffers.
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that FQDN is read from
+// a buffer and returned as a text. The representation of the FQDN
+// in the buffer complies with RFC1035, section 3.1.
+// This test also checks that if invalid (truncated) FQDN is stored
+// in a buffer the appropriate exception is returned when trying to
+// read it as a string.
+TEST_F(OptionDataTypesTest, readFqdn) {
+ // The binary representation of the "mydomain.example.com".
+ // Values: 8, 7, 3 and 0 specify the lengths of subsequent
+ // labels within the FQDN.
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+
+ // Make a vector out of the data.
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Read the buffer as FQDN and verify its correctness.
+ std::string fqdn;
+ EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf));
+ EXPECT_EQ("mydomain.example.com.", fqdn);
+
+ // By resizing the buffer we simulate truncation. The first
+ // length field (8) indicate that the first label's size is
+ // 8 but the actual buffer size is 5. Expect that conversion
+ // fails.
+ buf.resize(5);
+ EXPECT_THROW(
+ OptionDataTypeUtil::readFqdn(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Another special case: provide an empty buffer.
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::readFqdn(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that FQDN's syntax is validated
+// and that FQDN is correctly written to a buffer in a format described
+// in RFC1035 section 3.1.
+TEST_F(OptionDataTypesTest, writeFqdn) {
+ // Create empty buffer. The FQDN will be written to it.
+ OptionBuffer buf;
+ // Write a domain name into the buffer in the format described
+ // in RFC1035 section 3.1. This function should not throw
+ // exception because domain name is well formed.
+ EXPECT_NO_THROW(
+ OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf)
+ );
+ // The length of the data is 22 (8 bytes for "mydomain" label,
+ // 7 bytes for "example" label, 3 bytes for "com" label and
+ // finally 4 bytes positions between labels where length
+ // information is stored.
+ ASSERT_EQ(22, buf.size());
+
+ // Verify that length fields between labels hold valid values.
+ EXPECT_EQ(8, buf[0]); // length of "mydomain"
+ EXPECT_EQ(7, buf[9]); // length of "example"
+ EXPECT_EQ(3, buf[17]); // length of "com"
+ EXPECT_EQ(0, buf[21]); // zero byte at the end.
+
+ // Verify that labels are valid.
+ std::string label0(buf.begin() + 1, buf.begin() + 9);
+ EXPECT_EQ("mydomain", label0);
+
+ std::string label1(buf.begin() + 10, buf.begin() + 17);
+ EXPECT_EQ("example", label1);
+
+ std::string label2(buf.begin() + 18, buf.begin() + 21);
+ EXPECT_EQ("com", label2);
+
+ // The tested function is supposed to append data to a buffer
+ // so let's check that it is a case by appending another domain.
+ OptionDataTypeUtil::writeFqdn("hello.net", buf);
+
+ // The buffer length should be now longer.
+ ASSERT_EQ(33, buf.size());
+
+ // Check the length fields for new labels being appended.
+ EXPECT_EQ(5, buf[22]);
+ EXPECT_EQ(3, buf[28]);
+
+ // And check that labels are ok.
+ std::string label3(buf.begin() + 23, buf.begin() + 28);
+ EXPECT_EQ("hello", label3);
+
+ std::string label4(buf.begin() + 29, buf.begin() + 32);
+ EXPECT_EQ("net", label4);
+
+ // Check that invalid (empty) FQDN is rejected and expected
+ // exception type is thrown.
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::writeFqdn("", buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Check another invalid domain name (with repeated dot).
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::writeFqdn("example..com", buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the string
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readString) {
+ // Prepare a buffer with some string in it.
+ std::vector<uint8_t> buf;
+ writeString("hello world", buf);
+
+ // Read the string from the buffer.
+ std::string value;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readString(buf);
+ );
+ // Check that it is valid.
+ EXPECT_EQ("hello world", value);
+}
+
+// The purpose of this test is to verify that a string can be
+// stored in a buffer correctly.
+TEST_F(OptionDataTypesTest, writeString) {
+ // Prepare a buffer with a reference data.
+ std::vector<uint8_t> buf_ref;
+ writeString("hello world!", buf_ref);
+ // Create empty buffer we will write to.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeString("hello world!", buf));
+ // Compare two buffers.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 20c87d6..91b822c 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -23,6 +23,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <dhcp/option_definition.h>
#include <exceptions/exceptions.h>
@@ -883,7 +884,7 @@ TEST_F(OptionDefinitionTest, utf8StringTokenized) {
option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
);
ASSERT_TRUE(option_v6);
- ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionCustom));
std::vector<uint8_t> data = option_v6->getData();
std::vector<uint8_t> ref_data(values[0].c_str(), values[0].c_str()
+ values[0].length());
More information about the bind10-changes
mailing list