Browse Source

[master] Merge branch 'trac3082'

Marcin Siodelski 11 years ago
parent
commit
1b434debfb

+ 1 - 0
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

+ 525 - 0
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

+ 370 - 0
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

+ 1 - 1
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());
     }

+ 16 - 0
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

+ 15 - 0
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

+ 2 - 1
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[] = {

+ 1 - 0
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

+ 3 - 2
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");

+ 959 - 0
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