Browse Source

[3036] Address further review comments.

Marcin Siodelski 11 years ago
parent
commit
4e902ba369

+ 1 - 0
doc/devel/mainpage.dox

@@ -56,6 +56,7 @@
  *   - @subpage dhcpv6Session
  *   - @subpage dhcpv6ConfigParser
  *   - @subpage dhcpv6ConfigInherit
+ *   - @subpage dhcpv6DDNSIntegration
  *   - @subpage dhcpv6Other
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro

+ 3 - 0
doc/guide/bind10-guide.xml

@@ -5268,6 +5268,9 @@ should include options from the isc option space:
           <listitem>
             <simpara><ulink url="http://tools.ietf.org/html/rfc3646">RFC 3646</ulink>: Supported option is DNS_SERVERS.</simpara>
           </listitem>
+          <listitem>
+            <simpara><ulink url="http://tools.ietf.org/html/rfc4704">RFC 4704</ulink>: Supported option is CLIENT_FQDN.</simpara>
+          </listitem>
       </itemizedlist>
     </section>
 

+ 22 - 23
src/bin/dhcp6/dhcp6.dox

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-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
@@ -93,21 +93,21 @@
 @section dhcpv6DDNSIntegration DHCPv6 Server Support for the Dynamic DNS Updates
 
 The DHCPv6 server supports processing of the DHCPv6 Client FQDN Option described in
-the RFC4307. This Option is sent by the DHCPv6 client to instruct the server to
+the RFC4704. This Option is sent by the DHCPv6 client to instruct the server to
 update the DNS mappings for the acquired lease. A client may send its fully
 qualified domain name, a partial name or it may choose that server will generate
 the name. In the last case, the client sends an empty domain-name field in the
 DHCPv6 Client FQDN Option.
 
-As described in RFC4307, client may choose that the server delegates the forward
+As described in RFC4704, client may choose that the server delegates the forward
 DNS update to the client and that the server performs the reverse update only. Current
 version of the DHCPv6 server does not support delegation of the forward update
 to the client. The implementation of this feature is planned for the future releases.
 
-The bind10-d2 process is responsible for the actual communication with the DNS
-server, i.e. to send DNS Update messages. The bind10-dhcp6 module is responsible
+The b10-dhcp-ddns process is responsible for the actual communication with the DNS
+server, i.e. to send DNS Update messages. The b10-dhcp6 module is responsible
 for generating so called @ref isc::dhcp_ddns::NameChangeRequest and sending it to the
-bind10-d2 module. The @ref isc::dhcp_ddns::NameChangeRequest object represents changes to the
+b10-dhcp-ddns module. The @ref isc::dhcp_ddns::NameChangeRequest object represents changes to the
 DNS bindings, related to acquisition, renewal or release of the lease. The bind10-dhcp6
 module implements the simple FIFO queue of the NameChangeRequest objects. The module
 logic, which processes the incoming DHCPv6 Client FQDN Options puts these requests
@@ -116,24 +116,25 @@ into the FIFO queue.
 @todo Currently the FIFO queue is not processed after the NameChangeRequests are
 generated and added to it. In the future implementation steps it is planned to create
 a code which will check if there are any outstanding requests in the queue and
-send them to the bind10-d2 module when server is idle waiting for DHCP messages.
+send them to the bind10-dhcp-ddns module when server is idle waiting for DHCP messages.
 
-Depending on the message type, a DHCPv6 server may generate 0, 1 or 2 NameChangeRequests
-during single message processing. Server generates no NameChangeRequests if it is
-not configured to update DNS or it rejects the DNS update for any other reason.
+In the simplest case, when client gets one address from the server, a DHCPv6 server
+may generate 0, 1 or 2 NameChangeRequests during single message processing. 
+Server generates no NameChangeRequests if it is not configured to update DNS
+ or it rejects the DNS update for any other reason.
 
 Server may generate 1 NameChangeRequests in a situation when a client acquires a
 new lease or it releases an existing lease. In the former case, the NameChangeRequest
-type is CHG_ADD, which indicates that the bind10-d2 module should add a new DNS
+type is CHG_ADD, which indicates that the bind10-dhcp-ddns module should add a new DNS
 binding for the client, and it is assumed that there is no DNS binding for this
 client already. In the latter case, the NameChangeRequest type is CHG_REMOVE to
-indicate to the bind10-d2 module that the existing DNS binding should be removed
+indicate to the bind10-dhcp-ddns module that the existing DNS binding should be removed
 from the DNS. The binding consists of the forward and reverse mapping.
 A server may only remove the mapping which it had added. Therefore, the lease database
