Browse Source

[3036] Implemented Option6ClientFqdn class and unit tests.

Marcin Siodelski 11 years ago
parent
commit
8b1aa00503

+ 238 - 46
src/lib/dhcp/option6_client_fqdn.cc

@@ -15,38 +15,208 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option6_client_fqdn.h>
 #include <dhcp/option6_client_fqdn.h>
 #include <dns/labelsequence.h>
 #include <dns/labelsequence.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
-Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
-                                     const std::string& domain_name,
-                                     const DomainNameType domain_name_type)
-    : Option(Option::V6, D6O_CLIENT_FQDN),
-      flags_(flag),
-      domain_name_(NULL),
-      domain_name_type_(domain_name_type) {
+class Option6ClientFqdnImpl {
+public:
+    uint8_t flags_;
+    boost::shared_ptr<isc::dns::Name> domain_name_;
+    Option6ClientFqdn::DomainNameType domain_name_type_;
+
+    Option6ClientFqdnImpl(const uint8_t flag,
+                          const std::string& domain_name,
+                          const Option6ClientFqdn::DomainNameType name_type);
+
+    Option6ClientFqdnImpl(OptionBufferConstIter first,
+                          OptionBufferConstIter last);
+
+    Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source);
+
+    Option6ClientFqdnImpl& operator=(const Option6ClientFqdnImpl& source);
+
+    void setDomainName(const std::string& domain_name,
+                       const Option6ClientFqdn::DomainNameType name_type);
+
+    static void checkFlags(const uint8_t flags);
+
+    void parseWireData(OptionBufferConstIter first,
+                       OptionBufferConstIter last);
+
+};
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const uint8_t flag,
+                      const std::string& domain_name,
+                      const Option6ClientFqdn::DomainNameType name_type)
+    : flags_(flag),
+      domain_name_(),
+      domain_name_type_(name_type) {
+
     //  Check if flags are correct.
     //  Check if flags are correct.
     checkFlags(flags_);
     checkFlags(flags_);
+    // Set domain name. It may throw an exception if domain name has wrong
+    // format.
+    setDomainName(domain_name, name_type);
+}
+
+Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first,
+                                             OptionBufferConstIter last) {
+    parseWireData(first, last);
+    // Verify that flags value was correct.
+    checkFlags(flags_);
+}
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
+    : flags_(source.flags_),
+      domain_name_(new isc::dns::Name(*source.domain_name_)),
+      domain_name_type_(source.domain_name_type_) {
+}
+
+Option6ClientFqdnImpl&
+Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) {
+    domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+    // This assignment should be exception safe.
+    flags_ = source.flags_;
+    domain_name_type_ = source.domain_name_type_;
+
+    return (*this);
+}
+
+void
+Option6ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+              const Option6ClientFqdn::DomainNameType name_type) {
+    // domain-name must be trimmed. Otherwise, string comprising spaces only
+    // would be treated as a fully qualified name.
+    std::string name = isc::util::str::trim(domain_name);
+    if (name.empty()) {
+        if (name_type == Option6ClientFqdn::FULL) {
+            isc_throw(InvalidFqdnOptionDomainName,
+                      "fully qualified domain-name must not be empty"
+                      << " when setting new domain-name for DHCPv6 Client"
+                      << " FQDN Option");
+        }
+        // The special case when domain-name is empty is marked by setting the
+        // pointer to the domain-name object to NULL.
+        domain_name_.reset();
+
+    } else {
+        try {
+            domain_name_.reset(new isc::dns::Name(name));
+            domain_name_type_ = name_type;
+
+        } catch (const Exception& ex) {
+            isc_throw(InvalidFqdnOptionDomainName, "invalid domain-name value '"
+                      << domain_name << "' when setting new domain-name for"
+                      << " DHCPv6 Client FQDN Option");
+
+        }
+    }
+}
+
+void
+Option6ClientFqdnImpl::checkFlags(const uint8_t flags) {
+    // The Must Be Zero (MBZ) bits must not be set.
+    if ((flags & ~Option6ClientFqdn::FLAG_MASK) != 0) {
+        isc_throw(InvalidFqdnOptionFlags,
+                  "invalid DHCPv6 Client FQDN Option flags: 0x"
+                  << std::hex << static_cast<int>(flags) << std::dec);
+    }
+
+    // According to RFC 4704, section 4.1. if the N bit is 1, the S bit
+    // MUST be 0. Checking it here.
+    if ((flags & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S))
+        == (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) {
+        isc_throw(InvalidFqdnOptionFlags,
+                  "both N and S flag of the DHCPv6 Client FQDN Option are set."
+                  << " According to RFC 4704, if the N bit is 1 the S bit"
+                  << " MUST be 0");
+    }
+}
+
+void
+Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+                                     OptionBufferConstIter last) {
 
 
-    try {
-        domain_name_ = new isc::dns::Name(domain_name);
+    // Buffer must comprise at least one byte with the flags.
+    // The domain-name may be empty.
+    if (std::distance(first, last) < Option6ClientFqdn::FLAG_FIELD_LEN) {
+        isc_throw(OutOfRange, "DHCPv6 Client FQDN Option ("
+                  << D6O_CLIENT_FQDN << ") is truncated");
+    }
 
 
-    } catch (const Exception& ex) {
-        isc_throw(InvalidFqdnOptionDomainName, "invalid domain-name value: "
-                  << domain_name);
+    // Parse flags
+    flags_ = *(first++);
 
 
+    // Parse domain-name if any.
+    if (std::distance(first, last) > 0) {
+        // The FQDN may comprise a partial domain-name. In this case it lacks
+        // terminating 0. If this is the case, we will need to add zero at
+        // the end because Name object constructor requires it.
+        if (*(last - 1) != 0) {
+            // Create temporary buffer and add terminating zero.
+            OptionBuffer buf(first, last);
+            buf.push_back(0);
+            // Reset domain name.
+            isc::util::InputBuffer name_buf(&buf[0], buf.size());
+            domain_name_.reset(new isc::dns::Name(name_buf));
+            // Terminating zero was missing, so set the domain-name type
+            // to partial.
+            domain_name_type_ = Option6ClientFqdn::PARTIAL;
+        } else {
+            // We are dealing with fully qualified domain name so there is
+            // no need to add terminating zero. Simply pass the buffer to
+            // Name object constructor.
+            isc::util::InputBuffer name_buf(&(*first),
+                                            std::distance(first, last));
+            domain_name_.reset(new isc::dns::Name(name_buf));
+            // Set the domain-type to fully qualified domain name.
+            domain_name_type_ = Option6ClientFqdn::FULL;
+        }
     }
     }
 }
 }
 
 
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag)
+    : Option(Option::V6, D6O_CLIENT_FQDN),
+      impl_(new Option6ClientFqdnImpl(flag, "", PARTIAL)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
+                                     const std::string& domain_name,
+                                     const DomainNameType domain_name_type)
+    : Option(Option::V6, D6O_CLIENT_FQDN),
+      impl_(new Option6ClientFqdnImpl(flag, domain_name, domain_name_type)) {
+}
+
 Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
 Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
                                      OptionBufferConstIter last)
                                      OptionBufferConstIter last)
     : Option(Option::V6, D6O_CLIENT_FQDN, first, last),
     : Option(Option::V6, D6O_CLIENT_FQDN, first, last),
