BIND 10 master, updated. 1b434debfbf4a43070eb480fa0975a6eff6429d4 [master] Merge branch 'trac3082'

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Aug 20 06:29:25 UTC 2013


The branch, master has been updated
       via  1b434debfbf4a43070eb480fa0975a6eff6429d4 (commit)
       via  cc2866a7dd9a2eb30d328a24d3257f3179229a8d (commit)
       via  04f940ed5e4f796ce09e1cf216da863a9c9a0d9d (commit)
       via  33edd2ed3d657798c02a75f236c087527f8137ad (commit)
       via  da34264f7277725b59764e73be597ee7cc513a90 (commit)
       via  cf24637df0959a2b68f2bde161790a73e3270abd (commit)
       via  1596f2ccf2273eb5be25ff92ab196f9bd714c0c4 (commit)
       via  b2a59752080f1aa7dbcfd677c5bfb8f65239c4c1 (commit)
       via  4fbb85cb991a8f3f412c2c386dcbe154fa5f04f2 (commit)
       via  1ceca44c4f118ea3d0246a7ac4b67331fcf4241c (commit)
      from  55f0a8f3f54c91686f910669b5183e4e95b51ae0 (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 1b434debfbf4a43070eb480fa0975a6eff6429d4
Merge: 55f0a8f cc2866a
Author: Marcin Siodelski <marcin at isc.org>
Date:   Tue Aug 20 06:07:36 2013 +0200

    [master] Merge branch 'trac3082'

-----------------------------------------------------------------------

Summary of changes:
 src/lib/dhcp/Makefile.am                           |    1 +
 src/lib/dhcp/option4_client_fqdn.cc                |  525 +++++++++++
 src/lib/dhcp/option4_client_fqdn.h                 |  370 ++++++++
 src/lib/dhcp/option_data_types.cc                  |    2 +-
 src/lib/dhcp/option_definition.cc                  |   16 +
 src/lib/dhcp/option_definition.h                   |   15 +
 src/lib/dhcp/std_option_defs.h                     |    3 +-
 src/lib/dhcp/tests/Makefile.am                     |    1 +
 src/lib/dhcp/tests/libdhcp++_unittest.cc           |    5 +-
 src/lib/dhcp/tests/option4_client_fqdn_unittest.cc |  959 ++++++++++++++++++++
 10 files changed, 1893 insertions(+), 4 deletions(-)
 create mode 100644 src/lib/dhcp/option4_client_fqdn.cc
 create mode 100644 src/lib/dhcp/option4_client_fqdn.h
 create mode 100644 src/lib/dhcp/tests/option4_client_fqdn_unittest.cc

-----------------------------------------------------------------------
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index f73c02d..780dc03 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -23,6 +23,7 @@ libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
 libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
+libb10_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h
 libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
 libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
 libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
diff --git a/src/lib/dhcp/option4_client_fqdn.cc b/src/lib/dhcp/option4_client_fqdn.cc
new file mode 100644
index 0000000..7f93a50
--- /dev/null
+++ b/src/lib/dhcp/option4_client_fqdn.cc
@@ -0,0 +1,525 @@
+// Copyright (C) 2013 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 <dhcp/dhcp4.h>
+#include <dhcp/option4_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 {
+
+/// @brief Implements the logic for the Option6ClientFqdn class.
+///
+/// The purpose of the class is to separate the implementation details
+/// of the Option4ClientFqdn class from the interface. This implementation
+/// uses libdns classes to process FQDNs. At some point it may be
+/// desired to split libdhcp++ from libdns. In such case the
+/// implementation of this class may be changed. The declaration of the
+/// Option6ClientFqdn class holds the pointer to implementation, so
+/// the transition to a different implementation would not affect the
+/// header file.
+class Option4ClientFqdnImpl {
+public:
+    /// Holds flags carried by the option.
+    uint8_t flags_;
+    /// Holds RCODE1 and RCODE2 values.
+    Option4ClientFqdn::Rcode rcode1_;
+    Option4ClientFqdn::Rcode rcode2_;
+    /// Holds the pointer to a domain name carried in the option.
+    boost::shared_ptr<isc::dns::Name> domain_name_;
+    /// Indicates whether domain name is partial or fully qualified.
+    Option4ClientFqdn::DomainNameType domain_name_type_;
+
+    /// @brief Constructor, from domain name.
+    ///
+    /// @param flags A value of the flags option field.
+    /// @param rcode An object representing the RCODE1 and RCODE2 values.
+    /// @param domain_name A domain name carried by the option given in the
+    /// textual format.
+    /// @param name_type A value which indicates whether domain-name is partial
+    /// or fully qualified.
+    Option4ClientFqdnImpl(const uint8_t flags,
+                          const Option4ClientFqdn::Rcode& rcode,
+                          const std::string& domain_name,
+                          const Option4ClientFqdn::DomainNameType name_type);
+
+    /// @brief Constructor, from wire data.
+    ///
+    /// @param first An iterator pointing to the begining of the option data
+    /// in the wire format.
+    /// @param last An iterator poiting to the end of the option data in the
+    /// wire format.
+    Option4ClientFqdnImpl(OptionBufferConstIter first,
+                          OptionBufferConstIter last);
+
+    /// @brief Copy constructor.
+    ///
+    /// @param source An object being copied.
+    Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source);
+
+    /// @brief Assignment operator.
+    ///
+    /// @param source An object which is being assigned.
+    Option4ClientFqdnImpl& operator=(const Option4ClientFqdnImpl& source);
+
+    /// @brief Set a new domain name for the option.
+    ///
+    /// @param domain_name A new domain name to be assigned.
+    /// @param name_type A value which indicates whether the domain-name is
+    /// partial or fully qualified.
+    void setDomainName(const std::string& domain_name,
+                       const Option4ClientFqdn::DomainNameType name_type);
+
+    /// @brief Check if flags are valid.
+    ///
+    /// In particular, this function checks if the N and S bits are not
+    /// set to 1 in the same time.
+    ///
+    /// @param flags A value carried by the flags field of the option.
+    /// @param check_mbz A boolean value which indicates if this function should
+    /// check if the MBZ bits are set (if true). This parameter should be set
+    /// to false when validating flags in the received message. This is because
+    /// server should ignore MBZ bits in received messages.
+    /// @throw InvalidOption6FqdnFlags if flags are invalid.
+    static void checkFlags(const uint8_t flags, const bool check_mbz);
+
+    /// @brief Parse the Option provided in the wire format.
+    ///
+    /// @param first An iterator pointing to the begining of the option data
+    /// in the wire format.
+    /// @param last An iterator poiting to the end of the option data in the
+    /// wire format.
+    void parseWireData(OptionBufferConstIter first,
+                       OptionBufferConstIter last);
+
+    /// @brief Parse domain-name encoded in the canonical format.
+    ///
+    void parseCanonicalDomainName(OptionBufferConstIter first,
+                                  OptionBufferConstIter last);
+
+    /// @brief Parse domain-name emcoded in the deprecated ASCII format.
+    ///
+    /// @param first An iterator pointing to the begining of the option data
+    /// where domain-name is stored.
+    /// @param last An iterator poiting to the end of the option data where
+    /// domain-name is stored.
+    void parseASCIIDomainName(OptionBufferConstIter first,
+                              OptionBufferConstIter last);
+
+};
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const uint8_t flags,
+                      const Option4ClientFqdn::Rcode& rcode,
+                      const std::string& domain_name,
+                      // cppcheck 1.57 complains that const enum value is not passed
+                      // by reference. Note that, it accepts the non-const enum value
+                      // to be passed by value. In both cases it is unneccessary to
+                      // pass the enum by reference.
+                      // cppcheck-suppress passedByValue
+                      const Option4ClientFqdn::DomainNameType name_type)
+    : flags_(flags),
+      rcode1_(rcode),
+      rcode2_(rcode),
+      domain_name_(),
+      domain_name_type_(name_type) {
+
+    //  Check if flags are correct. Also, check that MBZ bits are not set. If
+    // they are, throw exception.
+    checkFlags(flags_, true);
+    // Set domain name. It may throw an exception if domain name has wrong
+    // format.
+    setDomainName(domain_name, name_type);
+}
+
+Option4ClientFqdnImpl::Option4ClientFqdnImpl(OptionBufferConstIter first,
+                                             OptionBufferConstIter last)
+    : rcode1_(Option4ClientFqdn::RCODE_CLIENT()),
+      rcode2_(Option4ClientFqdn::RCODE_CLIENT()) {
+    parseWireData(first, last);
+    // Verify that flags value was correct. This constructor is used to parse
+    // incoming packet, so don't check MBZ bits. They are ignored because we
+    // don't want to discard the whole option because MBZ bits are set.
+    checkFlags(flags_, false);
+}
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source)
+    : flags_(source.flags_),
+      rcode1_(source.rcode1_),
+      rcode2_(source.rcode2_),
+      domain_name_(),
+      domain_name_type_(source.domain_name_type_) {
+    if (source.domain_name_) {
+        domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+    }
+}
+
+Option4ClientFqdnImpl&
+// This assignment operator handles assignment to self, it copies all
+// required values.
+// cppcheck-suppress operatorEqToSelf
+Option4ClientFqdnImpl::operator=(const Option4ClientFqdnImpl& source) {
+    if (source.domain_name_) {
+        domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+    } else {
+        domain_name_.reset();
+    }
+
+    // Assignment is exception safe.
+    flags_ = source.flags_;
+    rcode1_ = source.rcode1_;
+    rcode2_ = source.rcode2_;
+    domain_name_type_ = source.domain_name_type_;
+
+    return (*this);
+}
+
+void
+Option4ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+              // cppcheck 1.57 complains that const enum value is not passed
+              // by reference. Note that, it accepts the non-const enum
+              // to be passed by value. In both cases it is unneccessary to
+              // pass the enum by reference.
+              // cppcheck-suppress passedByValue
+              const Option4ClientFqdn::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 == Option4ClientFqdn::FULL) {
+            isc_throw(InvalidOption4FqdnDomainName,
+                      "fully qualified domain-name must not be empty"
+                      << " when setting new domain-name for DHCPv4 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(InvalidOption4FqdnDomainName,
+                      "invalid domain-name value '"
+                      << domain_name << "' when setting new domain-name for"
+                      << " DHCPv4 Client FQDN Option");
+
+        }
+    }
+}
+
+void
+Option4ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
+    // The Must Be Zero (MBZ) bits must not be set.
+    if (check_mbz && ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0)) {
+        isc_throw(InvalidOption4FqdnFlags,
+                  "invalid DHCPv4 Client FQDN Option flags: 0x"
+                  << std::hex << static_cast<int>(flags) << std::dec);
+    }
+
+    // According to RFC 4702, section 2.1. if the N bit is 1, the S bit
+    // MUST be 0. Checking it here.
+    if ((flags & (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S))
+        == (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) {
+        isc_throw(InvalidOption4FqdnFlags,
+                  "both N and S flag of the DHCPv4 Client FQDN Option are set."
+                  << " According to RFC 4702, if the N bit is 1 the S bit"
+                  << " MUST be 0");
+    }
+}
+
+void
+Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+                                     OptionBufferConstIter last) {
+
+    // Buffer must comprise at least one byte with the flags.
+    // The domain-name may be empty.
+    if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
+        isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
+                  << DHO_FQDN << ") is truncated");
+    }
+
+    // Parse flags
+    flags_ = *(first++);
+
+    // Parse RCODE1 and RCODE2.
+    rcode1_ = Option4ClientFqdn::Rcode(*(first++));
+    rcode2_ = Option4ClientFqdn::Rcode(*(first++));
+
+    try {
+        if ((flags_ & Option4ClientFqdn::FLAG_E) != 0) {
+            parseCanonicalDomainName(first, last);
+
+        } else {
+            parseASCIIDomainName(first, last);
+
+        }
+    } catch (const Exception& ex) {
+        isc_throw(InvalidOption4FqdnDomainName,
+                  "failed to parse the domain-name in DHCPv4 Client FQDN"
+                  << " Option: " << ex.what());
+    }
+}
+
+void
+Option4ClientFqdnImpl::parseCanonicalDomainName(OptionBufferConstIter first,
+                                                OptionBufferConstIter last) {
+    // 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_ = Option4ClientFqdn::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_ = Option4ClientFqdn::FULL;
+        }
+    }
+}
+
+void
+Option4ClientFqdnImpl::parseASCIIDomainName(OptionBufferConstIter first,
+                                            OptionBufferConstIter last) {
+    if (std::distance(first, last) > 0) {
+        std::string domain_name(first, last);
+        domain_name_.reset(new isc::dns::Name(domain_name));
+        domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ?
+            Option4ClientFqdn::FULL : Option4ClientFqdn::PARTIAL;
+    }
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
+    : Option(Option::V4, DHO_FQDN),
+      impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag,
+                                     const Rcode& rcode,
+                                     const std::string& domain_name,
+                                     const DomainNameType domain_name_type)
+    : Option(Option::V4, DHO_FQDN),
+      impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
+                                      domain_name_type)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(OptionBufferConstIter first,
+                                     OptionBufferConstIter last)
+    : Option(Option::V4, DHO_FQDN, first, last),
+      impl_(new Option4ClientFqdnImpl(first, last)) {
+}
+
+Option4ClientFqdn::~Option4ClientFqdn() {
+    delete(impl_);
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
+    : Option(source),
+      impl_(new Option4ClientFqdnImpl(*source.impl_)) {
+}
+
+Option4ClientFqdn&
+// This assignment operator handles assignment to self, it uses copy
+// constructor of Option4ClientFqdnImpl to copy all required values.
+// cppcheck-suppress operatorEqToSelf
+Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
+    Option4ClientFqdnImpl* old_impl = impl_;
+    impl_ = new Option4ClientFqdnImpl(*source.impl_);
+    delete(old_impl);
+    return (*this);
+}
+
+bool
+Option4ClientFqdn::getFlag(const uint8_t flag) const {
+    // Caller should query for one of the: E, N, S or O flags. Any other value
+    /// is invalid and results in the exception.
+    if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
+        isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
+                  << " Option flag specified, expected E, N, S or O");
+    }
+
+    return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
+    // Check that flag is in range between 0x1 and 0x7. Although it is
+    // discouraged this check doesn't preclude the caller from setting
+    // multiple flags concurrently.
+    if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
+        isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
+                  << " Option flag " << std::hex
+                  << static_cast<int>(flag) << std::dec
+                  << "is being set. Expected combination of E, N, S and O");
+    }
+
+    // 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 = impl_->flags_;
+    if (set_flag) {
+        new_flag |= flag;
+    } else {
+        new_flag &= ~flag;
+    }
+
+    // Check new flags. If they are valid, apply them. Also, check that MBZ
+    // bits are not set.
+    Option4ClientFqdnImpl::checkFlags(new_flag, true);
+    impl_->flags_ = new_flag;
+}
+
+void
+Option4ClientFqdn::setRcode(const Rcode& rcode) {
+    impl_->rcode1_ = rcode;
+    impl_->rcode2_ = rcode;
+}
+
+void
+Option4ClientFqdn::resetFlags() {
+    impl_->flags_ = 0;
+}
+
+std::string
+Option4ClientFqdn::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
+Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
+    // If domain-name is empty, do nothing.
+    if (!impl_->domain_name_) {
+        return;
+    }
+
+    if (getFlag(FLAG_E)) {
+        // Domain name, encoded as a set of labels.
+        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 (impl_->domain_name_type_ == PARTIAL) {
+                --read_len;
+            }
+            buf.writeData(data, read_len);
+        }
+
+    } else {
+        std::string domain_name = impl_->domain_name_->toText();
+        buf.writeData(&domain_name[0], domain_name.size());
+
+    }
+}
+
+void
+Option4ClientFqdn::setDomainName(const std::string& domain_name,
+                                 const DomainNameType domain_name_type) {
+    impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option4ClientFqdn::resetDomainName() {
+    setDomainName("", PARTIAL);
+}
+
+Option4ClientFqdn::DomainNameType
+Option4ClientFqdn::getDomainNameType() const {
+    return (impl_->domain_name_type_);
+}
+
+void
+Option4ClientFqdn::pack(isc::util::OutputBuffer& buf) {
+    // Header = option code and length.
+    packHeader(buf);
+    // Flags field.
+    buf.writeUint8(impl_->flags_);
+    // RCODE1 and RCODE2
+    buf.writeUint8(impl_->rcode1_.getCode());
+    buf.writeUint8(impl_->rcode2_.getCode());
+    // Domain name.
+    packDomainName(buf);
+}
+
+void
+Option4ClientFqdn::unpack(OptionBufferConstIter first,
+                          OptionBufferConstIter last) {
+    setData(first, last);
+    impl_->parseWireData(first, last);
+    // Check that the flags in the received option are valid. Ignore MBZ bits,
+    // because we don't want to discard the whole option because of MBZ bits
+    // being set.
+    impl_->checkFlags(impl_->flags_, false);
+}
+
+std::string
+Option4ClientFqdn::toText(int indent) {
+    std::ostringstream stream;
+    std::string in(indent, ' '); // base indentation
+    stream << in  << "type=" << type_ << " (CLIENT_FQDN), "
+           <<  "flags: ("
+           << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
+           << "E=" << (getFlag(FLAG_E) ? "1" : "0") << ", "
+           << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
+           << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
+           << "domain-name='" << getDomainName() << "' ("
+           << (getDomainNameType() == PARTIAL ? "partial" : "full")
+           << ")";
+
+    return (stream.str());
+}
+
+uint16_t
+Option4ClientFqdn::len() {
+    uint16_t domain_name_length = 0;
+    // If domain name is partial, the NULL terminating character
+    // is not included and the option length have to be adjusted.
+    if (impl_->domain_name_) {
+        domain_name_length = impl_->domain_name_type_ == FULL ?
+            impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
+    }
+
+    return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option4_client_fqdn.h b/src/lib/dhcp/option4_client_fqdn.h
new file mode 100644
index 0000000..fc0fb66
--- /dev/null
+++ b/src/lib/dhcp/option4_client_fqdn.h
@@ -0,0 +1,370 @@
+// Copyright (C) 2013 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 OPTION4_CLIENT_FQDN_H
+#define OPTION4_CLIENT_FQDN_H
+
+#include <dhcp/option.h>
+#include <dns/name.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid flags have been specified for
+/// DHCPv4 Client FQDN %Option.
+class InvalidOption4FqdnFlags : public Exception {
+public:
+    InvalidOption4FqdnFlags(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidOption4FqdnDomainName : public Exception {
+public:
+    InvalidOption4FqdnDomainName(const char* file, size_t line,
+                                 const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// Forward declaration to implementation of @c Option4ClientFqdn class.
+class Option4ClientFqdnImpl;
+
+/// @brief Represents DHCPv4 Client FQDN %Option (code 81).
+///
+/// This option has been defined in the RFC 4702 and it has a following
+/// structure:
+/// - Code (1 octet) - option code (always equal to 81).
+/// - Len (1 octet) - a length of the option.
+/// - Flags (1 octet) - a field carrying "NEOS" flags described below.
+/// - RCODE1 (1 octet) - deprecated field which should be set to 0 by the client
+/// and set to 255 by the server.
+/// - RCODE2 (1 octet) - deprecated, should be used in the same way as RCODE1.
+/// - Domain Name - variable length field comprising partial or fully qualified
+/// domain name.
+///
+/// The flags field has the following structure:
+/// @code
+///        0 1 2 3 4 5 6 7
+///       +-+-+-+-+-+-+-+-+
+///       |  MBZ  |N|E|O|S|
+///       +-+-+-+-+-+-+-+-+
+/// @endcode
+/// where:
+/// - N flag specifies whether server should (0) or should not (1) perform DNS
+///  Update,
+/// - E flag specifies encoding of the Domain Name field. If this flag is set
+/// to 1 it indicates canonical wire format without compression. 0 indicates
+/// the deprecated ASCII format.
+/// - 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.
+///
+/// This class exposes a set of functions to modify flags and check their
+/// correctness.
+///
+/// Domain names being carried by DHCPv4 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 (or lack of dot at the end, in
+/// case of ASCII encoding). 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.
+///
+/// @warning: The RFC4702 section 2.3.1 states that the clients and servers
+/// should use character sets specified in RFC952, section 2.1 for ASCII-encoded
+/// domain-names. This class doesn't detect the character set violation for
+/// ASCII-encoded domain-name. It could be implemented in the future but it is
+/// not important now for two reasons:
+/// - ASCII encoding is deprecated
+/// - clients SHOULD obey restrictions but if they don't, server may still
+///   process the option
+///
+/// RFC 4702 mandates that the DHCP client sets RCODE1 and RCODE2 to 0 and that
+/// server sets them to 255. This class allows to set the value for these
+/// fields and both fields are always set to the same value. There is no way
+/// to set them separately (e.g. set different value for RCODE1 and RCODE2).
+/// However, there are no use cases which would require it.
+///
+/// <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 Option4ClientFqdn : public Option {
+public:
+
+    ///
+    /// @name A set of constants used to identify and set bits in the flags field
+    //@{
+    static const uint8_t FLAG_S = 0x01; ///< Bit S
+    static const uint8_t FLAG_O = 0x02; ///< Bit O
+    static const uint8_t FLAG_E = 0x04; ///< Bit E
+    static const uint8_t FLAG_N = 0x08; ///< Bit N
+    //@}
+
+    /// @brief Mask which zeroes MBZ flag bits.
+    static const uint8_t FLAG_MASK = 0xF;
+
+    /// @brief Represents the value of one of the RCODE1 or RCODE2 fields.
+    ///
+    /// Typically, RCODE values are set to 255 by the server and to 0 by the
+    /// clients (as per RFC 4702).
+    class Rcode {
+    public:
+        Rcode(const uint8_t rcode)
+            : rcode_(rcode) { }
+
+        /// @brief Returns the value of the RCODE.
+        ///
+        /// Returned value can be directly used to create the on-wire format
+        /// of the DHCPv4 Client FQDN %Option.
+        uint8_t getCode() const {
+            return (rcode_);
+        }
+
+    private:
+        uint8_t rcode_;
+    };
+
+
+    /// @brief Type of the domain-name: partial or full.
+    enum DomainNameType {
+        PARTIAL,
+        FULL
+    };
+
+    /// @brief The size in bytes of the fixed fields within DHCPv4 Client Fqdn
+    /// %Option.
+    ///
+    /// The fixed fields are:
+    /// - Flags
+    /// - RCODE1
+    /// - RCODE2
+    static const uint16_t FIXED_FIELDS_LEN = 3;
+
+    /// @brief Constructor, creates option instance using flags and domain name.
+    ///
+    /// This constructor is used to create an instance of the option which will
+    /// be included in outgoing messages.
+    ///
+    /// Note that the RCODE values are encapsulated by the Rcode object (not a
+    /// simple uint8_t value). This helps to prevent a caller from confusing the
+    /// flags value with rcode value (both are uint8_t values). For example:
+    /// if caller swaps the two, it will be detected in the compilation time.
+    /// Also, this API encourages the caller to use two predefined functions:
+    /// @c RCODE_SERVER and @c RCODE_CLIENT to set the value of RCODE. These
+    /// functions generate objects which represent the only valid values to be
+    /// be passed to the constructor (255 and 0 respectively). Other
+    /// values should not be used. However, it is still possible that the other
+    /// entity (client or server) sends the option with invalid value. Although,
+    /// the RCODE values are ignored, there should be a way to represent such
+    /// invalid RCODE value. The Rcode class is capable of representing it.
+    ///
+    /// @param flags a combination of flags to be stored in flags field.
+    /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
+    /// fields of the option. Both fields are assigned the same value
+    /// encapsulated by the parameter.
+    /// @param domain_name a name to be stored in the domain-name field.
+    /// @param domain_name_type indicates if the domain name is partial
+    /// or full.
+    /// @throw InvalidOption4FqdnFlags if value of the flags field is wrong.
+    /// @throw InvalidOption4FqdnDomainName if the domain-name is invalid.
+    explicit Option4ClientFqdn(const uint8_t flags,
+                               const Rcode& rcode,
+                               const std::string& domain_name,
+                               const DomainNameType domain_name_type = FULL);
+
+    /// @brief Constructor, creates option instance with empty domain name.
+    ///
+    /// This constructor creates an instance of the option with empty
+    /// domain-name. This domain-name is marked partial.
+    ///
+    /// @param flags a combination of flags to be stored in flags field.
+    /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
+    /// fields. Both fields are assigned the same value encapsulated by this
+    /// parameter.
+    /// @throw InvalidOption4FqdnFlags if value of the flags field is invalid.
+    Option4ClientFqdn(const uint8_t flags, const Rcode& rcode);
+
+    /// @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 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.
+    /// @param last the upper bound of the buffer to create option from.
+    /// @throw InvalidOption4FqdnFlags if value of the flags field is invalid.
+    /// @throw InvalidOption4FqdnDomainName if the domain-name carried by the
+    /// option is invalid.
+    /// @throw OutOfRange if the option is truncated.
+    explicit Option4ClientFqdn(OptionBufferConstIter first,
+                               OptionBufferConstIter last);
+
+   /// @brief Copy constructor
+    Option4ClientFqdn(const Option4ClientFqdn& source);
+
+    /// @brief Destructor
+    virtual ~Option4ClientFqdn();
+
+    /// @brief Assignment operator
+    Option4ClientFqdn& operator=(const Option4ClientFqdn& source);
+
+    /// @brief Checks if the specified flag of the DHCPv4 Client FQDN %Option
+    /// is set.
+    ///
+    /// @param flag A value specifying a bit within flags field to be checked.
+    /// It must be one of the following @c FLAG_S, @c FLAG_E, @c FLAG_O,
+    /// @c FLAG_N.
+    ///
+    /// @return true if the bit of the specified flags bit is set, false
+    /// otherwise.
+    /// @throw InvalidOption4ClientFlags if specified flag which value is to be
+    /// returned is invalid (is not one of the FLAG_S, FLAG_N, FLAG_O).
+    bool getFlag(const uint8_t flag) const;
+
+    /// @brief Modifies the value of the specified DHCPv4 Client Fqdn %Option
+    /// flag.
+    ///
+    /// @param flag A value specifying a bit within flags field to be set. It
+    /// must be one of the following @c FLAG_S, @c FLAG_E, @c FLAG_O, @c FLAG_N.
+    /// @param set a boolean value which indicates whether flag should be
+    /// set (true), or cleared (false).
+    /// @throw InvalidOption4ClientFlags if specified flag which value is to be
+    /// set is invalid (is not one of the FLAG_S, FLAG_N, FLAG_O).
+    void setFlag(const uint8_t flag, const bool set);
+
+    /// @brief Sets the flag field value to 0.
+    void resetFlags();
+
+    /// @brief Set Rcode value.
+    ///
+    /// @param rcode An @c Rcode object representing value of RCODE1 and RCODE2.
+    /// Both fields are assigned the same value.
+    void setRcode(const Rcode& rcode);
+
+    /// @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 Writes domain-name in the wire format into a buffer.
+    ///
+    /// The data being written are appended at the end of the buffer.
+    ///
+    /// @param [out] buf buffer where domain-name will be written.
+    void packDomainName(isc::util::OutputBuffer& buf) 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.
+    /// @throw InvalidOption4FqdnDomainName if the specified domain-name is
+    /// invalid.
+    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.
+    virtual void pack(isc::util::OutputBuffer& buf);
+
+    /// @brief Parses option from the received buffer.
+    ///
+    /// Method creates an instance of the DHCPv4 Client FQDN %Option from the
+    /// wire format. Parameters specify the bounds of the buffer to read option
+    /// data from. The size of the buffer limited by the specified parameters
+    /// should be equal or larger than size of the option (including its
+    /// header). Otherwise exception will be thrown.
+    ///
+    /// @param first lower bound of the buffer to parse option from.
+    /// @param last upper bound of the buffer to parse option from.
+    virtual void unpack(OptionBufferConstIter first,
+                        OptionBufferConstIter last);
+
+    /// @brief Returns string representation of the option.
+    ///
+    /// The string returned by the method comprises the bit value of each
+    /// option flag and the domain-name.
+    ///
+    /// @param indent number of spaces before printed text.
+    ///
+    /// @return string with text representation.
+    virtual std::string toText(int indent = 0);
+
+    /// @brief Returns length of the complete option (data length +
+    /// DHCPv4 option header).
+    ///
+    /// @return length of the option.
+    virtual uint16_t len();
+
+    ///
+    /// @name Well known Rcode declarations for DHCPv4 Client FQDN %Option
+    ///
+    //@{
+    /// @brief Rcode being set by the server.
+    inline static const Rcode& RCODE_SERVER() {
+        static Rcode rcode(255);
+        return (rcode);
+    }
+
+    /// @brief Rcode being set by the client.
+    inline static const Rcode& RCODE_CLIENT() {
+        static Rcode rcode(0);
+        return (rcode);
+    }
+    //@}
+
+private:
+
+    /// @brief A pointer to the implementation.
+    Option4ClientFqdnImpl* impl_;
+};
+
+/// A pointer to the @c Option4ClientFqdn object.
+typedef boost::shared_ptr<Option4ClientFqdn> Option4ClientFqdnPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION4_CLIENT_FQDN_H
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
index a567b7e..5b77edc 100644
--- a/src/lib/dhcp/option_data_types.cc
+++ b/src/lib/dhcp/option_data_types.cc
@@ -170,7 +170,7 @@ OptionDataTypeUtil::writeBinary(const std::string& hex_str,
 
 bool
 OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
-    if (buf.size() < 1) {
+    if (buf.empty()) {
         isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
                   << " value. Invalid buffer size " << buf.size());
     }
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index bf2c5fb..1b87bff 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -12,8 +12,10 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <dhcp/dhcp4.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option4_addrlst.h>
+#include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option6_addrlst.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
@@ -184,6 +186,10 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
                     // for IA_NA and IA_PD above.
                     return (factoryIAAddr6(type, begin, end));
                 }
+            } else {
+                if ((code_ == DHO_FQDN) && haveFqdn4Format()) {
+                    return (OptionPtr(new Option4ClientFqdn(begin, end)));
+                }
             }
         }
         return (OptionPtr(new OptionCustom(*this, u, begin, end)));
@@ -341,6 +347,16 @@ OptionDefinition::haveIAAddr6Format() const {
     return (haveIAx6Format(OPT_IPV6_ADDRESS_TYPE));
 }
 
+bool
+OptionDefinition::haveFqdn4Format() const {
+    return (haveType(OPT_RECORD_TYPE) &&
+            record_fields_.size() == 4 &&
+            record_fields_[0] == OPT_UINT8_TYPE &&
+            record_fields_[1] == OPT_UINT8_TYPE &&
+            record_fields_[2] == OPT_UINT8_TYPE &&
+            record_fields_[3] == OPT_FQDN_TYPE);
+}
+
 template<typename T>
 T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const {
     // Lexical cast in case of our data types make sense only
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index dcfc3c7..0472d01 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -275,6 +275,21 @@ public:
     /// @return true if specified format is IAADDR option format.
     bool haveIAAddr6Format() const;
 
+    /// @brief Check if option has format of the DHCPv4 Client FQDN
+    /// %Option.
+    ///
+    /// The encoding of the domain-name carried by the FQDN option is
+    /// conditional and is specified in the flags field of the option.
+    /// The domain-name can be encoded in the ASCII format or canonical
+    /// wire format. The ASCII format is deprecated, therefore canonical
+    /// format is selected for the FQDN option definition and this function
+    /// returns true if the option definition comprises the domain-name
+    /// field encoded in canonical format.
+    ///
+    /// @return true if option has the format of DHCPv4 Client FQDN
+    /// %Option.
+    bool haveFqdn4Format() const;
+
     /// @brief Option factory.
     ///
     /// This function creates an instance of DHCP option using
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index 9bd19a1..00acdab 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -62,7 +62,8 @@ struct OptionDefParams {
 // RFC 1035, section 3.1. The latter could be handled
 // by OPT_FQDN_TYPE but we can't use it here because
 // clients may request ASCII encoding.
-RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_STRING_TYPE);
+RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+            OPT_FQDN_TYPE);
 
 /// @brief Definitions of standard DHCPv4 options.
 const OptionDefParams OPTION_DEF_PARAMS4[] = {
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 0216a0b..84e99de 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -31,6 +31,7 @@ libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
+libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc
 libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index d523158..e4d723f 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -18,6 +18,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option4_addrlst.h>
+#include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option6_addrlst.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
@@ -733,8 +734,8 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
     LibDhcpTest::testStdOptionDefs4(DHO_USER_CLASS, begin, end,
                                     typeid(Option));
 
-    LibDhcpTest::testStdOptionDefs4(DHO_FQDN, begin, end,
-                                    typeid(OptionCustom));
+    LibDhcpTest::testStdOptionDefs4(DHO_FQDN, begin, begin + 3,
+                                    typeid(Option4ClientFqdn));
 
     LibDhcpTest::testStdOptionDefs4(DHO_DHCP_AGENT_OPTIONS, begin, end,
                                     typeid(Option), "dhcp-agent-options-space");
diff --git a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
new file mode 100644
index 0000000..cdd7c64
--- /dev/null
+++ b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
@@ -0,0 +1,959 @@
+// Copyright (C) 2013 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/option4_client_fqdn.h>
+#include <dns/name.h>
+#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::dhcp;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option4ClientFqdnTest, constructEmptyName) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_SERVER(),
+                                           "",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    // Constructor should not accept empty fully qualified domain name.
+    EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+                                   Option4ClientFqdn::FLAG_E,
+                                   Option4ClientFqdn::RCODE_CLIENT(),
+                                   "",
+                                   Option4ClientFqdn::FULL),
+                 InvalidOption4FqdnDomainName);
+    // 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(Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+                                   Option4ClientFqdn::FLAG_E,
+                                   Option4ClientFqdn::RCODE_CLIENT(),
+                                   " ",
+                                   Option4ClientFqdn::FULL),
+                 InvalidOption4FqdnDomainName);
+
+    // Try different constructor.
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_SERVER()))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option4ClientFqdn::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(Option4ClientFqdnTest, copyConstruct) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_SERVER(),
+                                           "myhost.example.com",
+                                           Option4ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+
+    // Use copy constructor to create a second instance of the option.
+    boost::scoped_ptr<Option4ClientFqdn> option_copy;
+    ASSERT_NO_THROW(
+        option_copy.reset(new Option4ClientFqdn(*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(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::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 Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_SERVER(),
+                                           "example",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+
+    // Call copy-constructor to copy the option.
+    ASSERT_NO_THROW(
+        option_copy.reset(new Option4ClientFqdn(*option))
+    );
+    ASSERT_TRUE(option_copy);
+
+    option.reset();
+
+    EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("example", option_copy->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with the domain-name
+// encoded in the canonical format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWire) {
+    // The E flag sets the domain-name format to canonical.
+    const uint8_t in_data[] = {
+        Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+        0,                                                     // RCODE1
+        0,                                                     // RCODE2
+        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<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with the domain-name
+// encoded in the ASCII format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWireASCII) {
+    // The E flag is set to zero which indicates that the domain name
+    // is encoded in the ASCII format. The "dot" character at the end
+    // indicates that the domain-name is fully qualified.
+    const uint8_t in_data[] = {
+        Option4ClientFqdn::FLAG_S,            // flags
+        0,                                    // RCODE1
+        0,                                    // RCODE2
+        109, 121, 104, 111, 115, 116, 46,     // myhost.
+        101, 120, 97, 109, 112, 108, 101, 46, // example.
+        99, 111, 109, 46                      // 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<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option4ClientFqdnTest, constructFromWireTruncated) {
+    // Empty buffer is invalid. It should be at least one octet long.
+    OptionBuffer in_buf;
+    for (uint8_t i = 0; i < 3; ++i) {
+        EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+                     OutOfRange) << "Test of the truncated buffer failed for"
+                                 << " buffer length " << static_cast<int>(i);
+        in_buf.push_back(0);
+    }
+
+    // Buffer is now 3 bytes long, so it should not fail now.
+    EXPECT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+}
+
+// This test verifies that exception is thrown when invalid domain-name
+// in canonical format is carried in the option.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidName) {
+    const uint8_t in_data[] = {
+        Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+        0,                                                     // RCODE1
+        0,                                                     // RCODE2
+        6, 109, 121, 104, 111, 115, 116,                       // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101,                   // example.
+        5, 99, 111, 109, 0                 // com. (invalid label length 5)
+    };
+    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that exception is thrown when invalid domain-name
+// in ASCII format is carried in the option.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidASCIIName) {
+    const uint8_t in_data[] = {
+        Option4ClientFqdn::FLAG_S,            // flags
+        0,                                    // RCODE1
+        0,                                    // RCODE2
+        109, 121, 104, 111, 115, 116, 46, 46, // myhost.. (double dot!)
+        101, 120, 97, 109, 112, 108, 101, 46, // example.
+        99, 111, 109, 46                      // com.
+    };
+    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name encoded in canonical format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWirePartial) {
+    const uint8_t in_data[] = {
+        Option4ClientFqdn::FLAG_N | Option4ClientFqdn:: FLAG_E,  // flags
+        255,                                                     // RCODE1
+        255,                                                     // RCODE2
+        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<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name encoded in ASCII format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWirePartialASCII) {
+    // There is no "dot" character at the end, so the domain-name is partial.
+    const uint8_t in_data[] = {
+        Option4ClientFqdn::FLAG_N,            // flags
+        255,                                  // RCODE1
+        255,                                  // RCODE2
+        109, 121, 104, 111, 115, 116, 46,     // myhost.
+        101, 120, 97, 109, 112, 108, 101      // example
+    };
+    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<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWireEmpty) {
+    // Initialize the 3-byte long buffer. All bytes initialized to 0:
+    // Flags, RCODE1 and RCODE2.
+    OptionBuffer in_buf(3, 0);
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    // domain-name field should be empty because on-wire data comprised
+    // flags field only.
+    EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another.
+TEST(Option4ClientFqdnTest, 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(Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+                                      Option4ClientFqdn::FLAG_E,
+                                      Option4ClientFqdn::RCODE_SERVER(),
+                                      "myhost.example.com",
+                                      Option4ClientFqdn::FULL));
+
+    ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_N |
+                                      Option4ClientFqdn::FLAG_E,
+                                      Option4ClientFqdn::RCODE_SERVER(),
+                                      "myhost",
+                                      Option4ClientFqdn::PARTIAL));
+
+    // Create options with the same parameters as tested above.
+
+    // Create first option.
+    Option4ClientFqdn option(Option4ClientFqdn::FLAG_S |
+                             Option4ClientFqdn::FLAG_E,
+                             Option4ClientFqdn::RCODE_SERVER(),
+                             "myhost.example.com",
+                             Option4ClientFqdn::FULL);
+
+    // Verify that the values have been set correctly.
+    ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_S));
+    ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_E));
+    ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_N));
+    ASSERT_EQ("myhost.example.com.", option.getDomainName());
+    ASSERT_EQ(Option4ClientFqdn::FULL, option.getDomainNameType());
+
+    // Create a second option.
+    Option4ClientFqdn option2(Option4ClientFqdn::FLAG_N |
+                              Option4ClientFqdn::FLAG_E,
+                              Option4ClientFqdn::RCODE_SERVER(),
+                              "myhost",
+                              Option4ClientFqdn::PARTIAL);
+
+    // Verify tha the values have been set correctly.
+    ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+    ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E));
+    ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+    ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+    ASSERT_EQ("myhost", option2.getDomainName());
+    ASSERT_EQ(Option4ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+    // Make the assignment.
+    ASSERT_NO_THROW(option2 = option);
+
+    // Both options should now have the same values.
+    EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+    EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+
+    // Make self-assignment.
+    ASSERT_NO_THROW(option2 = option2);
+    EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+    EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+// This test verifies that constructor will throw an exception if invalid
+// DHCPv6 Client FQDN Option flags are specified.
+TEST(Option4ClientFqdnTest, 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.
+    uint8_t flags = 0;
+    ASSERT_NO_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+                                      "myhost.example.com"));
+
+    // Invalid flags: The maximal value is 0xF when all flag bits are set
+    // (00001111b). The flag value of 0x18 sets the bit from the Must Be
+    // Zero (MBZ) bitset (00011000b).
+    flags = 0x18;
+    EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+                                   "myhost.example.com"),
+                 InvalidOption4FqdnFlags);
+
+    // According to RFC 4702, section 2.1. if the N bit is set the S bit MUST
+    // be zero. If both are set, constructor is expected to throw.
+    flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S;
+    EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+                                   "myhost.example.com"),
+                 InvalidOption4FqdnFlags);
+}
+
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option4ClientFqdnTest, 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(3, 0);
+    in_buf[0] = Option4ClientFqdn::FLAG_S;
+    ASSERT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+
+    // Replace the flags with invalid value and verify that constructor throws
+    // appropriate exception.
+    in_buf[0] = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S;
+    EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidOption4FqdnFlags);
+}
+
+// This test verifies that if invalid domain name is used the constructor
+// will throw appropriate exception.
+TEST(Option4ClientFqdnTest, 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.
+    ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+                                      Option4ClientFqdn::RCODE_CLIENT(),
+                                      "myhost.example.com"));
+
+    // Specify invalid domain name and expect that exception is thrown.
+    EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+                                   Option4ClientFqdn::RCODE_CLIENT(),
+                                   "my...host.example.com"),
+                 InvalidOption4FqdnDomainName);
+
+    // Do the same test for the domain-name in ASCII format.
+    ASSERT_NO_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                      "myhost.example.com"));
+
+    EXPECT_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                   "my...host.example.com"),
+                 InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that getFlag throws an exception if flag value other
+// than N, E, O, S was specified.
+TEST(Option4ClientFqdnTest, getFlag) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // The value of 0x3 (binary 0011) is invalid because it specifies two bits
+    // in the flags field which value is to be checked.
+    EXPECT_THROW(option->getFlag(0x3), InvalidOption4FqdnFlags);
+}
+
+// This test verifies that flags can be modified and that incorrect flags
+// are rejected.
+TEST(Option4ClientFqdnTest, setFlag) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // All flags should be set to 0 initially.
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+
+    // Set E = 1
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_E, true));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+
+    // Set N = 1
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+    // Set O = 1
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, true));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+    // Set S = 1, this should throw exception because S and N must not
+    // be set in the same time.
+    ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true),
+                 InvalidOption4FqdnFlags);
+
+    // Set E = 0
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+    // Set N = 0
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+    // Set S = 1, this should not result in exception because N has been
+    // cleared.
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+
+    // Set N = 1, this should result in exception because S = 1
+    ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true),
+                 InvalidOption4FqdnFlags);
+
+    // Set O = 0
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, false));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+    // Try out of bounds settings.
+    uint8_t flags = 0;
+    ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags);
+
+    flags = 0x18;
+    ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags);
+}
+
+// This test verifies that flags field of the option is set to 0 when resetFlags
+// function is called.
+TEST(Option4ClientFqdnTest, resetFlags) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+                                           Option4ClientFqdn::FLAG_O |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com",
+                                           Option4ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+
+    // Check that flags we set in the constructor are set.
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+
+    option->resetFlags();
+
+    // After reset, all flags should be 0.
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+}
+
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option4ClientFqdnTest, setDomainName) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_SERVER(),
+                                           "myhost.example.com",
+                                           Option4ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+    ASSERT_EQ("myhost.example.com.", option->getDomainName());
+    ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+    // Partial domain-name.
+    ASSERT_NO_THROW(option->setDomainName("myhost",
+                                          Option4ClientFqdn::PARTIAL));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    // Fully qualified domain-name.
+    ASSERT_NO_THROW(option->setDomainName("example.com",
+                                          Option4ClientFqdn::FULL));
+    EXPECT_EQ("example.com.", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+    // Empty domain name (partial). This should be successful.
+    ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL));
+    EXPECT_TRUE(option->getDomainName().empty());
+
+    // Fully qualified domain-names must not be empty.
+    EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL),
+                 InvalidOption4FqdnDomainName);
+    EXPECT_THROW(option->setDomainName(" ", Option4ClientFqdn::FULL),
+                 InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option4ClientFqdnTest, resetDomainName) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com",
+                                           Option4ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+    ASSERT_EQ("myhost.example.com.", option->getDomainName());
+    ASSERT_EQ(Option4ClientFqdn::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(Option4ClientFqdnTest, pack) {
+    // Create option instance. Check that constructor doesn't throw.
+    const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E;
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // Prepare on-wire format of the option.
+    isc::util::OutputBuffer buf(10);
+    ASSERT_NO_THROW(option->pack(buf));
+
+    // Prepare reference data.
+    const uint8_t ref_data[] = {
+        81, 23,                                                 // header
+        Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E,  // flags
+        0,                                                      // RCODE1
+        0,                                                      // RCODE2
+        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 ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+    // Check if the buffer has the same length as the reference data,
+    // so as they can be compared directly.
+    ASSERT_EQ(ref_data_size, buf.getLength());
+    EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+TEST(Option4ClientFqdnTest, packASCII) {
+    // Create option instance. Check that constructor doesn't throw.
+    const uint8_t flags = Option4ClientFqdn::FLAG_S;
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // Prepare on-wire format of the option.
+    isc::util::OutputBuffer buf(10);
+    ASSERT_NO_THROW(option->pack(buf));
+
+    // Prepare reference data.
+    const uint8_t ref_data[] = {
+        81, 23,                               // header
+        Option4ClientFqdn::FLAG_S,            // flags
+        0,                                    // RCODE1
+        0,                                    // RCODE2
+        109, 121, 104, 111, 115, 116, 46,     // myhost.
+        101, 120, 97, 109, 112, 108, 101, 46, // example.
+        99, 111, 109, 46                      // com.
+    };
+    size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+    // Check if the buffer has the same length as the reference data,
+    // so as they can be compared directly.
+    ASSERT_EQ(ref_data_size, buf.getLength());
+    EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+
+}
+
+// This test verifies on-wire format of the option with partial domain name
+// is correctly created.
+TEST(Option4ClientFqdnTest, packPartial) {
+    // Create option instance. Check that constructor doesn't throw.
+    const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E;
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+
+    // Prepare on-wire format of the option.
+    isc::util::OutputBuffer buf(10);
+    ASSERT_NO_THROW(option->pack(buf));
+
+    // Prepare reference data.
+    const uint8_t ref_data[] = {
+        81, 10,                                                // header
+        Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+        0,                                                     // RCODE1
+        0,                                                     // RCODE2
+        6, 109, 121, 104, 111, 115, 116                        // myhost
+    };
+    size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+    // Check if the buffer has the same length as the reference data,
+    // so as they can be compared directly.
+    ASSERT_EQ(ref_data_size, buf.getLength());
+    EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that it is possible to encode option with empty
+// domain-name in the on-wire format.
+TEST(Option4ClientFqdnTest, packEmpty) {
+    // Create option instance. Check that constructor doesn't throw.
+    const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E;
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT()))
+    );
+    ASSERT_TRUE(option);
+
+    // Prepare on-wire format of the option.
+    isc::util::OutputBuffer buf(10);
+    ASSERT_NO_THROW(option->pack(buf));
+
+    // Prepare reference data.
+    const uint8_t ref_data[] = {
+        81, 3,                                                 // header
+        Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+        0,                                                     // RCODE1
+        0                                                      // RCODE2
+    };
+    size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+    // Check if the buffer has the same length as the reference data,
+    // so as they can be compared directly.
+    ASSERT_EQ(ref_data_size, buf.getLength());
+    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(Option4ClientFqdnTest, unpack) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost",
+                                           Option4ClientFqdn::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(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    const uint8_t in_data[] = {
+        Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+        0,                                                     // RCODE1
+        0,                                                     // RCODE2
+        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(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option4ClientFqdnTest, unpackPartial) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "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(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+    const uint8_t in_data[] = {
+        Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+        0,                                                     // RCODE1
+        0,                                                     // RCODE2
+        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(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option4ClientFqdnTest, unpackTruncated) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+                                           Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT()))
+    );
+    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(Option4ClientFqdnTest, toText) {
+    // Create option instance. Check that constructor doesn't throw.
+    uint8_t flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_O |
+        Option4ClientFqdn::FLAG_E;
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "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=81 (CLIENT_FQDN), flags: (N=1, E=1, O=1, S=0), "
+        "domain-name='myhost.example.com.' (full)";
+    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 Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ref_string =
+        "type=81 (CLIENT_FQDN), flags: (N=0, E=0, O=0, S=0), "
+        "domain-name='myhost' (partial)";
+    EXPECT_EQ(ref_string, option->toText());
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned.
+TEST(Option4ClientFqdnTest, len) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+    // This option comprises a header (2 octets), flag field (1 octet),
+    // RCODE1 and RCODE2 (2 octets) and wire representation of the
+    // domain name (length equal to the length of the string representation
+    // of the domain name + 1).
+    EXPECT_EQ(25, option->len());
+
+    // Let's check that the size will change when domain name of a different
+    // size is used.
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "example.com"))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_EQ(18, option->len());
+
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_EQ(12, option->len());
+    }
+
+} // anonymous namespace



More information about the bind10-changes mailing list