-holds an information which updates (no update, reverse only update, both reverse and
-forward update) have been performed when the lease was acquired. Server checks
-this information to make a decision which mapping it is supposed to remove when
-a lease is released.
+holds an information which updates (no update, reverse only update, forward only update,
+both reverse and forward update) have been performed when the lease was acquired.
+Server checks this information to make a decision which mapping it is supposed to
+remove when a lease is released.
 
 Server may generate 2 NameChangeRequests in case the client is renewing a lease and
 it already has a DNS binding for that lease. Note, that renewal may be triggered
@@ -147,14 +148,12 @@ received from the client. If the FQDN sent in the message which triggered a rene
 doesn't change (comparing to the information in the lease database) the NameChangeRequest
 is not generated.
 
-@todo The decision about not generating the NameChangeRequest for the client which
-renews the lease but doesn't change its FQDN may be wrong in case it is necessary
-to inform the bind10-d2 module that the lease has been extended. However, the
-house keeper process (which removes DNS bindings for expired leases) will be
-implemented within the bind10-dhcp6 module (not the bind10-d2), so there is no
-need to store lease lifetime information in the bind10-d2 and thus send it there.
+In the more complex scenarios, when server sends multiple IA_NA options, each holding
+multiple IAADDR options, server will generate more NameChangeRequests for a single
+message being processed. That is 0, 1, 2 for the individual IA_NA. Generation of
+the distinct NameChangeRequests for each IADDR is not supported yet.
 
-The DHCPv6 Client FQDN Option is comprises "NOS" flags which communicate to the
+The DHCPv6 Client FQDN Option comprises "NOS" flags which communicate to the
 server what updates (if any), client expects the server to perform. Server
 may be configured to obey client's preference or do FQDN processing in a
 different way. If the server overrides client's preference it will communicate it

+ 6 - 2
src/bin/dhcp6/dhcp6_srv.cc

@@ -1046,6 +1046,8 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
     Option::OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
     for (Option::OptionCollection::const_iterator answer_ia =
              answer_ias.begin(); answer_ia != answer_ias.end(); ++answer_ia) {
+        // @todo IA_NA may contain multiple addresses. We should process
+        // each address individually. Currently we get only one.
         Option6IAAddrPtr iaaddr = boost::static_pointer_cast<
             Option6IAAddr>(answer_ia->second->getOption(D6O_IAADDR));
         // We need an address to create a name-to-address mapping.
@@ -1094,11 +1096,13 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
     // If hostname is non-empty, try to convert it to wire format so as
     // DHCID can be computed from it. This may throw an exception if hostname
     // has invalid format. Again, this should be only possible in case of
-    // manual intervention in the database.
+    // manual intervention in the database. Note that the last parameter
+    // passed to the writeFqdn function forces conversion of the FQDN
+    // to lower case. This is required by the RFC4701, section 3.5.
     // The DHCID computation is further in this function.
     std::vector<uint8_t> hostname_wire;
     try {
-        OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire);
+        OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire, true);
     } catch (const Exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_INVALID_HOSTNAME);
         return;

+ 2 - 0
src/bin/dhcp6/dhcp6_srv.h

@@ -352,6 +352,8 @@ protected:
     /// function are only adding or updating DNS records. In order to generate
     /// requests for DNS records removal, use @c createRemovalNameChangeRequest.
     ///
+    /// @todo Add support for multiple IAADDR options in the IA_NA.
+    ///
     /// @param answer A message beging sent to the Client.
     /// @param fqdn_answer A DHCPv6 Client FQDN %Option which is included in the
     /// response message sent to a client.