-      domain_name_(NULL) {
+      impl_(new Option6ClientFqdnImpl(first, last)) {
 }
 }
 
 
 Option6ClientFqdn::~Option6ClientFqdn() {
 Option6ClientFqdn::~Option6ClientFqdn() {
-    delete (domain_name_);
+    delete(impl_);
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source)
+    : Option(source),
+      impl_(new Option6ClientFqdnImpl(*source.impl_)) {
+}
+
+Option6ClientFqdn&
+Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
+    Option6ClientFqdnImpl* old_impl = impl_;
+    impl_ = new Option6ClientFqdnImpl(*source.impl_);
+    delete(old_impl);
+    return (*this);
 }
 }
 
 
 bool
 bool
@@ -62,7 +232,7 @@ Option6ClientFqdn::getFlag(const Flag flag) const {
                   << " Option flag specified, expected N, S or O");
                   << " Option flag specified, expected N, S or O");
     }
     }
 
 
-    return ((flags_ & flag) != 0);
+    return ((impl_->flags_ & flag) != 0);
 }
 }
 
 
 void
 void
@@ -78,7 +248,7 @@ Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
 
 
     // Copy the current flags into local variable. That way we will be able
     // Copy the current flags into local variable. That way we will be able
     // to test new flags settings before applying them.
     // to test new flags settings before applying them.
-    uint8_t new_flag = flags_;
+    uint8_t new_flag = impl_->flags_;
     if (set_flag) {
     if (set_flag) {
         new_flag |= flag;
         new_flag |= flag;
     } else {
     } else {
@@ -86,8 +256,35 @@ Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
     }
     }
 
 
     // Check new flags. If they are valid, apply them.
     // Check new flags. If they are valid, apply them.
