BIND 10 trac3036, updated. 8b1aa00503272f26a9f0a0271cdea93aab3b3295 [3036] Implemented Option6ClientFqdn class and unit tests.
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu Jul 18 11:30:36 UTC 2013
The branch, trac3036 has been updated
via 8b1aa00503272f26a9f0a0271cdea93aab3b3295 (commit)
from 95701a2c5bed358c1eb3f2e8d2424b9a88d8612e (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 8b1aa00503272f26a9f0a0271cdea93aab3b3295
Author: Marcin Siodelski <marcin at isc.org>
Date: Thu Jul 18 13:30:09 2013 +0200
[3036] Implemented Option6ClientFqdn class and unit tests.
-----------------------------------------------------------------------
Summary of changes:
src/lib/dhcp/option6_client_fqdn.cc | 284 ++++++++++--
src/lib/dhcp/option6_client_fqdn.h | 84 +++-
src/lib/dhcp/tests/option6_client_fqdn_unittest.cc | 467 +++++++++++++++++++-
3 files changed, 756 insertions(+), 79 deletions(-)
-----------------------------------------------------------------------
diff --git a/src/lib/dhcp/option6_client_fqdn.cc b/src/lib/dhcp/option6_client_fqdn.cc
index e1444a6..a6dd2b0 100644
--- a/src/lib/dhcp/option6_client_fqdn.cc
+++ b/src/lib/dhcp/option6_client_fqdn.cc
@@ -15,38 +15,208 @@
#include <dhcp/dhcp6.h>
#include <dhcp/option6_client_fqdn.h>
#include <dns/labelsequence.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
namespace isc {
namespace dhcp {
-Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
- const std::string& domain_name,
- const DomainNameType domain_name_type)
- : Option(Option::V6, D6O_CLIENT_FQDN),
- flags_(flag),
- domain_name_(NULL),
- domain_name_type_(domain_name_type) {
+class Option6ClientFqdnImpl {
+public:
+ uint8_t flags_;
+ boost::shared_ptr<isc::dns::Name> domain_name_;
+ Option6ClientFqdn::DomainNameType domain_name_type_;
+
+ Option6ClientFqdnImpl(const uint8_t flag,
+ const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type);
+
+ Option6ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source);
+
+ Option6ClientFqdnImpl& operator=(const Option6ClientFqdnImpl& source);
+
+ void setDomainName(const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type);
+
+ static void checkFlags(const uint8_t flags);
+
+ void parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+};
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const uint8_t flag,
+ const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type)
+ : flags_(flag),
+ domain_name_(),
+ domain_name_type_(name_type) {
+
// Check if flags are correct.
checkFlags(flags_);
+ // Set domain name. It may throw an exception if domain name has wrong
+ // format.
+ setDomainName(domain_name, name_type);
+}
+
+Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ parseWireData(first, last);
+ // Verify that flags value was correct.
+ checkFlags(flags_);
+}
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
+ : flags_(source.flags_),
+ domain_name_(new isc::dns::Name(*source.domain_name_)),
+ domain_name_type_(source.domain_name_type_) {
+}
+
+Option6ClientFqdnImpl&
+Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+ // This assignment should be exception safe.
+ flags_ = source.flags_;
+ domain_name_type_ = source.domain_name_type_;
+
+ return (*this);
+}
+
+void
+Option6ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type) {
+ // domain-name must be trimmed. Otherwise, string comprising spaces only
+ // would be treated as a fully qualified name.
+ std::string name = isc::util::str::trim(domain_name);
+ if (name.empty()) {
+ if (name_type == Option6ClientFqdn::FULL) {
+ isc_throw(InvalidFqdnOptionDomainName,
+ "fully qualified domain-name must not be empty"
+ << " when setting new domain-name for DHCPv6 Client"
+ << " FQDN Option");
+ }
+ // The special case when domain-name is empty is marked by setting the
+ // pointer to the domain-name object to NULL.
+ domain_name_.reset();
+
+ } else {
+ try {
+ domain_name_.reset(new isc::dns::Name(name));
+ domain_name_type_ = name_type;
+
+ } catch (const Exception& ex) {
+ isc_throw(InvalidFqdnOptionDomainName, "invalid domain-name value '"
+ << domain_name << "' when setting new domain-name for"
+ << " DHCPv6 Client FQDN Option");
+
+ }
+ }
+}
+
+void
+Option6ClientFqdnImpl::checkFlags(const uint8_t flags) {
+ // The Must Be Zero (MBZ) bits must not be set.
+ if ((flags & ~Option6ClientFqdn::FLAG_MASK) != 0) {
+ isc_throw(InvalidFqdnOptionFlags,
+ "invalid DHCPv6 Client FQDN Option flags: 0x"
+ << std::hex << static_cast<int>(flags) << std::dec);
+ }
+
+ // According to RFC 4704, section 4.1. if the N bit is 1, the S bit
+ // MUST be 0. Checking it here.
+ if ((flags & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S))
+ == (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) {
+ isc_throw(InvalidFqdnOptionFlags,
+ "both N and S flag of the DHCPv6 Client FQDN Option are set."
+ << " According to RFC 4704, if the N bit is 1 the S bit"
+ << " MUST be 0");
+ }
+}
+
+void
+Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
- try {
- domain_name_ = new isc::dns::Name(domain_name);
+ // Buffer must comprise at least one byte with the flags.
+ // The domain-name may be empty.
+ if (std::distance(first, last) < Option6ClientFqdn::FLAG_FIELD_LEN) {
+ isc_throw(OutOfRange, "DHCPv6 Client FQDN Option ("
+ << D6O_CLIENT_FQDN << ") is truncated");
+ }
- } catch (const Exception& ex) {
- isc_throw(InvalidFqdnOptionDomainName, "invalid domain-name value: "
- << domain_name);
+ // Parse flags
+ flags_ = *(first++);
+ // Parse domain-name if any.
+ if (std::distance(first, last) > 0) {
+ // The FQDN may comprise a partial domain-name. In this case it lacks
+ // terminating 0. If this is the case, we will need to add zero at
+ // the end because Name object constructor requires it.
+ if (*(last - 1) != 0) {
+ // Create temporary buffer and add terminating zero.
+ OptionBuffer buf(first, last);
+ buf.push_back(0);
+ // Reset domain name.
+ isc::util::InputBuffer name_buf(&buf[0], buf.size());
+ domain_name_.reset(new isc::dns::Name(name_buf));
+ // Terminating zero was missing, so set the domain-name type
+ // to partial.
+ domain_name_type_ = Option6ClientFqdn::PARTIAL;
+ } else {
+ // We are dealing with fully qualified domain name so there is
+ // no need to add terminating zero. Simply pass the buffer to
+ // Name object constructor.
+ isc::util::InputBuffer name_buf(&(*first),
+ std::distance(first, last));
+ domain_name_.reset(new isc::dns::Name(name_buf));
+ // Set the domain-type to fully qualified domain name.
+ domain_name_type_ = Option6ClientFqdn::FULL;
+ }
}
}
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag)
+ : Option(Option::V6, D6O_CLIENT_FQDN),
+ impl_(new Option6ClientFqdnImpl(flag, "", PARTIAL)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type)
+ : Option(Option::V6, D6O_CLIENT_FQDN),
+ impl_(new Option6ClientFqdnImpl(flag, domain_name, domain_name_type)) {
+}
+
Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
OptionBufferConstIter last)
: Option(Option::V6, D6O_CLIENT_FQDN, first, last),
- domain_name_(NULL) {
+ impl_(new Option6ClientFqdnImpl(first, last)) {
}
Option6ClientFqdn::~Option6ClientFqdn() {
- delete (domain_name_);
+ delete(impl_);
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source)
+ : Option(source),
+ impl_(new Option6ClientFqdnImpl(*source.impl_)) {
+}
+
+Option6ClientFqdn&
+Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
+ Option6ClientFqdnImpl* old_impl = impl_;
+ impl_ = new Option6ClientFqdnImpl(*source.impl_);
+ delete(old_impl);
+ return (*this);
}
bool
@@ -62,7 +232,7 @@ Option6ClientFqdn::getFlag(const Flag flag) const {
<< " Option flag specified, expected N, S or O");
}
- return ((flags_ & flag) != 0);
+ return ((impl_->flags_ & flag) != 0);
}
void
@@ -78,7 +248,7 @@ Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
// Copy the current flags into local variable. That way we will be able
// to test new flags settings before applying them.
- uint8_t new_flag = flags_;
+ uint8_t new_flag = impl_->flags_;
if (set_flag) {
new_flag |= flag;
} else {
@@ -86,8 +256,35 @@ Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
}
// Check new flags. If they are valid, apply them.
- checkFlags(new_flag);
- flags_ = new_flag;
+ Option6ClientFqdnImpl::checkFlags(new_flag);
+ impl_->flags_ = new_flag;
+}
+
+std::string
+Option6ClientFqdn::getDomainName() const {
+ if (impl_->domain_name_) {
+ return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
+ PARTIAL));
+ }
+ // If an object holding domain-name is NULL it means that the domain-name
+ // is empty.
+ return ("");
+}
+
+void
+Option6ClientFqdn::setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type) {
+ impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option6ClientFqdn::resetDomainName() {
+ setDomainName("", PARTIAL);
+}
+
+Option6ClientFqdn::DomainNameType
+Option6ClientFqdn::getDomainNameType() const {
+ return (impl_->domain_name_type_);
}
void
@@ -95,13 +292,13 @@ Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
// Header = option code and length.
packHeader(buf);
// Flags field.
- buf.writeUint8(flags_);
+ buf.writeUint8(impl_->flags_);
// Domain name, encoded as a set of labels.
- isc::dns::LabelSequence labels(*domain_name_);
+ isc::dns::LabelSequence labels(*impl_->domain_name_);
if (labels.getDataLength() > 0) {
size_t read_len = 0;
const uint8_t* data = labels.getData(&read_len);
- if (domain_name_type_ == PARTIAL) {
+ if (impl_->domain_name_type_ == PARTIAL) {
--read_len;
}
buf.writeData(data, read_len);
@@ -111,43 +308,38 @@ Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
}
void
-Option6ClientFqdn::unpack(OptionBufferConstIter,
- OptionBufferConstIter) {
+Option6ClientFqdn::unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ setData(first, last);
+ impl_->parseWireData(first, last);
}
std::string
-Option6ClientFqdn::toText(int) {
- return std::string();
+Option6ClientFqdn::toText(int indent) {
+ std::ostringstream stream;
+ std::string in(indent, ' '); // base indentation
+ std::string in_add(2, ' '); // second-level indentation is 2 spaces long
+ stream << in << "type=" << type_ << "(CLIENT_FQDN)" << std::endl
+ << in << "flags:" << std::endl
+ << in << in_add << "N=" << (getFlag(FLAG_N) ? "1" : "0") << std::endl
+ << in << in_add << "O=" << (getFlag(FLAG_O) ? "1" : "0") << std::endl
+ << in << in_add << "S=" << (getFlag(FLAG_S) ? "1" : "0") << std::endl
+ << in << "domain-name='" << getDomainName() << "' ("
+ << (getDomainNameType() == PARTIAL ? "partial" : "full")
+ << ")" << std::endl;
+
+ return (stream.str());
}
uint16_t
Option6ClientFqdn::len() {
// If domain name is partial, the NULL terminating character
// is not included and the option length have to be adjusted.
- uint16_t domain_name_length = domain_name_type_ == FULL ?
- domain_name_->getLength() : domain_name_->getLength() - 1;
+ uint16_t domain_name_length = impl_->domain_name_type_ == FULL ?
+ impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
}
-void
-Option6ClientFqdn::checkFlags(const uint8_t flags) {
- // The Must Be Zero (MBZ) bits must not be set.
- if ((flags & ~FLAG_MASK) != 0) {
- isc_throw(InvalidFqdnOptionFlags,
- "invalid DHCPv6 Client FQDN Option flags: 0x"
- << std::hex << static_cast<int>(flags) << std::dec);
- }
-
- // According to RFC 4704, section 4.1. if the N bit is 1, the S bit
- // MUST be 0. Checking it here.
- if ((flags & (FLAG_N | FLAG_S)) == (FLAG_N | FLAG_S)) {
- isc_throw(InvalidFqdnOptionFlags,
- "both N and S flag of the DHCPv6 Client FQDN Option are set."
- << " According to RFC 4704, if the N bit is 1 the S bit"
- << " MUST be 0");
- }
-}
-
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcp/option6_client_fqdn.h b/src/lib/dhcp/option6_client_fqdn.h
index 4461ca0..1f2a478 100644
--- a/src/lib/dhcp/option6_client_fqdn.h
+++ b/src/lib/dhcp/option6_client_fqdn.h
@@ -39,6 +39,8 @@ public:
isc::Exception(file, line, what) {}
};
+/// Forward declaration to implementation of @c Option6ClientFqdn class.
+class Option6ClientFqdnImpl;
/// @brief Represents DHCPv6 Client FQDN %Option (code 39).
///
@@ -60,8 +62,8 @@ public:
/// where:
/// - N flag specifies whether server should (0) or should not (1) perform DNS
/// Update,
-/// - O flag is set by the server to indicate that it has overriden client's
-/// preferrence set with the S bit.
+/// - O flag is set by the server to indicate that it has overridden client's
+/// preference set with the S bit.
/// - S flag specifies whether server should (1) or should not (0) perform
/// forward (FQDN-to-address) updates.
///
@@ -71,7 +73,21 @@ public:
/// Domain names being carried by DHCPv6 Client Fqdn %Option can be fully
/// qualified or partial. Partial domain names are encoded similar to the
/// fully qualified domain names, except that they lack terminating zero
-/// at the end of their wire representation.
+/// at the end of their wire representation. It is also accepted to create an
+/// instance of this option which has empty domain-name. Clients use empty
+/// domain-names to indicate that server should generate complete fully
+/// qualified domain-name.
+///
+/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface
+/// from implementation specifics. Implementations may use different approaches
+/// to handle domain names (mostly validation of the domain-names). The existing
+/// @c isc::dns::Name class is a natural (and the simplest) choice to handle
+/// domain-names. Use of this class however, implies that libdhcp must be linked
+/// with libdns. At some point these libraries may need to be separated, i.e. to
+/// support compilation and use of standalone DHCP server. This will require
+/// that the part of implementation which deals with domain-names is modified to
+/// not use classes from libdns. These changes will be transparent for this
+/// interface.
class Option6ClientFqdn : public Option {
public:
@@ -108,12 +124,20 @@ public:
const std::string& domain_name,
const DomainNameType domain_name_type = FULL);
+ /// @brief Constructor, creates option instance using flags.
+ ///
+ /// This constructor creates an instance of the option with empty
+ /// domain-name. This domain-name is marked partial.
+ ///
+ /// @param flag a combination of flags to be stored in flags field.
+ Option6ClientFqdn(const uint8_t flag);
+
/// @brief Constructor, creates an option instance from part of the buffer.
///
/// This constructor is mainly used to parse options in the received
/// messages. Function parameters specify buffer bounds from which the
/// option should be created. The size of the buffer chunk, specified by
- /// the constructor's paramaters should be equal or larger than the size
+ /// the constructor's parameters should be equal or larger than the size
/// of the option. Otherwise, constructor will throw an exception.
///
/// @param first the lower bound of the buffer to create option from.
@@ -121,9 +145,15 @@ public:
explicit Option6ClientFqdn(OptionBufferConstIter first,
OptionBufferConstIter last);
+ /// @brief Copy constructor
+ Option6ClientFqdn(const Option6ClientFqdn& source);
+
/// @brief Destructor
virtual ~Option6ClientFqdn();
+ /// @brief Assignment operator
+ Option6ClientFqdn& operator=(const Option6ClientFqdn& source);
+
/// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
/// is set.
///
@@ -140,6 +170,36 @@ public:
/// set (true), or cleared (false).
void setFlag(const Flag flag, const bool set);
+ /// @brief Returns the domain-name in the text format.
+ ///
+ /// If domain-name is partial, it lacks the dot at the end (e.g. myhost).
+ /// If domain-name is fully qualified, it has the dot at the end (e.g.
+ /// myhost.example.com.).
+ ///
+ /// @return domain-name in the text format.
+ std::string getDomainName() const;
+
+ /// @brief Set new domain-name.
+ ///
+ /// @param domain_name domain name field value in the text format.
+ /// @param domain_name_type type of the domain name: partial or fully
+ /// qualified.
+ void setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type);
+
+ /// @brief Set empty domain-name.
+ ///
+ /// This function is equivalent to @c Option6ClientFqdn::setDomainName
+ /// with empty partial domain-name. It is exception safe.
+ void resetDomainName();
+
+ /// @brief Returns enumerator value which indicates whether domain-name is
+ /// partial or full.
+ ///
+ /// @return An enumerator value indicating whether domain-name is partial
+ /// or full.
+ DomainNameType getDomainNameType() const;
+
/// @brief Writes option in the wire format into a buffer.
///
/// @param [out] buf output buffer where option data will be stored.
@@ -176,20 +236,8 @@ public:
private:
- /// @brief Verifies that flags are correct.
- ///
- /// Function throws @c isc::dhcp::InvalidFqdnOptionFlags exception if
- /// current setting of DHCPv6 Client Fqdn %Option flags is invalid.
- /// In particular, it checks that if N is set, S is cleared.
- ///
- /// @param flags DHCPv6 Client FQDN %Option flags to be checked.
- ///
- /// @throw isc::dhcp::InvalidFqdnOptionFlags if flags are incorrect.
- static void checkFlags(const uint8_t flags);
-
- uint8_t flags_;
- dns::Name* domain_name_;
- DomainNameType domain_name_type_;
+ /// @brief A pointer to the implementation.
+ Option6ClientFqdnImpl* impl_;
};
/// A pointer to the @c Option6ClientFqdn object.
diff --git a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
index 296d311..4c26776 100644
--- a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
+++ b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
@@ -24,15 +24,250 @@ namespace {
using namespace isc;
using namespace isc::dhcp;
-class Option6ClientFqdnTest : public ::testing::Test {
-public:
+// Redefine option flags here as uint8_t. They will be used to initialize
+// elements of the arrays that are used in tests below. Note that use of
+// enum values defined in Option6ClientFqdn class may cause compilation issues
+// during uint8_t arrays initialization. That is because the underlying
+// integral type used to represent enums is larger than one byte.
+const uint8_t FLAG_S = 0x01;
+const uint8_t FLAG_O = 0x02;
+const uint8_t FLAG_N = 0x04;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option6ClientFqdnTest, constructEmptyName) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Constructor should not accept empty fully qualified domain name.
+ EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+ Option6ClientFqdn::FULL),
+ InvalidFqdnOptionDomainName);
+ // This check is similar to previous one, but using domain-name comprising
+ // a single space character. This should be treated as empty domain-name.
+ EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, " ",
+ Option6ClientFqdn::FULL),
+ InvalidFqdnOptionDomainName);
+
+ // Try different constructor.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option and
+// the source option instance can be deleted (both instances don't share
+// any resources).
+TEST(Option6ClientFqdnTest, copyConstruct) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Use copy constructor to create a second instance of the option.
+ boost::scoped_ptr<Option6ClientFqdn> option_copy;
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option6ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ // Copy construction should result in no shared resources between
+ // two objects. In particular, pointer to implementation should not
+ // be shared. Thus, we can release the source object now.
+ option.reset();
+
+ // Verify that all parameters have been copied to the target object.
+ EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option_copy->getDomainNameType());
+
+ // Do another test with different parameters to verify that parameters
+ // change when copied object is changed.
+
+ // Create an option with different parameters.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "example",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Call copy-constructor to copy the option.
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option6ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ option.reset();
+
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("example", option_copy->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWire) {
+ const uint8_t in_data[] = {
+ FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option6ClientFqdnTest, constructFromWireTruncated) {
+ // Empty buffer is invalid. It should be at least one octet long.
+ OptionBuffer in_buf;
+ ASSERT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ OutOfRange);
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWirePartial) {
+ const uint8_t in_data[] = {
+ FLAG_N, // flags
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWireEmpty) {
+ OptionBuffer in_buf(FLAG_S);
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
- Option6ClientFqdnTest() { }
+ // domain-name field should be empty because on-wire data comprised
+ // flags field only.
+ EXPECT_TRUE(option->getDomainName().empty());
+}
- virtual ~Option6ClientFqdnTest() { }
-};
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another.
+TEST(Option6ClientFqdnTest, assignment) {
+ // Usually the smart pointer is used to declare options and call
+ // constructor within assert. Thanks to this approach, the option instance
+ // is in the function scope and only initialization is done within assert.
+ // In this particular test we can't use smart pointers because we are
+ // testing assignment operator like this:
+ //
+ // option2 = option;
+ //
+ // The two asserts below do not create the instances that we will used to
+ // test assignment. They just attempt to create instances of the options
+ // with the same parameters as those that will be created for the actual
+ // assignment test. If these asserts do not fail, we can create options
+ // for the assignment test, do not surround them with asserts and be sure
+ // they will not throw.
+ ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL));
+
+ ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL));
+
+ // Create options with the same parameters as tested above.
+
+ // Create first option.
+ Option6ClientFqdn option(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL);
+
+ // Verify that the values have been set correctly.
+ ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost.example.com.", option.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::FULL, option.getDomainNameType());
+
+ // Create a second option.
+ Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL);
+
+ // Verify tha the values have been set correctly.
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost", option2.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+ // Make the assignment.
+ ASSERT_NO_THROW(option2 = option);
+
+ // Both options should now have the same values.
+ EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
-TEST_F(Option6ClientFqdnTest, ctorInvalidFlags) {
+// This test verifies that constructor will throw an exception if invalid
+// DHCPv6 Client FQDN Option flags are specified.
+TEST(Option6ClientFqdnTest, constructInvalidFlags) {
// First, check that constructor does not throw an exception when
// valid flags values are provided. That way we eliminate the issue
// that constructor always throws exception.
@@ -53,9 +288,24 @@ TEST_F(Option6ClientFqdnTest, ctorInvalidFlags) {
InvalidFqdnOptionFlags);
}
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option6ClientFqdnTest, constructFromWireInvalidFlags) {
+ // Create a buffer which holds flags field only. Set valid flag field at
+ // at first to make sure that constructor doesn't always throw an exception.
+ OptionBuffer in_buf(FLAG_N);
+ ASSERT_NO_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()));
+
+ // Replace the flags with invalid value and verify that constructor throws
+ // appropriate exception.
+ in_buf[0] = FLAG_N | FLAG_S;
+ EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidFqdnOptionFlags);
+}
+
// This test verifies that if invalid domain name is used the constructor
// will throw appropriate exception.
-TEST_F(Option6ClientFqdnTest, ctorInvalidName) {
+TEST(Option6ClientFqdnTest, constructInvalidName) {
// First, check that constructor does not throw when valid domain name
// is specified. That way we eliminate the possibility that constructor
// always throws exception.
@@ -67,11 +317,11 @@ TEST_F(Option6ClientFqdnTest, ctorInvalidName) {
}
// This test verifies that getFlag throws an exception if flag value of 0x3
-// is specified.TThis test does not verify other invalid values, e.g. 0x5,
+// is specified.This test does not verify other invalid values, e.g. 0x5,
// 0x6 etc. because conversion of int values which do not belong to the range
// between the lowest and highest enumerator will give an undefined
// result.
-TEST_F(Option6ClientFqdnTest, getFlag) {
+TEST(Option6ClientFqdnTest, getFlag) {
boost::scoped_ptr<Option6ClientFqdn> option;
ASSERT_NO_THROW(
option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
@@ -87,7 +337,7 @@ TEST_F(Option6ClientFqdnTest, getFlag) {
// This test verifies that flags can be modified and that incorrect flags
// are rejected.
-TEST_F(Option6ClientFqdnTest, setFlag) {
+TEST(Option6ClientFqdnTest, setFlag) {
// Create option instance. Check that constructor doesn't throw.
boost::scoped_ptr<Option6ClientFqdn> option;
ASSERT_NO_THROW(
@@ -142,8 +392,61 @@ TEST_F(Option6ClientFqdnTest, setFlag) {
InvalidFqdnOptionFlags);
}
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option6ClientFqdnTest, setDomainName) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ // Partial domain-name.
+ ASSERT_NO_THROW(option->setDomainName("myhost",
+ Option6ClientFqdn::PARTIAL));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Fully qualified domain-name.
+ ASSERT_NO_THROW(option->setDomainName("example.com",
+ Option6ClientFqdn::FULL));
+ EXPECT_EQ("example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ // Empty domain name (partial). This should be successful.
+ ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL));
+ EXPECT_TRUE(option->getDomainName().empty());
+
+ // Fully qualified domain-names must not be empty.
+ EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),
+ InvalidFqdnOptionDomainName);
+ EXPECT_THROW(option->setDomainName(" ", Option6ClientFqdn::FULL),
+ InvalidFqdnOptionDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option6ClientFqdnTest, resetDomainName) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ // Set the domain-name to empty one.
+ ASSERT_NO_THROW(option->resetDomainName());
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
// This test verifies on-wire format of the option is correctly created.
-TEST_F(Option6ClientFqdnTest, pack) {
+TEST(Option6ClientFqdnTest, pack) {
// Create option instance. Check that constructor doesn't throw.
const uint8_t flags = Option6ClientFqdn::FLAG_S;
boost::scoped_ptr<Option6ClientFqdn> option;
@@ -159,7 +462,7 @@ TEST_F(Option6ClientFqdnTest, pack) {
// Prepare reference data.
const uint8_t ref_data[] = {
0, 39, 0, 21, // header
- Option6ClientFqdn::FLAG_S, // flags
+ FLAG_S, // flags
6, 109, 121, 104, 111, 115, 116, // myhost.
7, 101, 120, 97, 109, 112, 108, 101, // example.
3, 99, 111, 109, 0 // com.
@@ -174,7 +477,7 @@ TEST_F(Option6ClientFqdnTest, pack) {
// This test verifies on-wire format of the option with partial domain name
// is correctly created.
-TEST_F(Option6ClientFqdnTest, packPartial) {
+TEST(Option6ClientFqdnTest, packPartial) {
// Create option instance. Check that constructor doesn't throw.
const uint8_t flags = Option6ClientFqdn::FLAG_S;
boost::scoped_ptr<Option6ClientFqdn> option;
@@ -191,7 +494,7 @@ TEST_F(Option6ClientFqdnTest, packPartial) {
// Prepare reference data.
const uint8_t ref_data[] = {
0, 39, 0, 8, // header
- Option6ClientFqdn::FLAG_S, // flags
+ FLAG_S, // flags
6, 109, 121, 104, 111, 115, 116 // myhost
};
size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
@@ -202,9 +505,143 @@ TEST_F(Option6ClientFqdnTest, packPartial) {
EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
}
+// This test verifies that on-wire option data holding fully qualified domain
+// name is parsed correctly.
+TEST(Option6ClientFqdnTest, unpack) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option6ClientFqdnTest, unpackPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option6ClientFqdnTest, unpackTruncated) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+ );
+ ASSERT_TRUE(option);
+
+ // Empty buffer is invalid. It should be at least 1 octet long.
+ OptionBuffer in_buf;
+ EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option6ClientFqdnTest, toText) {
+ // Create option instance. Check that constructor doesn't throw.
+ uint8_t flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_O;
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags,
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The base indentation of the option will be set to 2. It should appear
+ // as follows.
+ std::string ref_string =
+ " type=39(CLIENT_FQDN)\n"
+ " flags:\n"
+ " N=1\n"
+ " O=1\n"
+ " S=0\n"
+ " domain-name='myhost.example.com.' (full)\n";
+ const int indent = 2;
+ EXPECT_EQ(ref_string, option->toText(indent));
+
+ // Create another option with different parameters:
+ // - flags set to 0
+ // - domain-name is now partial, not fully qualified
+ // Also, remove base indentation.
+ flags = 0;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags, "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ref_string =
+ "type=39(CLIENT_FQDN)\n"
+ "flags:\n"
+ " N=0\n"
+ " O=0\n"
+ " S=0\n"
+ "domain-name='myhost' (partial)\n";
+ EXPECT_EQ(ref_string, option->toText());
+}
+
// This test verifies that the correct length of the option in on-wire
// format is returned.
-TEST_F(Option6ClientFqdnTest, len) {
+TEST(Option6ClientFqdnTest, len) {
// Create option instance. Check that constructor doesn't throw.
boost::scoped_ptr<Option6ClientFqdn> option;
ASSERT_NO_THROW(
More information about the bind10-changes
mailing list