+ 19 - 19
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -58,12 +58,11 @@ using namespace std;
 // Maybe it should be isc::test?
 namespace {
 
-const uint8_t FQDN_FLAG_S = 0x1;
-const uint8_t FQDN_FLAG_O = 0x2;
-const uint8_t FQDN_FLAG_N = 0x4;
-
+// This is a test fixture class for testing the processing of the DHCPv6 Client
+// FQDN Option.
 class FqdnDhcpv6SrvTest : public Dhcpv6SrvTest {
 public:
+    // Constructor
     FqdnDhcpv6SrvTest()
         : Dhcpv6SrvTest() {
         // generateClientId assigns DUID to duid_.
@@ -74,9 +73,11 @@ public:
 
     }
 
+    // Destructor
     virtual ~FqdnDhcpv6SrvTest() {
     }
 
+    // Construct the DHCPv6 Client FQDN Option using flags and domain-name.
     Option6ClientFqdnPtr
     createClientFqdn(const uint8_t flags,
                      const std::string& fqdn_name,
@@ -202,12 +203,9 @@ public:
         ASSERT_NO_THROW(answ_fqdn = srv.processClientFqdn(question));
         ASSERT_TRUE(answ_fqdn);
 
-        const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0 ?
-            true : false;
-        const bool flag_s = (exp_flags & Option6ClientFqdn::FLAG_S) != 0 ?
-            true : false;
-        const bool flag_o = (exp_flags & Option6ClientFqdn::FLAG_O) != 0 ?
-            true : false;
+        const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0;
+        const bool flag_s = (exp_flags & Option6ClientFqdn::FLAG_S) != 0;
+        const bool flag_o = (exp_flags & Option6ClientFqdn::FLAG_O) != 0;
 
         EXPECT_EQ(flag_n, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_N));
         EXPECT_EQ(flag_s, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_S));
@@ -225,7 +223,7 @@ public:
         // Create a message of a specified type, add server id and
         // FQDN option.
         OptionPtr srvid = srv.getServerID();
-        Pkt6Ptr req = generatePktWithFqdn(msg_type, FQDN_FLAG_S,
+        Pkt6Ptr req = generatePktWithFqdn(msg_type, Option6ClientFqdn::FLAG_S,
                                           hostname,
                                           Option6ClientFqdn::FULL,
                                           true, srvid);
@@ -298,6 +296,7 @@ public:
         srv.name_change_reqs_.pop();
     }
 
+    // Holds a lease used by a test.
     Lease6Ptr lease_;
 
 };
@@ -1736,7 +1735,8 @@ TEST_F(Dhcpv6SrvTest, ServerID) {
 
 // Test server's response when client requests that server performs AAAA update.
 TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
-    testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "myhost.example.com",
+    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S,
+             "myhost.example.com",
              Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
              "myhost.example.com.");
 }
@@ -1744,7 +1744,7 @@ TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
 // Test server's response when client provides partial domain-name and requests
 // that server performs AAAA update.
 TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
-    testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "myhost",
+    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "myhost",
              Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
              "myhost.example.com.");
 }
@@ -1752,14 +1752,15 @@ TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
 // Test server's response when client provides empty domain-name and requests
 // that server performs AAAA update.
 TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdateNoName) {
-    testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "",
+    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "",
              Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
              "myhost.example.com.");
 }
 
 // Test server's response when client requests no DNS update.
 TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
-    testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_N, "myhost.example.com",
+    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_N,
+             "myhost.example.com",
              Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N,
              "myhost.example.com.");
 }
@@ -1768,7 +1769,8 @@ TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
 // update to the client and this delegation is not allowed.
 TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
     testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
-             Option6ClientFqdn::FULL, FQDN_FLAG_S | FQDN_FLAG_O,
+             Option6ClientFqdn::FULL,
+             Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
              "myhost.example.com.");
 }
 
@@ -1974,9 +1976,7 @@ TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
     NakedDhcpv6Srv srv(0);
 
     // Create a Solicit message with FQDN option and generate server's
-    // response using processRequest function. This will result in the
-    // creation of a new lease and the appropriate NameChangeRequest
-    // to add both reverse and forward mapping to DNS.
+    // response using processSolicit function.
     testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", srv);
     EXPECT_TRUE(srv.name_change_reqs_.empty());
 }

+ 41 - 28
src/lib/dhcp/option6_client_fqdn.cc

@@ -90,7 +90,7 @@ public:
     /// 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 InvalidFqdnOptionFlags if flags are invalid.