-    checkFlags(new_flag);
-    flags_ = new_flag;
+    Option6ClientFqdnImpl::checkFlags(new_flag);
+    impl_->flags_ = new_flag;
+}
+
+std::string
+Option6ClientFqdn::getDomainName() const {
+    if (impl_->domain_name_) {
+        return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
+                                            PARTIAL));
+    }
+    // If an object holding domain-name is NULL it means that the domain-name
+    // is empty.
+    return ("");
+}
+
+void
+Option6ClientFqdn::setDomainName(const std::string& domain_name,
+                                 const DomainNameType domain_name_type) {
+    impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option6ClientFqdn::resetDomainName() {
+    setDomainName("", PARTIAL);
+}
+
+Option6ClientFqdn::DomainNameType
+Option6ClientFqdn::getDomainNameType() const {
+    return (impl_->domain_name_type_);
 }
 }
 
 
 void
 void
@@ -95,13 +292,13 @@ Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
     // Header = option code and length.
     // Header = option code and length.
     packHeader(buf);
     packHeader(buf);
     // Flags field.
     // Flags field.
-    buf.writeUint8(flags_);
+    buf.writeUint8(impl_->flags_);
     // Domain name, encoded as a set of labels.
     // Domain name, encoded as a set of labels.
-    isc::dns::LabelSequence labels(*domain_name_);
+    isc::dns::LabelSequence labels(*impl_->domain_name_);
     if (labels.getDataLength() > 0) {
     if (labels.getDataLength() > 0) {
         size_t read_len = 0;
         size_t read_len = 0;
         const uint8_t* data = labels.getData(&read_len);
         const uint8_t* data = labels.getData(&read_len);
-        if (domain_name_type_ == PARTIAL) {
+        if (impl_->domain_name_type_ == PARTIAL) {
             --read_len;
             --read_len;
         }
         }
         buf.writeData(data, read_len);
         buf.writeData(data, read_len);
@@ -111,43 +308,38 @@ Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
 }
 }
 
 
 void
 void
-Option6ClientFqdn::unpack(OptionBufferConstIter,
-                          OptionBufferConstIter) {
+Option6ClientFqdn::unpack(OptionBufferConstIter first,
+                          OptionBufferConstIter last) {
+    setData(first, last);
+    impl_->parseWireData(first, last);
 }
 }
 
 
 std::string
 std::string
-Option6ClientFqdn::toText(int) {
-    return std::string();
+Option6ClientFqdn::toText(int indent) {
+    std::ostringstream stream;
+    std::string in(indent, ' '); // base indentation
+    std::string in_add(2, ' ');  // second-level indentation is 2 spaces long
+    stream << in  << "type=" << type_ << "(CLIENT_FQDN)" << std::endl
+           << in << "flags:" << std::endl
+           << in << in_add << "N=" << (getFlag(FLAG_N) ? "1" : "0") << std::endl
+           << in << in_add << "O=" << (getFlag(FLAG_O) ? "1" : "0") << std::endl
+           << in << in_add << "S=" << (getFlag(FLAG_S) ? "1" : "0") << std::endl
+           << in << "domain-name='" << getDomainName() << "' ("
+           << (getDomainNameType() == PARTIAL ? "partial" : "full")
+           << ")" << std::endl;
+
+    return (stream.str());
 }
 }
 
 
 uint16_t
 uint16_t
 Option6ClientFqdn::len() {
 Option6ClientFqdn::len() {
     // If domain name is partial, the NULL terminating character
     // If domain name is partial, the NULL terminating character
     // is not included and the option length have to be adjusted.
     // is not included and the option length have to be adjusted.
-    uint16_t domain_name_length = domain_name_type_ == FULL ?
-        domain_name_->getLength() : domain_name_->getLength() - 1;
+    uint16_t domain_name_length = impl_->domain_name_type_ == FULL ?
+        impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
 
 
     return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
     return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
 }
 }
 
 
-void
-Option6ClientFqdn::checkFlags(const uint8_t flags) {
-    // The Must Be Zero (MBZ) bits must not be set.
-    if ((flags & ~FLAG_MASK) != 0) {
-        isc_throw(InvalidFqdnOptionFlags,
-                  "invalid DHCPv6 Client FQDN Option flags: 0x"
-                  << std::hex << static_cast<int>(flags) << std::dec);
-    }
-
-    // According to RFC 4704, section 4.1. if the N bit is 1, the S bit
-    // MUST be 0. Checking it here.
-    if ((flags & (FLAG_N | FLAG_S)) == (FLAG_N | FLAG_S)) {
-        isc_throw(InvalidFqdnOptionFlags,
-                  "both N and S flag of the DHCPv6 Client FQDN Option are set."
-                  << " According to RFC 4704, if the N bit is 1 the S bit"
-                  << " MUST be 0");
-    }
-}
-
 } // end of isc::dhcp namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace

+ 66 - 18
src/lib/dhcp/option6_client_fqdn.h

@@ -39,6 +39,8 @@ public:
         isc::Exception(file, line, what) {}
         isc::Exception(file, line, what) {}
 };
 };
 
 
+/// Forward declaration to implementation of @c Option6ClientFqdn class.
+class Option6ClientFqdnImpl;
 
 
 /// @brief Represents DHCPv6 Client FQDN %Option (code 39).
 /// @brief Represents DHCPv6 Client FQDN %Option (code 39).
 ///
 ///
@@ -60,8 +62,8 @@ public:
 /// where:
 /// where:
 /// - N flag specifies whether server should (0) or should not (1) perform DNS
 /// - N flag specifies whether server should (0) or should not (1) perform DNS
 ///  Update,
 ///  Update,
-/// - O flag is set by the server to indicate that it has overriden client's
-/// preferrence set with the S bit.
+/// - O flag is set by the server to indicate that it has overridden client's
+/// preference set with the S bit.
 /// - S flag specifies whether server should (1) or should not (0) perform
 /// - S flag specifies whether server should (1) or should not (0) perform
 /// forward (FQDN-to-address) updates.
 /// forward (FQDN-to-address) updates.
 ///
 ///
@@ -71,7 +73,21 @@ public:
 /// Domain names being carried by DHCPv6 Client Fqdn %Option can be fully
 /// Domain names being carried by DHCPv6 Client Fqdn %Option can be fully
 /// qualified or partial. Partial domain names are encoded similar to the
 /// qualified or partial. Partial domain names are encoded similar to the
 /// fully qualified domain names, except that they lack terminating zero
 /// fully qualified domain names, except that they lack terminating zero
-/// at the end of their wire representation.
+/// at the end of their wire representation. It is also accepted to create an
+/// instance of this option which has empty domain-name. Clients use empty
+/// domain-names to indicate that server should generate complete fully
+/// qualified domain-name.
+///
+/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface
+/// from implementation specifics. Implementations may use different approaches
+/// to handle domain names (mostly validation of the domain-names). The existing
+/// @c isc::dns::Name class is a natural (and the simplest) choice to handle
+/// domain-names. Use of this class however, implies that libdhcp must be linked
+/// with libdns. At some point these libraries may need to be separated, i.e. to
+/// support compilation and use of standalone DHCP server. This will require
+/// that the part of implementation which deals with domain-names is modified to
+/// not use classes from libdns. These changes will be transparent for this
+/// interface.
 class Option6ClientFqdn : public Option {
 class Option6ClientFqdn : public Option {
 public:
 public:
 
 
@@ -108,12 +124,20 @@ public:
                                const std::string& domain_name,
                                const std::string& domain_name,
                                const DomainNameType domain_name_type = FULL);
                                const DomainNameType domain_name_type = FULL);
 
 
+    /// @brief Constructor, creates option instance using flags.
+    ///
+    /// This constructor creates an instance of the option with empty
+    /// domain-name. This domain-name is marked partial.
+    ///
+    /// @param flag a combination of flags to be stored in flags field.
+    Option6ClientFqdn(const uint8_t flag);
+
     /// @brief Constructor, creates an option instance from part of the buffer.
     /// @brief Constructor, creates an option instance from part of the buffer.
     ///
     ///
     /// This constructor is mainly used to parse options in the received
     /// This constructor is mainly used to parse options in the received
     /// messages. Function parameters specify buffer bounds from which the
     /// messages. Function parameters specify buffer bounds from which the
     /// option should be created. The size of the buffer chunk, specified by
     /// option should be created. The size of the buffer chunk, specified by
-    /// the constructor's paramaters should be equal or larger than the size
+    /// the constructor's parameters should be equal or larger than the size
     /// of the option. Otherwise, constructor will throw an exception.
     /// of the option. Otherwise, constructor will throw an exception.
     ///
     ///
     /// @param first the lower bound of the buffer to create option from.
     /// @param first the lower bound of the buffer to create option from.
@@ -121,9 +145,15 @@ public:
     explicit Option6ClientFqdn(OptionBufferConstIter first,
     explicit Option6ClientFqdn(OptionBufferConstIter first,
                                OptionBufferConstIter last);
                                OptionBufferConstIter last);
 
 
+   /// @brief Copy constructor
+    Option6ClientFqdn(const Option6ClientFqdn& source);
+
     /// @brief Destructor
     /// @brief Destructor
     virtual ~Option6ClientFqdn();
     virtual ~Option6ClientFqdn();
 
 
+    /// @brief Assignment operator
+    Option6ClientFqdn& operator=(const Option6ClientFqdn& source);
+
     /// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
     /// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
     /// is set.
     /// is set.
     ///
     ///
@@ -140,6 +170,36 @@ public:
     /// set (true), or cleared (false).
     /// set (true), or cleared (false).
     void setFlag(const Flag flag, const bool set);
     void setFlag(const Flag flag, const bool set);
 
 
+    /// @brief Returns the domain-name in the text format.
+    ///
+    /// If domain-name is partial, it lacks the dot at the end (e.g. myhost).
+    /// If domain-name is fully qualified, it has the dot at the end (e.g.
+    /// myhost.example.com.).
+    ///
+    /// @return domain-name in the text format.
+    std::string getDomainName() const;
+
+    /// @brief Set new domain-name.
+    ///
+    /// @param domain_name domain name field value in the text format.
+    /// @param domain_name_type type of the domain name: partial or fully
+    /// qualified.
+    void setDomainName(const std::string& domain_name,
+                       const DomainNameType domain_name_type);
+
+    /// @brief Set empty domain-name.
+    ///
+    /// This function is equivalent to @c Option6ClientFqdn::setDomainName
+    /// with empty partial domain-name. It is exception safe.
+    void resetDomainName();
+
+    /// @brief Returns enumerator value which indicates whether domain-name is
+    /// partial or full.
+    ///
+    /// @return An enumerator value indicating whether domain-name is partial
+    /// or full.
+    DomainNameType getDomainNameType() const;
+
    /// @brief Writes option in the wire format into a buffer.
    /// @brief Writes option in the wire format into a buffer.
     ///
     ///
     /// @param [out] buf output buffer where option data will be stored.
     /// @param [out] buf output buffer where option data will be stored.
@@ -176,20 +236,8 @@ public:
 
 
 private:
 private:
 
 
-    /// @brief Verifies that flags are correct.
-    ///
-    /// Function throws @c isc::dhcp::InvalidFqdnOptionFlags exception if
-    /// current setting of DHCPv6 Client Fqdn %Option flags is invalid.
-    /// In particular, it checks that if N is set, S is cleared.
-    ///
-    /// @param flags DHCPv6 Client FQDN %Option flags to be checked.
-    ///
-    /// @throw isc::dhcp::InvalidFqdnOptionFlags if flags are incorrect.
-    static void checkFlags(const uint8_t flags);
-
-    uint8_t flags_;
-    dns::Name* domain_name_;
-    DomainNameType domain_name_type_;
+    /// @brief A pointer to the implementation.
+    Option6ClientFqdnImpl* impl_;
 };
 };
 
 
 /// A pointer to the @c Option6ClientFqdn object.
 /// A pointer to the @c Option6ClientFqdn object.

+ 452 - 15
src/lib/dhcp/tests/option6_client_fqdn_unittest.cc