+    /// @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.
@@ -139,7 +139,13 @@ Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
 
 Option6ClientFqdnImpl&
 Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) {
-    domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+    if (source.domain_name_) {
+        domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+    } else {
+        domain_name_.reset();
+
+    }
 
     // This assignment should be exception safe.
     flags_ = source.flags_;
@@ -157,7 +163,7 @@ setDomainName(const std::string& domain_name,
     std::string name = isc::util::str::trim(domain_name);
     if (name.empty()) {
         if (name_type == Option6ClientFqdn::FULL) {
-            isc_throw(InvalidFqdnOptionDomainName,
+            isc_throw(InvalidOption6FqdnDomainName,
                       "fully qualified domain-name must not be empty"
                       << " when setting new domain-name for DHCPv6 Client"
                       << " FQDN Option");
@@ -172,7 +178,7 @@ setDomainName(const std::string& domain_name,
             domain_name_type_ = name_type;
 
         } catch (const Exception& ex) {
-            isc_throw(InvalidFqdnOptionDomainName, "invalid domain-name value '"
+            isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
                       << domain_name << "' when setting new domain-name for"
                       << " DHCPv6 Client FQDN Option");
 
@@ -184,7 +190,7 @@ void
 Option6ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
     // The Must Be Zero (MBZ) bits must not be set.
     if (check_mbz && ((flags & ~Option6ClientFqdn::FLAG_MASK) != 0)) {
-        isc_throw(InvalidFqdnOptionFlags,
+        isc_throw(InvalidOption6FqdnFlags,
                   "invalid DHCPv6 Client FQDN Option flags: 0x"
                   << std::hex << static_cast<int>(flags) << std::dec);
     }
@@ -193,7 +199,7 @@ Option6ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
     // MUST be 0. Checking it here.
     if ((flags & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S))
         == (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) {
-        isc_throw(InvalidFqdnOptionFlags,
+        isc_throw(InvalidOption6FqdnFlags,
                   "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");
@@ -227,7 +233,12 @@ Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
             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));
+            try {
+                domain_name_.reset(new isc::dns::Name(name_buf));
+            } catch (const Exception& ex) {
+                isc_throw(InvalidOption6FqdnDomainName, "failed to parse"
+                          "partial domain-name from wire format");
+            }
             // Terminating zero was missing, so set the domain-name type
             // to partial.
             domain_name_type_ = Option6ClientFqdn::PARTIAL;
@@ -237,7 +248,12 @@ Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
             // Name object constructor.
             isc::util::InputBuffer name_buf(&(*first),
                                             std::distance(first, last));
-            domain_name_.reset(new isc::dns::Name(name_buf));
+            try {
+                domain_name_.reset(new isc::dns::Name(name_buf));
+            } catch (const Exception& ex) {
+                isc_throw(InvalidOption6FqdnDomainName, "failed to parse"
+                          "fully qualified domain-name from wire format");
+            }
             // Set the domain-type to fully qualified domain name.
             domain_name_type_ = Option6ClientFqdn::FULL;
         }
@@ -280,15 +296,11 @@ Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
 }
 
 bool
-Option6ClientFqdn::getFlag(const Flag flag) const {
-    // Caller should query for one of the: N, S or O flags. However, enumerator
-    // value of 0x3 is valid (because it belongs to the range between the
-    // lowest and highest enumerator). The value 0x3 represents two flags:
-    // S and O and would cause ambiguity. Therefore, we selectively check
-    // that the flag is equal to one of the explicit enumerator values. If
-    // not, throw an exception.
+Option6ClientFqdn::getFlag(const uint8_t flag) const {
+    // Caller should query for one of the: N, S or O flags. Any other
+    // value is invalid.
     if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N) {
-        isc_throw(InvalidFqdnOptionFlags, "invalid DHCPv6 Client FQDN"
+        isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
                   << " Option flag specified, expected N, S or O");
     }
 
@@ -296,14 +308,16 @@ Option6ClientFqdn::getFlag(const Flag flag) const {
 }
 
 void
-Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
+Option6ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
     // Check that flag is in range between 0x1 and 0x7. Note that this
-    // allows to set or clear multiple flags concurrently.
+    // allows to set or clear multiple flags concurrently. Setting
+    // concurrent bits is discouraged (see header file) but it is not
+    // checked here so it will work.
     if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
-        isc_throw(InvalidFqdnOptionFlags, "invalid DHCPv6 Client FQDN"
+        isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
                   << " Option flag " << std::hex
                   << static_cast<int>(flag) << std::dec
-                  << "is being set. Expected combination of N, S and O");
+                  << "is being set. Expected: N, S or O");
     }
 
     // Copy the current flags into local variable. That way we will be able
@@ -387,15 +401,14 @@ 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() << "' ("
+    stream << in  << "type=" << type_ << "(CLIENT_FQDN)" << ", "
+           << "flags: ("
+           << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
+           << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
+           << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
+           << "domain-name='" << getDomainName() << "' ("
            << (getDomainNameType() == PARTIAL ? "partial" : "full")
-           << ")" << std::endl;
+           << ")";
 
     return (stream.str());
 }

+ 37 - 25
src/lib/dhcp/option6_client_fqdn.h

@@ -25,16 +25,16 @@ namespace dhcp {
 
 /// @brief Exception thrown when invalid flags have been specified for
 /// DHCPv6 Client Fqdn %Option.
-class InvalidFqdnOptionFlags : public Exception {
+class InvalidOption6FqdnFlags : public Exception {
 public:
-    InvalidFqdnOptionFlags(const char* file, size_t line, const char* what) :
+    InvalidOption6FqdnFlags(const char* file, size_t line, const char* what) :
         isc::Exception(file, line, what) {}
 };
 
 /// @brief Exception thrown when invalid domain name is specified.
-class InvalidFqdnOptionDomainName : public Exception {
+class InvalidOption6FqdnDomainName : public Exception {
 public:
-    InvalidFqdnOptionDomainName(const char* file, size_t line,
+    InvalidOption6FqdnDomainName(const char* file, size_t line,
                                 const char* what) :
         isc::Exception(file, line, what) {}
 };
@@ -91,19 +91,13 @@ class Option6ClientFqdnImpl;
 class Option6ClientFqdn : public Option {
 public:
 
-    /// @brief Enumeration holding different flags used in the Client
-    /// FQDN %Option.
-    enum Flag {
-        FLAG_S = 0x01,
-        FLAG_O = 0x02,
-        FLAG_N = 0x04
-    };
-
-    /// @brief Type of the domain-name: partial or full.
-    enum DomainNameType {
-        PARTIAL,
-        FULL
-    };
+    ///
+    ///@name A set of constants setting respective bits in 'flags' field
+    //@{
+    static const uint8_t FLAG_S = 0x01; ///< S bit.
+    static const uint8_t FLAG_O = 0x02; ///< O bit.
+    static const uint8_t FLAG_N = 0x04; ///< N bit.
+    //@}
 
     /// @brief Mask which zeroes MBZ flag bits.
     static const uint8_t FLAG_MASK = 0x7;
@@ -111,16 +105,22 @@ public:
     /// @brief The length of the flag field within DHCPv6 Client Fqdn %Option.
     static const uint16_t FLAG_FIELD_LEN = 1;
 
+    /// @brief Type of the domain-name: partial or full.
+    enum DomainNameType {
+        PARTIAL,
+        FULL
+    };
+
     /// @brief Constructor, creates option instance using flags and domain name.
     ///
     /// This constructor is used to create instance of the option which will be
     /// included in outgoing messages.
     ///
-    /// @param flag a combination of flags to be stored in flags field.
+    /// @param flags a combination of flag bits to be stored in flags field.
     /// @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.
-    explicit Option6ClientFqdn(const uint8_t flag,
+    explicit Option6ClientFqdn(const uint8_t flags,
                                const std::string& domain_name,
                                const DomainNameType domain_name_type = FULL);
 
@@ -129,8 +129,8 @@ public:
     /// 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);
+    /// @param flags A combination of flag bits to be stored in flags field.
+    Option6ClientFqdn(const uint8_t flags);
 
     /// @brief Constructor, creates an option instance from part of the buffer.
     ///
@@ -157,18 +157,30 @@ public:
     /// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
     /// is set.
     ///
-    /// @param flag an enum value specifying the flag to be checked.
+    /// This method checks the single bit of flags field. Therefore, a caller
+    /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as
+    /// an argument of the function. Attempt to use any other value (including
+    /// combinations of these constants) will result in exception.
+    ///
+    /// @param flag A value specifying the flags bit to be checked. It can be
+    /// one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O.
     ///
     /// @return true if the bit of the specified flag is set, false otherwise.
-    bool getFlag(const Flag flag) const;
+    bool getFlag(const uint8_t flag) const;
 
     /// @brief Modifies the value of the specified DHCPv6 Client Fqdn %Option
     /// flag.
     ///
-    /// @param flag an enum value specifying the flag to be modified.
+    /// This method sets the single bit of flags field. Therefore, a caller
+    /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as
+    /// an argument of the function. Attempt to use any other value (including
+    /// combinations of these constants) will result in exception.
+    ///
+    /// @param flag A value specifying the flags bit to be modified. It can
+    /// be one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O.
     /// @param set a boolean value which indicates whether flag should be
     /// set (true), or cleared (false).
-    void setFlag(const Flag flag, const bool set);
+    void setFlag(const uint8_t flag, const bool set);
 
     /// @brief Sets the flag field value to 0.
     void resetFlags();

+ 3 - 2
src/lib/dhcp/option_data_types.cc

@@ -212,9 +212,10 @@ OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
 
 void
 OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
-                              std::vector<uint8_t>& buf) {
+                              std::vector<uint8_t>& buf,
+                              bool downcase) {
     try {
-        isc::dns::Name name(fqdn);
+        isc::dns::Name name(fqdn, downcase);
         isc::dns::LabelSequence labels(name);
         if (labels.getDataLength() > 0) {
             size_t read_len = 0;

+ 4 - 1
src/lib/dhcp/option_data_types.h

@@ -366,11 +366,14 @@ public:
     ///
     /// @param fqdn fully qualified domain name to be written.
     /// @param [out] buf output buffer.
+    /// @param downcase indicates if the FQDN should be converted to lower
+    /// case (if true). By default it is not converted.
     ///
     /// @throw isc::dhcp::BadDataTypeCast if provided FQDN
     /// is invalid.
     static void writeFqdn(const std::string& fqdn,
-                          std::vector<uint8_t>& buf);
+                          std::vector<uint8_t>& buf,
+                          const bool downcase = false);
 
     /// @brief Read string value from a buffer.
     ///

+ 140 - 56
src/lib/dhcp/tests/option6_client_fqdn_unittest.cc

@@ -24,15 +24,6 @@ namespace {
 using namespace isc;
 using namespace isc::dhcp;
 
-// 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) {
@@ -52,12 +43,12 @@ TEST(Option6ClientFqdnTest, constructEmptyName) {
     // Constructor should not accept empty fully qualified domain name.
     EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
                                    Option6ClientFqdn::FULL),
-                 InvalidFqdnOptionDomainName);
+                 InvalidOption6FqdnDomainName);
     // 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);
+                 InvalidOption6FqdnDomainName);
 
     // Try different constructor.
     ASSERT_NO_THROW(
@@ -129,10 +120,40 @@ TEST(Option6ClientFqdnTest, copyConstruct) {
     EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
 }
 
+// This test verifies that copy constructor makes a copy of the option, when
+// domain-name is empty.
+TEST(Option6ClientFqdnTest, copyConstructEmptyDomainName) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S));
+    );
+    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("", 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
+        Option6ClientFqdn::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.
@@ -154,6 +175,37 @@ TEST(Option6ClientFqdnTest, constructFromWire) {
     EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
 }
 
+// Verify that exception is thrown if the domain-name label is
+// longer than 63.
+TEST(Option6ClientFqdnTest, constructFromWireTooLongLabel) {
+    OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+    in_buf.push_back(70);
+    in_buf.insert(in_buf.end(), 70, 109);
+    in_buf.push_back(0);
+
+    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidOption6FqdnDomainName);
+}
+
+// Verify that exception is thrown if the overall length of the domain-name
+// is over 255.
+TEST(Option6ClientFqdnTest, constructFromWireTooLongDomainName) {
+    OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+    for (int i = 0; i < 26;  ++i) {
+        in_buf.push_back(10);
+        in_buf.insert(in_buf.end(), 10, 109);
+    }
+    in_buf.push_back(0);
+
+    try {
+        Option6ClientFqdn(in_buf.begin(), in_buf.end());
+    } catch (const Exception& ex) {
+        std::cout << ex.what() << std::endl;
+    }
+    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidOption6FqdnDomainName);
+}
+
 // This test verifies that truncated option is rejected.
 TEST(Option6ClientFqdnTest, constructFromWireTruncated) {
     // Empty buffer is invalid. It should be at least one octet long.
@@ -166,7 +218,7 @@ TEST(Option6ClientFqdnTest, constructFromWireTruncated) {
 // domain-name is parsed correctly.
 TEST(Option6ClientFqdnTest, constructFromWirePartial) {
     const uint8_t in_data[] = {
-        FLAG_N,                              // flags
+        Option6ClientFqdn::FLAG_N,           // flags
         6, 109, 121, 104, 111, 115, 116      // myhost
     };
     size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
@@ -189,7 +241,7 @@ TEST(Option6ClientFqdnTest, constructFromWirePartial) {
 // 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);
+    OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
     // Create option instance. Check that constructor doesn't throw.
     boost::scoped_ptr<Option6ClientFqdn> option;
     ASSERT_NO_THROW(
@@ -265,6 +317,54 @@ TEST(Option6ClientFqdnTest, assignment) {
     EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
 }
 
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another, when the domain-name is empty.
+TEST(Option6ClientFqdnTest, assignmentEmptyDomainName) {
+    ASSERT_NO_THROW(
+        Option6ClientFqdn(static_cast<uint8_t>(Option6ClientFqdn::FLAG_S))
+    );
+
+    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);
+
+    // 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("", option.getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::PARTIAL, option.getDomainNameType());
+
+    // Create a second option.
+    Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
+                              "myhost",
+                              Option6ClientFqdn::PARTIAL);
+
+    // Verify that 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("", 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(Option6ClientFqdnTest, constructInvalidFlags) {
@@ -279,13 +379,13 @@ TEST(Option6ClientFqdnTest, constructInvalidFlags) {
     // Zero (MBZ) bitset (00001100b).
     flags = 0x14;
     EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
-                 InvalidFqdnOptionFlags);
+                 InvalidOption6FqdnFlags);
 
     // According to RFC 4704, section 4.1. if the N bit is set the S bit MUST
     // be zero. If both are set, constructor is expected to throw.
     flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
     EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
-                 InvalidFqdnOptionFlags);
+                 InvalidOption6FqdnFlags);
 }
 
 // This test verifies that constructor which parses option from on-wire format
@@ -293,14 +393,14 @@ TEST(Option6ClientFqdnTest, constructInvalidFlags) {
 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);
+    OptionBuffer in_buf(Option6ClientFqdn::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;
+    in_buf[0] = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
     EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
-                 InvalidFqdnOptionFlags);
+                 InvalidOption6FqdnFlags);
 }
 
 // This test verifies that if invalid domain name is used the constructor
@@ -313,14 +413,11 @@ TEST(Option6ClientFqdnTest, constructInvalidName) {
 
     // Specify invalid domain name and expect that exception is thrown.
     EXPECT_THROW(Option6ClientFqdn(0, "my...host.example.com"),
-                 InvalidFqdnOptionDomainName);
+                 InvalidOption6FqdnDomainName);
 }
 
-// This test verifies that getFlag throws an exception if flag value of 0x3
-// is specified.This test does not verify other invalid values, e.g. 0x5,
-// 0x6 etc. because conversion of int values which do not belong to the range
-// between the lowest and highest enumerator will give an undefined
-// result.
+// This test verifies that getFlag throws an exception if flag value other
+// than FLAG_N, FLAG_S, FLAG_O is specified.
 TEST(Option6ClientFqdnTest, getFlag) {
     boost::scoped_ptr<Option6ClientFqdn> option;
     ASSERT_NO_THROW(
@@ -328,11 +425,10 @@ TEST(Option6ClientFqdnTest, getFlag) {
     );
     ASSERT_TRUE(option);
 
-    // The 0x3 is a valid enumerator value (even though it is not explicitly
-    // included in the Option6ClientFqdn::Flag definition). The getFlag()
-    // function should not accept it. Only explicit values are accepted.
-    EXPECT_THROW(option->getFlag(static_cast<Option6ClientFqdn::Flag>(0x3)),
-                                 InvalidFqdnOptionFlags);
+    // The 0x3 (binary 011) specifies two distinct bits in the flags field.
+    // This value is ambiguous for getFlag function and this function doesn't
+    // know which flag the caller is attempting to check.
+    EXPECT_THROW(option->getFlag(0x3), InvalidOption6FqdnFlags);
 }
 
 // This test verifies that flags can be modified and that incorrect flags
@@ -361,7 +457,7 @@ TEST(Option6ClientFqdnTest, setFlag) {
     // Set S = 1, this should throw exception because S and N must not
     // be set in the same time.
     ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true),
-                 InvalidFqdnOptionFlags);
+                 InvalidOption6FqdnFlags);
 
     // Set N = 0
     ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, false));