@@ -24,15 +24,250 @@ namespace {
 using namespace isc;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 
 
-class Option6ClientFqdnTest : public ::testing::Test {
-public:
+// Redefine option flags here as uint8_t. They will be used to initialize
+// elements of the arrays that are used in tests below. Note that use of
+// enum values defined in Option6ClientFqdn class may cause compilation issues
+// during uint8_t arrays initialization. That is because the underlying
+// integral type used to represent enums is larger than one byte.
+const uint8_t FLAG_S = 0x01;
+const uint8_t FLAG_O = 0x02;
+const uint8_t FLAG_N = 0x04;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option6ClientFqdnTest, constructEmptyName) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    // Constructor should not accept empty fully qualified domain name.
+    EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+                                   Option6ClientFqdn::FULL),
+                 InvalidFqdnOptionDomainName);
+    // This check is similar to previous one, but using domain-name comprising
+    // a single space character. This should be treated as empty domain-name.
+    EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, " ",
+                                   Option6ClientFqdn::FULL),
+                 InvalidFqdnOptionDomainName);
+
+    // Try different constructor.
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option and
+// the source option instance can be deleted (both instances don't share
+// any resources).
+TEST(Option6ClientFqdnTest, copyConstruct) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                           "myhost.example.com",
+                                           Option6ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+
+    // Use copy constructor to create a second instance of the option.
+    boost::scoped_ptr<Option6ClientFqdn> option_copy;
+    ASSERT_NO_THROW(
+        option_copy.reset(new Option6ClientFqdn(*option))
+    );
+    ASSERT_TRUE(option_copy);
+
+    // Copy construction should result in no shared resources between
+    // two objects. In particular, pointer to implementation should not
+    // be shared. Thus, we can release the source object now.
+    option.reset();
+
+    // Verify that all parameters have been copied to the target object.
+    EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, option_copy->getDomainNameType());
+
+    // Do another test with different parameters to verify that parameters
+    // change when copied object is changed.
+
+    // Create an option with different parameters.
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+                                           "example",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+
+    // Call copy-constructor to copy the option.
+    ASSERT_NO_THROW(
+        option_copy.reset(new Option6ClientFqdn(*option))
+    );
+    ASSERT_TRUE(option_copy);
+
+    option.reset();
+
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("example", option_copy->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWire) {
+    const uint8_t in_data[] = {
+        FLAG_S,                              // flags
+        6, 109, 121, 104, 111, 115, 116,     // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101, // example.
+        3, 99, 111, 109, 0                   // com.
+    };
+    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option6ClientFqdnTest, constructFromWireTruncated) {
+    // Empty buffer is invalid. It should be at least one octet long.
+    OptionBuffer in_buf;
+    ASSERT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+                 OutOfRange);
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWirePartial) {
+    const uint8_t in_data[] = {
+        FLAG_N,                              // flags
+        6, 109, 121, 104, 111, 115, 116      // myhost
+    };
+    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWireEmpty) {
+    OptionBuffer in_buf(FLAG_S);
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
 
 
-    Option6ClientFqdnTest() { }
+    // domain-name field should be empty because on-wire data comprised
+    // flags field only.
+    EXPECT_TRUE(option->getDomainName().empty());
+}
 
 
-    virtual ~Option6ClientFqdnTest() { }
-};
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another.
+TEST(Option6ClientFqdnTest, assignment) {
+    // Usually the smart pointer is used to declare options and call
+    // constructor within assert. Thanks to this approach, the option instance
+    // is in the function scope and only initialization is done within assert.
+    // In this particular test we can't use smart pointers because we are
+    // testing assignment operator like this:
+    //
+    //          option2 = option;
+    //
+    // The two asserts below do not create the instances that we will used to
+    // test assignment. They just attempt to create instances of the options
+    // with the same parameters as those that will be created for the actual
+    // assignment test. If these asserts do not fail, we can create options
+    // for the assignment test, do not surround them with asserts and be sure
+    // they will not throw.
+    ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                      "myhost.example.com",
+                                      Option6ClientFqdn::FULL));
+
+    ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+                                      "myhost",
+                                      Option6ClientFqdn::PARTIAL));
+
+    // Create options with the same parameters as tested above.
+
+    // Create first option.
+    Option6ClientFqdn option(Option6ClientFqdn::FLAG_S,
+                             "myhost.example.com",
+                             Option6ClientFqdn::FULL);
+
+    // Verify that the values have been set correctly.
+    ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
+    ASSERT_EQ("myhost.example.com.", option.getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::FULL, option.getDomainNameType());
+
+    // Create a second option.
+    Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
+                              "myhost",
+                              Option6ClientFqdn::PARTIAL);
+
+    // Verify tha the values have been set correctly.
+    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+    ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+    ASSERT_EQ("myhost", option2.getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+    // Make the assignment.
+    ASSERT_NO_THROW(option2 = option);
+
+    // Both options should now have the same values.
+    EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+    EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
 
 
-TEST_F(Option6ClientFqdnTest, ctorInvalidFlags) {
+// This test verifies that constructor will throw an exception if invalid
+// DHCPv6 Client FQDN Option flags are specified.
+TEST(Option6ClientFqdnTest, constructInvalidFlags) {
     // First, check that constructor does not throw an exception when
     // First, check that constructor does not throw an exception when
     // valid flags values are provided. That way we eliminate the issue
     // valid flags values are provided. That way we eliminate the issue
     // that constructor always throws exception.
     // that constructor always throws exception.
@@ -53,9 +288,24 @@ TEST_F(Option6ClientFqdnTest, ctorInvalidFlags) {
                  InvalidFqdnOptionFlags);
                  InvalidFqdnOptionFlags);
 }
 }
 
 
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option6ClientFqdnTest, constructFromWireInvalidFlags) {
+    // Create a buffer which holds flags field only. Set valid flag field at
+    // at first to make sure that constructor doesn't always throw an exception.
+    OptionBuffer in_buf(FLAG_N);
+    ASSERT_NO_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()));
+
+    // Replace the flags with invalid value and verify that constructor throws
+    // appropriate exception.
+    in_buf[0] = FLAG_N | FLAG_S;
+    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidFqdnOptionFlags);
+}
+
 // This test verifies that if invalid domain name is used the constructor
 // This test verifies that if invalid domain name is used the constructor
 // will throw appropriate exception.
 // will throw appropriate exception.