@@ -374,7 +470,7 @@ TEST(Option6ClientFqdnTest, setFlag) {
 
     // Set N = 1, this should result in exception because S = 1
     ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true),
-                 InvalidFqdnOptionFlags);
+                 InvalidOption6FqdnFlags);
 
     // Set O = 0
     ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, false));
@@ -382,14 +478,10 @@ TEST(Option6ClientFqdnTest, setFlag) {
 
     // Try out of bounds settings.
     uint8_t flags = 0;
-    ASSERT_THROW(option->setFlag(static_cast<Option6ClientFqdn::Flag>(flags),
-                                 true),
-                 InvalidFqdnOptionFlags);
+    ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags);
 
     flags = 0x14;
-    ASSERT_THROW(option->setFlag(static_cast<Option6ClientFqdn::Flag>(flags),
-                                 true),
-                 InvalidFqdnOptionFlags);
+    ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags);
 }
 
 // This test verifies that flags field of the option is set to 0 when resetFlags
@@ -448,9 +540,9 @@ TEST(Option6ClientFqdnTest, setDomainName) {
 
     // Fully qualified domain-names must not be empty.
     EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),
-                 InvalidFqdnOptionDomainName);
+                 InvalidOption6FqdnDomainName);
     EXPECT_THROW(option->setDomainName(" ", Option6ClientFqdn::FULL),
-                 InvalidFqdnOptionDomainName);
+                 InvalidOption6FqdnDomainName);
 }
 
 // This test verifies that current domain-name can be reset to empty one.
@@ -487,7 +579,7 @@ TEST(Option6ClientFqdnTest, pack) {
     // Prepare reference data.
     const uint8_t ref_data[] = {
         0, 39, 0, 21,                        // header
-        FLAG_S,                              // flags
+        Option6ClientFqdn::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.
@@ -519,7 +611,7 @@ TEST(Option6ClientFqdnTest, packPartial) {
     // Prepare reference data.
     const uint8_t ref_data[] = {
         0, 39, 0, 8,                         // header
-        FLAG_S,                              // flags
+        Option6ClientFqdn::FLAG_S,           // flags
         6, 109, 121, 104, 111, 115, 116      // myhost
     };
     size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
@@ -551,7 +643,7 @@ TEST(Option6ClientFqdnTest, unpack) {
     EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
 
     const uint8_t in_data[] = {
-        FLAG_S,                              // flags
+        Option6ClientFqdn::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.
@@ -590,7 +682,7 @@ TEST(Option6ClientFqdnTest, unpackPartial) {
     EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
 
     const uint8_t in_data[] = {
-        FLAG_S,                              // flags
+        Option6ClientFqdn::FLAG_S,           // flags
         6, 109, 121, 104, 111, 115, 116      // myhost
     };
     size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
@@ -636,12 +728,8 @@ TEST(Option6ClientFqdnTest, toText) {
     // 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";
+        "  type=39(CLIENT_FQDN), flags: (N=1, O=1, S=0), "
+        "domain-name='myhost.example.com.' (full)";
     const int indent = 2;
     EXPECT_EQ(ref_string, option->toText(indent));
 
@@ -655,12 +743,8 @@ TEST(Option6ClientFqdnTest, toText) {
                                            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";
+        "type=39(CLIENT_FQDN), flags: (N=0, O=0, S=0), "
+        "domain-name='myhost' (partial)";
     EXPECT_EQ(ref_string, option->toText());
 }
 

+ 4 - 0
src/lib/dhcp_ddns/ncr_msg.h

@@ -103,6 +103,10 @@ public:
 
     /// @brief Sets the DHCID value based on the DUID and FQDN.
     ///
+    /// This function requires that the FQDN conforms to the section 3.5
+    /// of the RFC4701, which says that the FQDN must be in lowercase.
+    /// This function doesn't validate if it really converted.
+    ///
     /// @param duid A @c isc::dhcp::DUID object encapsulating DUID.
     /// @param wire_fqdn A on-wire canonical representation of the FQDN.
     void fromDUID(const isc::dhcp::DUID& duid,

+ 61 - 0
src/lib/dhcp_ddns/tests/ncr_unittests.cc

@@ -327,6 +327,67 @@ TEST(NameChangeRequestTest, dhcidFromDUID) {
     EXPECT_EQ(dhcid_ref, dhcid.toStr());
 }
 
+// Test that DHCID is correctly created when the DUID has minimal length (1).
+TEST(NameChangeRequestTest, dhcidFromMinDUID) {
+    D2Dhcid dhcid;
+
+    // Create DUID.
+    uint8_t duid_data[] = { 1 };
+    DUID duid(duid_data, sizeof(duid_data));
+
+    // Create FQDN in on-wire format: myhost.example.com. It is encoded
+    // as a set of labels, each preceded by its length. The whole FQDN
+    // is zero-terminated.
+    const uint8_t fqdn_data[] = {
+        6, 109, 121, 104, 111, 115, 116,     // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101, // example.
+        3, 99, 111, 109, 0                   // com.
+    };
+    std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+    // Create DHCID.
+    ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
+
+    // The reference DHCID (represented as string of hexadecimal digits)
+    // has been calculated using one of the online calculators.
+    std::string dhcid_ref = "000201F89004F73E60CAEDFF514E11CB91D"
+        "1F45C8F0A55D4BC4C688484A819F8EA4074";
+
+    // Make sure that the DHCID is valid.
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that DHCID is correctly created when the DUID has maximal length (128).
+TEST(NameChangeRequestTest, dhcidFromMaxDUID) {
+    D2Dhcid dhcid;
+
+    // Create DUID.
+    std::vector<uint8_t> duid_data(128, 1);
+    DUID duid(&duid_data[0], duid_data.size());
+
+    // Create FQDN in on-wire format: myhost.example.com. It is encoded
+    // as a set of labels, each preceded by its length. The whole FQDN
+    // is zero-terminated.
+    const uint8_t fqdn_data[] = {
+        6, 109, 121, 104, 111, 115, 116,     // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101, // example.
+        3, 99, 111, 109, 0                   // com.
+    };
+    std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+    // Create DHCID.
+    ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
+
+    // The reference DHCID (represented as string of hexadecimal digits)
+    // has been calculated using one of the online calculators.
+    std::string dhcid_ref = "00020137D8FBDC0585B44DFA03FAD2E36C6"
+        "159737D545A12EFB40B0D88D110A5748234";
+
+    // Make sure that the DHCID is valid.
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+
 /// @brief Verifies the fundamentals of converting from and to JSON.
 /// It verifies that:
 /// 1. A NameChangeRequest can be created from a valid JSON string.