-TEST_F(Option6ClientFqdnTest, ctorInvalidName) {
+TEST(Option6ClientFqdnTest, constructInvalidName) {
     // First, check that constructor does not throw when valid domain name
     // First, check that constructor does not throw when valid domain name
     // is specified. That way we eliminate the possibility that constructor
     // is specified. That way we eliminate the possibility that constructor
     // always throws exception.
     // always throws exception.
@@ -67,11 +317,11 @@ TEST_F(Option6ClientFqdnTest, ctorInvalidName) {
 }
 }
 
 
 // This test verifies that getFlag throws an exception if flag value of 0x3
 // This test verifies that getFlag throws an exception if flag value of 0x3
-// is specified.TThis test does not verify other invalid values, e.g. 0x5,
+// is specified.This test does not verify other invalid values, e.g. 0x5,
 // 0x6 etc. because conversion of int values which do not belong to the range
 // 0x6 etc. because conversion of int values which do not belong to the range
 // between the lowest and highest enumerator will give an undefined
 // between the lowest and highest enumerator will give an undefined
 // result.
 // result.
-TEST_F(Option6ClientFqdnTest, getFlag) {
+TEST(Option6ClientFqdnTest, getFlag) {
     boost::scoped_ptr<Option6ClientFqdn> option;
     boost::scoped_ptr<Option6ClientFqdn> option;
     ASSERT_NO_THROW(
     ASSERT_NO_THROW(
         option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
         option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
@@ -87,7 +337,7 @@ TEST_F(Option6ClientFqdnTest, getFlag) {
 
 
 // This test verifies that flags can be modified and that incorrect flags
 // This test verifies that flags can be modified and that incorrect flags
 // are rejected.
 // are rejected.
-TEST_F(Option6ClientFqdnTest, setFlag) {
+TEST(Option6ClientFqdnTest, setFlag) {
     // Create option instance. Check that constructor doesn't throw.
     // Create option instance. Check that constructor doesn't throw.
     boost::scoped_ptr<Option6ClientFqdn> option;
     boost::scoped_ptr<Option6ClientFqdn> option;
     ASSERT_NO_THROW(
     ASSERT_NO_THROW(
@@ -142,8 +392,61 @@ TEST_F(Option6ClientFqdnTest, setFlag) {
                  InvalidFqdnOptionFlags);
                  InvalidFqdnOptionFlags);
 }
 }
 
 
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option6ClientFqdnTest, setDomainName) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                           "myhost.example.com",
+                                           Option6ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+    ASSERT_EQ("myhost.example.com.", option->getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+    // Partial domain-name.
+    ASSERT_NO_THROW(option->setDomainName("myhost",
+                                          Option6ClientFqdn::PARTIAL));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    // Fully qualified domain-name.
+    ASSERT_NO_THROW(option->setDomainName("example.com",
+                                          Option6ClientFqdn::FULL));
+    EXPECT_EQ("example.com.", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+    // Empty domain name (partial). This should be successful.
+    ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL));
+    EXPECT_TRUE(option->getDomainName().empty());
+
+    // Fully qualified domain-names must not be empty.
+    EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),
+                 InvalidFqdnOptionDomainName);
+    EXPECT_THROW(option->setDomainName(" ", Option6ClientFqdn::FULL),
+                 InvalidFqdnOptionDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option6ClientFqdnTest, resetDomainName) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                           "myhost.example.com",
+                                           Option6ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+    ASSERT_EQ("myhost.example.com.", option->getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+    // Set the domain-name to empty one.
+    ASSERT_NO_THROW(option->resetDomainName());
+    EXPECT_TRUE(option->getDomainName().empty());
+}
+
 // This test verifies on-wire format of the option is correctly created.
 // This test verifies on-wire format of the option is correctly created.
-TEST_F(Option6ClientFqdnTest, pack) {
+TEST(Option6ClientFqdnTest, pack) {
     // Create option instance. Check that constructor doesn't throw.
     // Create option instance. Check that constructor doesn't throw.
     const uint8_t flags = Option6ClientFqdn::FLAG_S;
     const uint8_t flags = Option6ClientFqdn::FLAG_S;
     boost::scoped_ptr<Option6ClientFqdn> option;
     boost::scoped_ptr<Option6ClientFqdn> option;
@@ -159,7 +462,7 @@ TEST_F(Option6ClientFqdnTest, pack) {
     // Prepare reference data.
     // Prepare reference data.
     const uint8_t ref_data[] = {
     const uint8_t ref_data[] = {
         0, 39, 0, 21,                        // header
         0, 39, 0, 21,                        // header
-        Option6ClientFqdn::FLAG_S,           // flags
+        FLAG_S,                              // flags
         6, 109, 121, 104, 111, 115, 116,     // myhost.
         6, 109, 121, 104, 111, 115, 116,     // myhost.
         7, 101, 120, 97, 109, 112, 108, 101, // example.
         7, 101, 120, 97, 109, 112, 108, 101, // example.
         3, 99, 111, 109, 0                   // com.
         3, 99, 111, 109, 0                   // com.
@@ -174,7 +477,7 @@ TEST_F(Option6ClientFqdnTest, pack) {
 
 
 // This test verifies on-wire format of the option with partial domain name
 // This test verifies on-wire format of the option with partial domain name
 // is correctly created.
 // is correctly created.
-TEST_F(Option6ClientFqdnTest, packPartial) {
+TEST(Option6ClientFqdnTest, packPartial) {
     // Create option instance. Check that constructor doesn't throw.
     // Create option instance. Check that constructor doesn't throw.
     const uint8_t flags = Option6ClientFqdn::FLAG_S;
     const uint8_t flags = Option6ClientFqdn::FLAG_S;
     boost::scoped_ptr<Option6ClientFqdn> option;
     boost::scoped_ptr<Option6ClientFqdn> option;
@@ -191,7 +494,7 @@ TEST_F(Option6ClientFqdnTest, packPartial) {
     // Prepare reference data.
     // Prepare reference data.
     const uint8_t ref_data[] = {
     const uint8_t ref_data[] = {
         0, 39, 0, 8,                         // header
         0, 39, 0, 8,                         // header
-        Option6ClientFqdn::FLAG_S,           // flags
+        FLAG_S,                              // flags
         6, 109, 121, 104, 111, 115, 116      // myhost
         6, 109, 121, 104, 111, 115, 116      // myhost
     };
     };
     size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
     size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
@@ -202,9 +505,143 @@ TEST_F(Option6ClientFqdnTest, packPartial) {
     EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
     EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
 }
 }
 
 
+// This test verifies that on-wire option data holding fully qualified domain
+// name is parsed correctly.
+TEST(Option6ClientFqdnTest, unpack) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+                                           "myhost",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    // Make sure that the parameters have been set correctly. Later in this
+    // test we will check that they will be replaced with new values when
+    // unpack is called.
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    const uint8_t in_data[] = {
+        FLAG_S,                              // flags
+        6, 109, 121, 104, 111, 115, 116,     // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101, // example.
+        3, 99, 111, 109, 0                   // com.
+    };
+    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    // Initialize new values from the on-wire format.
+    ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+    // Check that new values are correct.
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option6ClientFqdnTest, unpackPartial) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+    // Make sure that the parameters have been set correctly. Later in this
+    // test we will check that they will be replaced with new values when
+    // unpack is called.
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+    const uint8_t in_data[] = {
+        FLAG_S,                              // flags
+        6, 109, 121, 104, 111, 115, 116      // myhost
+    };
+    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    // Initialize new values from the on-wire format.
+    ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+    // Check that new values are correct.
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option6ClientFqdnTest, unpackTruncated) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+    );
+    ASSERT_TRUE(option);
+
+    // Empty buffer is invalid. It should be at least 1 octet long.
+    OptionBuffer in_buf;
+    EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option6ClientFqdnTest, toText) {
+    // Create option instance. Check that constructor doesn't throw.
+    uint8_t flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_O;
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(flags,
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // The base indentation of the option will be set to 2. It should appear
+    // as follows.
+    std::string ref_string =
+        "  type=39(CLIENT_FQDN)\n"
+        "  flags:\n"
+        "    N=1\n"
+        "    O=1\n"
+        "    S=0\n"
+        "  domain-name='myhost.example.com.' (full)\n";
+    const int indent = 2;
+    EXPECT_EQ(ref_string, option->toText(indent));
+
+    // Create another option with different parameters:
+    // - flags set to 0
+    // - domain-name is now partial, not fully qualified
+    // Also, remove base indentation.
+    flags = 0;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(flags, "myhost",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ref_string =
+        "type=39(CLIENT_FQDN)\n"
+        "flags:\n"
+        "  N=0\n"
+        "  O=0\n"
+        "  S=0\n"
+        "domain-name='myhost' (partial)\n";
+    EXPECT_EQ(ref_string, option->toText());
+}
+
 // This test verifies that the correct length of the option in on-wire
 // This test verifies that the correct length of the option in on-wire
 // format is returned.
 // format is returned.
-TEST_F(Option6ClientFqdnTest, len) {
+TEST(Option6ClientFqdnTest, len) {
     // Create option instance. Check that constructor doesn't throw.
     // Create option instance. Check that constructor doesn't throw.
     boost::scoped_ptr<Option6ClientFqdn> option;
     boost::scoped_ptr<Option6ClientFqdn> option;
     ASSERT_NO_THROW(
     ASSERT_NO_THROW(