BIND 10 trac3035, updated. df1550729f9ea49d6b0f90e677726fc0dec1f804 [3035] Implemented function which generates NameChangeRequests for a lease.

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Sep 4 12:35:38 UTC 2013


The branch, trac3035 has been updated
       via  df1550729f9ea49d6b0f90e677726fc0dec1f804 (commit)
       via  782e4e8e612336d637f848c1e2c754906754d79d (commit)
       via  b526ece8f040116089977371782a40e7956d643b (commit)
       via  cafeb7250bc2c3400c0d3e7736f6cf3971bf741b (commit)
       via  e91d83ea552f8704e04f099a701a838e14947368 (commit)
       via  b0131d52efae513ed8d86f6c0b3bb4391d32ad16 (commit)
       via  c0554a39903141b168944a9c67ef378e5c923e4a (commit)
       via  ecd5a9bc58e0f15933f5508afec02a0d48ba342e (commit)
       via  fcedc148b175e78928bf0f46d904e088ef549b00 (commit)
      from  847cada3ef9794906046f1b1094e066bed55b581 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit df1550729f9ea49d6b0f90e677726fc0dec1f804
Author: Marcin Siodelski <marcin at isc.org>
Date:   Wed Sep 4 14:35:05 2013 +0200

    [3035] Implemented function which generates NameChangeRequests for a lease.

commit 782e4e8e612336d637f848c1e2c754906754d79d
Author: Marcin Siodelski <marcin at isc.org>
Date:   Wed Sep 4 08:11:17 2013 +0200

    [3035] Added functions to check that two leases belong to the same client.

commit b526ece8f040116089977371782a40e7956d643b
Author: Marcin Siodelski <marcin at isc.org>
Date:   Tue Sep 3 18:39:48 2013 +0200

    [3035] Generate NameChangeRequest when new lease is acquired.

commit cafeb7250bc2c3400c0d3e7736f6cf3971bf741b
Author: Marcin Siodelski <marcin at isc.org>
Date:   Tue Sep 3 11:36:40 2013 +0200

    [3035] Include HW type in the digest when generating DHCID from HW addr.

commit e91d83ea552f8704e04f099a701a838e14947368
Author: Marcin Siodelski <marcin at isc.org>
Date:   Tue Sep 3 11:09:16 2013 +0200

    [3035] Renamed DhcidComputeError to DhcidRdataComputeError.

commit b0131d52efae513ed8d86f6c0b3bb4391d32ad16
Author: Marcin Siodelski <marcin at isc.org>
Date:   Tue Sep 3 10:52:37 2013 +0200

    [3035] Implemented functions to compute DHCID from Client Id and HW address

commit c0554a39903141b168944a9c67ef378e5c923e4a
Author: Marcin Siodelski <marcin at isc.org>
Date:   Mon Sep 2 17:21:11 2013 +0200

    [3035] Basic implementation of the function which computes DHCID.

commit ecd5a9bc58e0f15933f5508afec02a0d48ba342e
Author: Marcin Siodelski <marcin at isc.org>
Date:   Mon Sep 2 17:20:34 2013 +0200

    [3035] Added functions to construct DHCID object from raw buffer.

commit fcedc148b175e78928bf0f46d904e088ef549b00
Author: Marcin Siodelski <marcin at isc.org>
Date:   Mon Sep 2 17:19:53 2013 +0200

    [3035] Implemented DHCPv4 srv function to process an FQDN option.

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

Summary of changes:
 src/bin/dhcp4/Makefile.am                   |    1 +
 src/bin/dhcp4/dhcp4_srv.cc                  |  207 ++++++++++++++++
 src/bin/dhcp4/dhcp4_srv.h                   |   39 +++
 src/bin/dhcp4/tests/Makefile.am             |    1 +
 src/bin/dhcp4/tests/dhcp4_test_utils.h      |    3 +
 src/bin/dhcp4/tests/fqdn_unittest.cc        |  218 ++++++++++++++++-
 src/lib/dhcp_ddns/ncr_msg.cc                |  104 ++++++--
 src/lib/dhcp_ddns/ncr_msg.h                 |   56 +++++
 src/lib/dhcp_ddns/tests/ncr_unittests.cc    |  346 ++++++++++++++++-----------
 src/lib/dhcpsrv/lease_mgr.cc                |   80 ++++---
 src/lib/dhcpsrv/lease_mgr.h                 |   28 +++
 src/lib/dhcpsrv/tests/lease_mgr_unittest.cc |  141 +++++++++--
 12 files changed, 992 insertions(+), 232 deletions(-)

-----------------------------------------------------------------------
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
index 80b7fc3..6251b51 100644
--- a/src/bin/dhcp4/Makefile.am
+++ b/src/bin/dhcp4/Makefile.am
@@ -56,6 +56,7 @@ nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
 EXTRA_DIST += dhcp4_messages.mes
 
 b10_dhcp4_LDADD  = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 253270a..5cc0e5f 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -43,6 +43,7 @@
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
 using namespace isc::hooks;
 using namespace isc::log;
 using namespace std;
@@ -76,6 +77,34 @@ Dhcp4Hooks Hooks;
 namespace isc {
 namespace dhcp {
 
+namespace {
+
+// The following constants describe server's behavior with respect to the
+// DHCPv4 Client FQDN Option sent by a client. They will be removed
+// when DDNS parameters for DHCPv4 are implemented with the ticket #3033.
+
+// Should server always include the FQDN option in its response, regardless
+// if it has been requested in Parameter Request List Option (Disabled).
+const bool FQDN_ALWAYS_INCLUDE = false;
+// Enable A RR update delegation to the client (Disabled).
+const bool FQDN_ALLOW_CLIENT_UPDATE = false;
+// Globally enable updates (Enabled).
+const bool FQDN_ENABLE_UPDATE = true;
+// The partial name generated for the client if empty name has been
+// supplied.
+const char* FQDN_GENERATED_PARTIAL_NAME = "myhost";
+// Do update, even if client requested no updates with N flag (Disabled).
+const bool FQDN_OVERRIDE_NO_UPDATE = false;
+// Server performs an update when client requested delegation (Enabled).
+const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
+// The fully qualified domain-name suffix if partial name provided by
+// a client.
+const char* FQDN_PARTIAL_SUFFIX = "example.com";
+// Should server replace the domain-name supplied by the client (Disabled).
+const bool FQDN_REPLACE_CLIENT_NAME = false;
+
+}
+
 /// @brief file name of a server-id file
 ///
 /// Server must store its server identifier in persistent storage that must not
@@ -540,6 +569,52 @@ Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
     return (addrs[0].toText());
 }
 
+isc::dhcp_ddns::D2Dhcid
+Dhcpv4Srv::computeDhcid(const Lease4Ptr& lease) {
+    if (!lease) {
+        isc_throw(DhcidComputeError, "a pointer to the lease must be not"
+                  " NULL to compute DHCID");
+
+    } else if (lease->hostname_.empty()) {
+        isc_throw(DhcidComputeError, "unable to compute the DHCID for the"
+                  " lease which has empty hostname set");
+
+    }
+
+    // In order to compute DHCID the client's hostname must be encoded in
+    // canonical wire format. It is unlikely that the FQDN is malformed
+    // because it is validated by the classes which encapsulate options
+    // carrying client FQDN. However, if the client name was carried in the
+    // Hostname option it is more likely as it carries the hostname as a
+    // regular string.
+    std::vector<uint8_t> fqdn_wire;
+    try {
+        OptionDataTypeUtil::writeFqdn(lease->hostname_, fqdn_wire, true);
+
+    } catch (const Exception& ex) {
+        isc_throw(DhcidComputeError, "unable to compute DHCID because the"
+                  " hostname: " << lease->hostname_ << " is invalid");
+
+    }
+
+    // Prefer client id to HW address to compute DHCID. If Client Id is
+    // NULL, use HW address.
+    try {
+        if (lease->client_id_) {
+            return (D2Dhcid(lease->client_id_->getClientId(), fqdn_wire));
+
+        } else {
+            HWAddrPtr hwaddr(new HWAddr(lease->hwaddr_, HTYPE_ETHER));
+            return (D2Dhcid(hwaddr, fqdn_wire));
+        }
+    } catch (const Exception& ex) {
+        isc_throw(DhcidComputeError, "unable to compute DHCID: "
+                  << ex.what());
+
+    }
+
+}
+
 void
 Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     answer->setIface(question->getIface());
@@ -695,6 +770,74 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
     fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, 0);
     fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, 0);
 
+    // Conditions when N flag has to be set to indicate that server will not
+    // perform DNS updates:
+    // 1. Updates are globally disabled,
+    // 2. Client requested no update and server respects it,
+    // 3. Client requested that the foward DNS update is delegated to the client
+    //    but server neither respects requests for forward update delegation nor
+    //    it is configured to send update on its own when client requested
+    //    delegation.
+    if (!FQDN_ENABLE_UPDATE ||
+        (fqdn->getFlag(Option4ClientFqdn::FLAG_N) &&
+         !FQDN_OVERRIDE_NO_UPDATE) ||
+        (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) &&
+         !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) {
+        fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, true);
+
+    // Conditions when S flag is set to indicate that server will perform DNS
+    // update on its own:
+    // 1. Client requested that server performs DNS update and DNS updates are
+    //    globally enabled.
+    // 2. Client requested that server delegates forward update to the client
+    //    but server doesn't respect requests for delegation and it is
+    // configured to perform an update on its own when client requested the
+    // delegation.
+    } else  if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) ||
+                (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) &&
+                 !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
+        fqdn_resp->setFlag(Option4ClientFqdn::FLAG_S, true);
+    }
+
+    // Server MUST set the O flag if it has overriden the client's setting
+    // of S flag.
+    if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) !=
+        fqdn_resp->getFlag(Option4ClientFqdn::FLAG_S)) {
+        fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, true);
+    }
+
+    // If client suppled partial or empty domain-name, server should generate
+    // one.
+    if (fqdn->getDomainNameType() == Option4ClientFqdn::PARTIAL) {
+        std::ostringstream name;
+        if (fqdn->getDomainName().empty()) {
+            name << FQDN_GENERATED_PARTIAL_NAME;
+        } else {
+            name << fqdn->getDomainName();
+        }
+        name << "." << FQDN_PARTIAL_SUFFIX;
+        fqdn_resp->setDomainName(name.str(), Option4ClientFqdn::FULL);
+
+    // Server may be configured to replace a name supplied by a client, even if
+    // client supplied fully qualified domain-name.
+    } else if(FQDN_REPLACE_CLIENT_NAME) {
+        std::ostringstream name;
+        name << FQDN_GENERATED_PARTIAL_NAME << "." << FQDN_PARTIAL_SUFFIX;
+        fqdn_resp->setDomainName(name.str(), Option4ClientFqdn::FULL);
+    }
+
+    // Add FQDN option to the response message. Note that, there may be some
+    // cases when server may choose not to include the FQDN option in a
+    // response to a client. In such cases, the FQDN should be removed from the
+    // outgoing message. In theory we could cease to include the FQDN option
+    // in this function until it is confirmed that it should be included.
+    // However, we include it here for simplicity. Functions used to acquire
+    // lease for a client will scan the response message for FQDN and if it
+    // is found they will take necessary actions to store the FQDN information
+    // in the lease database as well as to generate NameChangeRequests to DNS.
+    // If we don't store the option in the reponse message, we will have to
+    // propagate it in the different way to the functions which acquire the
+    // lease. This would require modifications to the API of this class.
     answer->addOption(fqdn_resp);
 }
 
@@ -703,6 +846,70 @@ Dhcpv4Srv::processHostnameOption(const Pkt4Ptr&, Pkt4Ptr&) {
 }
 
 void
+Dhcpv4Srv::createNameChangeRequests(const Lease4Ptr& lease,
+                                    const Lease4Ptr& old_lease) {
+    if (!lease) {
+        isc_throw(isc::Unexpected,
+                  "NULL lease specified when creating NameChangeRequest");
+    }
+
+    // If old lease is not NULL, it is an indication that the lease has
+    // just been renewed. In such case we may need to generate the
+    // additional NameChangeRequest to remove an existing entry before
+    // we create a NameChangeRequest to add the entry for an updated lease.
+    // We may also decide not to generate any requests at all. This is when
+    // we discover that nothing has changed in the client's FQDN data.
+    if (old_lease) {
+        if (!lease->matches(*old_lease)) {
+            isc_throw(isc::Unexpected,
+                      "there is no match between the current instance of the"
+                      " lease: " << lease->toText() << ", and the previous"
+                      " instance: " << lease->toText());
+        } else {
+            // There will be a NameChangeRequest generated to remove existing
+            // DNS entries if the following conditions are met:
+            // - The hostname is set for the existing lease, we can't generate
+            //   removal request for non-existent hostname.
+            // - A server has performed reverse, forward or both updates.
+            // - FQDN data between the new and old lease do not match.
+            if ((!old_lease->hostname_.empty() &&
+                (old_lease->fqdn_fwd_ || old_lease->fqdn_rev_)) &&
+                ((lease->hostname_ != old_lease->hostname_) ||
+                 (lease->fqdn_fwd_ != old_lease->fqdn_fwd_) ||
+                 (lease->fqdn_rev_ != old_lease->fqdn_rev_))) {
+                D2Dhcid dhcid = computeDhcid(old_lease);
+                NameChangeRequest ncr(isc::dhcp_ddns::CHG_REMOVE,
+                                      old_lease->fqdn_fwd_,
+                                      old_lease->fqdn_rev_,
+                                      old_lease->hostname_,
+                                      old_lease->addr_.toText(),
+                                      dhcid, 0, old_lease->valid_lft_);
+                name_change_reqs_.push(ncr);
+
+            // If FQDN data from both leases match, there is no need to update.
+            } else if ((lease->hostname_ == old_lease->hostname_) &&
+                       (lease->fqdn_fwd_ == old_lease->fqdn_fwd_) &&
+                       (lease->fqdn_rev_ == old_lease->fqdn_rev_)) {
+                return;
+            }
+
+        }
+    }
+
+    // We may need to generate the NameChangeRequest for the new lease. But,
+    // this is only when hostname is set and if forward or reverse update has
+    // been requested.
+    if (!lease->hostname_.empty() && (lease->fqdn_fwd_ || lease->fqdn_rev_)) {
+        D2Dhcid dhcid = computeDhcid(lease);
+        NameChangeRequest ncr(isc::dhcp_ddns::CHG_ADD,
+                              lease->fqdn_fwd_, lease->fqdn_rev_,
+                              lease->hostname_, lease->addr_.toText(),
+                              dhcid, 0, lease->valid_lft_);
+        name_change_reqs_.push(ncr);
+    }
+}
+
+void
 Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
 
     // We need to select a subnet the client is connected in.
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 5c9d776..bbc0912 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -19,6 +19,7 @@
 #include <dhcp/pkt4.h>
 #include <dhcp/option.h>
 #include <dhcp/option4_client_fqdn.h>
+#include <dhcp_ddns/ncr_msg.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <hooks/callout_handle.h>
@@ -26,10 +27,18 @@
 #include <boost/noncopyable.hpp>
 
 #include <iostream>
+#include <queue>
 
 namespace isc {
 namespace dhcp {
 
+/// @brief Exception thrown when DHCID computation failed.
+class DhcidComputeError : public isc::Exception {
+public:
+    DhcidComputeError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
 /// @brief DHCPv4 server service.
 ///
 /// This singleton class represents DHCPv4 server. It contains all
@@ -302,6 +311,24 @@ private:
     void processHostnameOption(const Pkt4Ptr& query, Pkt4Ptr& answer);
 
 protected:
+
+    /// @brief Creates NameChangeRequests which correspond to the lease
+    /// which has been acquired.
+    ///
+    /// If this function is called whe an existing lease is renewed, it
+    /// may generate NameChangeRequest to remove existing DNS entries which
+    /// correspond to the old lease instance. This function may cease to
+    /// generate NameChangeRequests if the notion of the client's FQDN hasn't
+    /// changed between an old and new lease.
+    ///
+    /// @param lease A pointer to the new lease which has been acquired.
+    /// @param old_lease A pointer to the instance of the old lease which has
+    /// been replaced by the new lease passed in the first argument. The NULL
+    /// value indicates that the new lease has been allocated, rather than
+    /// lease being renewed.
+    void createNameChangeRequests(const Lease4Ptr& lease,
+                                  const Lease4Ptr& old_lease);
+
     /// @brief Attempts to renew received addresses
     ///
     /// Attempts to renew existing lease. This typically includes finding a lease that
@@ -376,6 +403,12 @@ protected:
     /// @return string representation
     static std::string srvidToString(const OptionPtr& opt);
 
+    /// @brief Computes DHCID from a lease.
+    ///
+    /// @param lease A pointer to the structure describing a lease.
+    /// @return An object encapsulating DHCID to be used for DNS updates.
+    static isc::dhcp_ddns::D2Dhcid computeDhcid(const Lease4Ptr& lease);
+
     /// @brief Selects a subnet for a given client's packet.
     ///
     /// @param question client's message
@@ -422,6 +455,12 @@ private:
     int hook_index_pkt4_receive_;
     int hook_index_subnet4_select_;
     int hook_index_pkt4_send_;
+
+protected:
+
+    /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects which
+    /// are waiting for sending  to b10-dhcp-ddns module.
+    std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;
 };
 
 }; // namespace isc::dhcp
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index cdeaae9..4c25501 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -82,6 +82,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h
index e84e6dc..75ec22a 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.h
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h
@@ -127,6 +127,8 @@ public:
     std::list<Pkt4Ptr> fake_sent_;
 
     using Dhcpv4Srv::adjustRemoteAddr;
+    using Dhcpv4Srv::computeDhcid;
+    using Dhcpv4Srv::createNameChangeRequests;
     using Dhcpv4Srv::processDiscover;
     using Dhcpv4Srv::processRequest;
     using Dhcpv4Srv::processRelease;
@@ -139,6 +141,7 @@ public:
     using Dhcpv4Srv::writeServerID;
     using Dhcpv4Srv::sanityCheck;
     using Dhcpv4Srv::srvidToString;
+    using Dhcpv4Srv::name_change_reqs_;
 };
 
 /// @brief Dummy Packet Filtering class.
diff --git a/src/bin/dhcp4/tests/fqdn_unittest.cc b/src/bin/dhcp4/tests/fqdn_unittest.cc
index 0b4c2c9..173c7bf 100644
--- a/src/bin/dhcp4/tests/fqdn_unittest.cc
+++ b/src/bin/dhcp4/tests/fqdn_unittest.cc
@@ -16,6 +16,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp_ddns/ncr_msg.h>
 
 #include <gtest/gtest.h>
 #include <boost/scoped_ptr.hpp>
@@ -23,6 +24,7 @@
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
 using namespace isc::test;
 
 namespace {
@@ -36,6 +38,24 @@ public:
         delete srv_;
     }
 
+    // Create a lease to be used by various tests.
+    Lease4Ptr createLease(const isc::asiolink::IOAddress& addr,
+                          const std::string& hostname,
+                          const bool fqdn_fwd,
+                          const bool fqdn_rev) {
+        const uint8_t hwaddr[] = { 0, 1, 2, 3, 4, 5, 6 };
+        Lease4Ptr lease(new Lease4(addr, hwaddr, sizeof(hwaddr),
+                                   &generateClientId()->getData()[0],
+                                   generateClientId()->getData().size(),
+                                   100, 50, 75, time(NULL), subnet_->getID()));
+        // @todo Set this through the Lease4 constructor.
+        lease->hostname_ = hostname;
+        lease->fqdn_fwd_ = fqdn_fwd;
+        lease->fqdn_rev_ = fqdn_rev;
+
+        return (lease);
+    }
+
     // Create an instance of the DHCPv4 Client FQDN Option.
     Option4ClientFqdnPtr
     createClientFqdn(const uint8_t flags,
@@ -59,7 +79,8 @@ public:
                                 const std::string& fqdn_domain_name,
                                 const Option4ClientFqdn::DomainNameType
                                 fqdn_type,
-                                const bool include_prl) {
+                                const bool include_prl,
+                                const bool include_clientid = true) {
         Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
         pkt->setRemoteAddr(IOAddress("192.0.2.3"));
         // For DISCOVER we don't include server id, because client broadcasts
@@ -67,8 +88,10 @@ public:
         if (msg_type != DHCPDISCOVER) {
             pkt->addOption(srv_->getServerID());
         }
-        // Client id is required.
-        pkt->addOption(generateClientId());
+
+        if (include_clientid) {
+            pkt->addOption(generateClientId());
+        }
 
         // Create Client FQDN Option with the specified flags and
         // domain-name.
@@ -121,11 +144,89 @@ public:
 
     }
 
-private:
+    // Verify that NameChangeRequest holds valid values.
+    void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
+                                 const bool reverse, const bool forward,
+                                 const std::string& addr,
+                                 const std::string& fqdn,
+                                 const std::string& dhcid,
+                                 const uint16_t expires,
+                                 const uint16_t len) {
+        NameChangeRequest ncr = srv_->name_change_reqs_.front();
+        EXPECT_EQ(type, ncr.getChangeType());
+        EXPECT_EQ(forward, ncr.isForwardChange());
+        EXPECT_EQ(reverse, ncr.isReverseChange());
+        EXPECT_EQ(addr, ncr.getIpAddress());
+        EXPECT_EQ(fqdn, ncr.getFqdn());
+        EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
+        EXPECT_EQ(expires, ncr.getLeaseExpiresOn());
+        EXPECT_EQ(len, ncr.getLeaseLength());
+        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
+        srv_->name_change_reqs_.pop();
+    }
+
     NakedDhcpv4Srv* srv_;
 
 };
 
+// Test that the exception is thrown if lease pointer specified as the argument
+// of computeDhcid function is NULL.
+TEST_F(FqdnDhcpv4SrvTest, dhcidNullLease) {
+    Lease4Ptr lease;
+    EXPECT_THROW(srv_->computeDhcid(lease), isc::dhcp::DhcidComputeError);
+
+}
+
+// Test that the appropriate exception is thrown if the lease object used
+// to compute DHCID comprises wrong hostname.
+TEST_F(FqdnDhcpv4SrvTest, dhcidWrongHostname) {
+    // First, make sure that the lease with the correct hostname is accepted.
+    Lease4Ptr lease = createLease(IOAddress("192.0.2.3"),
+                                  "myhost.example.com.", true, true);
+    ASSERT_NO_THROW(srv_->computeDhcid(lease));
+
+    // Now, use the wrong hostname. It should result in the exception.
+    lease->hostname_ = "myhost...example.com.";
+    EXPECT_THROW(srv_->computeDhcid(lease), isc::dhcp::DhcidComputeError);
+
+    // Also, empty hostname is wrong.
+    lease->hostname_ = "";
+    EXPECT_THROW(srv_->computeDhcid(lease), isc::dhcp::DhcidComputeError);
+}
+
+// Test that the DHCID is computed correctly, when the lease holds
+// correct hostname and non-NULL client id.
+TEST_F(FqdnDhcpv4SrvTest, dhcidComputeFromClientId) {
+    Lease4Ptr lease = createLease(IOAddress("192.0.2.3"),
+                                  "myhost.example.com.",
+                                  true, true);
+    isc::dhcp_ddns::D2Dhcid dhcid;
+    ASSERT_NO_THROW(dhcid = srv_->computeDhcid(lease));
+
+    // Make sure that the computed DHCID is valid.
+    std::string dhcid_ref = "00010132E91AA355CFBB753C0F0497A5A9404"
+        "36965B68B6D438D98E680BF10B09F3BCF";
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that the DHCID is computed correctly, when the lease holds correct
+// hostname and NULL client id.
+TEST_F(FqdnDhcpv4SrvTest, dhcidComputeFromHWAddr) {
+    Lease4Ptr lease = createLease(IOAddress("192.0.2.3"),
+                                  "myhost.example.com.",
+                                  true, true);
+    lease->client_id_.reset();
+
+    isc::dhcp_ddns::D2Dhcid dhcid;
+    ASSERT_NO_THROW(dhcid = srv_->computeDhcid(lease));
+
+    // Make sure that the computed DHCID is valid.
+    std::string dhcid_ref = "0000012247F6DC4423C3E8627434A9D6868609"
+        "D88948F78018B215EDCAA30C0C135035";
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+
 // Test that server confirms to perform the forward and reverse DNS update,
 // when client asks for it.
 TEST_F(FqdnDhcpv4SrvTest, serverUpdateForward) {
@@ -174,13 +275,26 @@ TEST_F(FqdnDhcpv4SrvTest, serverUpdateForwardNoName) {
 
 }
 
+// Test server's response when client requests no DNS update.
+TEST_F(FqdnDhcpv4SrvTest, noUpdate) {
+    Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
+                                        Option4ClientFqdn::FLAG_E |
+                                        Option4ClientFqdn::FLAG_N,
+                                        "myhost.example.com.",
+                                        Option4ClientFqdn::FULL,
+                                        true);
+    testProcessFqdn(query, Option4ClientFqdn::FLAG_E |
+                    Option4ClientFqdn::FLAG_N,
+                    "myhost.example.com.");
+}
+
 // Test that server does not accept delegation of the forward DNS update
 // to a client.
 TEST_F(FqdnDhcpv4SrvTest, clientUpdateNotAllowed) {
     Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
                                         Option4ClientFqdn::FLAG_E,
                                         "myhost.example.com.",
-                                        Option4ClientFqdn::PARTIAL,
+                                        Option4ClientFqdn::FULL,
                                         true);
 
     testProcessFqdn(query, Option4ClientFqdn::FLAG_E |
@@ -189,6 +303,100 @@ TEST_F(FqdnDhcpv4SrvTest, clientUpdateNotAllowed) {
 
 }
 
+// Test that exactly one NameChangeRequest is generated when the new lease
+// has been acquired (old lease is NULL).
+TEST_F(FqdnDhcpv4SrvTest, createNameChangeRequestsNewLease) {
+    Lease4Ptr lease = createLease(IOAddress("192.0.2.3"), "myhost.example.com.",
+                                  true, true);
+    Lease4Ptr old_lease;
+
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease));
+    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            "192.0.2.3", "myhost.example.com.",
+                            "00010132E91AA355CFBB753C0F0497A5A940436965"
+                            "B68B6D438D98E680BF10B09F3BCF",
+                            0, 100);
+}
+
+// Test that no NameChangeRequest is generated when a lease is renewed and
+// the FQDN data hasn't changed.
+TEST_F(FqdnDhcpv4SrvTest, createNameChangeRequestsRenewNoChange) {
+    Lease4Ptr lease = createLease(IOAddress("192.0.2.3"), "myhost.example.com.",
+                                  true, true);
+    Lease4Ptr old_lease = createLease(IOAddress("192.0.2.3"),
+                                      "myhost.example.com.", true, true);
+    old_lease->valid_lft_ += 100;
+
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease));
+    EXPECT_TRUE(srv_->name_change_reqs_.empty());
+}
+
+// Test that no NameChangeRequest is generated when forward and reverse
+// DNS update flags are not set in the lease.
+TEST_F(FqdnDhcpv4SrvTest, createNameChangeRequestsNoUpdate) {
+    Lease4Ptr lease1 = createLease(IOAddress("192.0.2.3"),
+                                   "lease1.example.com.",
+                                   true, true);
+    Lease4Ptr lease2 = createLease(IOAddress("192.0.2.3"),
+                                   "lease2.example.com.",
+                                   false, false);
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(lease2, lease1));
+    EXPECT_EQ(1, srv_->name_change_reqs_.size());
+
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            "192.0.2.3", "lease1.example.com.",
+                            "0001013A5B311F5B9FB10DDF8E53689B874F25D"
+                            "62CC147C2FF237A64C90E5A597C9B7A",
+                            0, 100);
+
+    lease2->hostname_ = "";
+    lease2->fqdn_rev_ = true;
+    lease2->fqdn_fwd_ = true;
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(lease2, lease1));
+    EXPECT_EQ(1, srv_->name_change_reqs_.size());
+
+}
 
+// Test that two NameChangeRequests are generated when the lease is being
+// renewed and the new lease has updated FQDN data.
+TEST_F(FqdnDhcpv4SrvTest, createNameChangeRequestsRenew) {
+    Lease4Ptr lease1 = createLease(IOAddress("192.0.2.3"),
+                                   "lease1.example.com.",
+                                   true, true);
+    Lease4Ptr lease2 = createLease(IOAddress("192.0.2.3"),
+                                   "lease2.example.com.",
+                                   true, true);
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(lease2, lease1));
+    ASSERT_EQ(2, srv_->name_change_reqs_.size());
+
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            "192.0.2.3", "lease1.example.com.",
+                            "0001013A5B311F5B9FB10DDF8E53689B874F25D"
+                            "62CC147C2FF237A64C90E5A597C9B7A",
+                            0, 100);
+
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            "192.0.2.3", "lease2.example.com.",
+                            "000101F906D2BB752E1B2EECC5FF2BF434C0B2D"
+                            "D6D7F7BD873F4F280165DB8C9DBA7CB",
+                            0, 100);
+
+}
+
+// This test verifies that exception is thrown when leases passed to the
+// createNameChangeRequests function do not match, i.e. they comprise
+// different IP addresses, client ids etc.
+TEST_F(FqdnDhcpv4SrvTest, createNameChangeRequestsLeaseMismatch) {
+    Lease4Ptr lease1 = createLease(IOAddress("192.0.2.3"),
+                                   "lease1.example.com.",
+                                   true, true);
+    Lease4Ptr lease2 = createLease(IOAddress("192.0.2.4"),
+                                   "lease2.example.com.",
+                                   true, true);
+    EXPECT_THROW(srv_->createNameChangeRequests(lease2, lease1),
+                 isc::Unexpected);
+}
 
 } // end of anonymous namespace
diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc
index 3c7a18e..4726850 100644
--- a/src/lib/dhcp_ddns/ncr_msg.cc
+++ b/src/lib/dhcp_ddns/ncr_msg.cc
@@ -25,8 +25,23 @@
 namespace isc {
 namespace dhcp_ddns {
 
+
 /********************************* D2Dhcid ************************************/
 
+namespace {
+
+///
+/// @name Constants which define DHCID identifier-type
+//@{
+/// DHCID created from client's HW address.
+const uint8_t DHCID_ID_HWADDR   = 0x0;
+/// DHCID created from client identifier.
+const uint8_t DHCID_ID_CLIENTID = 0x1;
+/// DHCID created from DUID.
+const uint8_t DHCID_ID_DUID     = 0x2;
+
+}
+
 D2Dhcid::D2Dhcid() {
 }
 
@@ -34,6 +49,16 @@ D2Dhcid::D2Dhcid(const std::string& data) {
     fromStr(data);
 }
 
+D2Dhcid::D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
+                 const std::vector<uint8_t>& wire_fqdn) {
+    fromHWAddr(hwaddr, wire_fqdn);
+}
+
+D2Dhcid::D2Dhcid(const std::vector<uint8_t>& clientid_data,
+                 const std::vector<uint8_t>& wire_fqdn) {
+    fromClientId(clientid_data, wire_fqdn);
+}
+
 D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
                  const std::vector<uint8_t>& wire_fqdn) {
     fromDUID(duid, wire_fqdn);
@@ -56,33 +81,62 @@ D2Dhcid::toStr() const {
 }
 
 void
+D2Dhcid::fromClientId(const std::vector<uint8_t>& clientid_data,
+                      const std::vector<uint8_t>& wire_fqdn) {
+    createDigest(DHCID_ID_CLIENTID, clientid_data, wire_fqdn);
+}
+
+void
+D2Dhcid::fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
+                    const std::vector<uint8_t>& wire_fqdn) {
+    if (!hwaddr) {
+        isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+                  "unable to compute DHCID from the HW address, "
+                  "NULL pointer has been specified");
+    } else if (hwaddr->hwaddr_.empty()) {
+        isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+                  "unable to compute DHCID from the HW address, "
+                  "HW address is empty");
+    }
+    std::vector<uint8_t> hwaddr_data;
+    hwaddr_data.push_back(hwaddr->htype_);
+    hwaddr_data.insert(hwaddr_data.end(), hwaddr->hwaddr_.begin(),
+                       hwaddr->hwaddr_.end());
+    createDigest(DHCID_ID_HWADDR, hwaddr_data, wire_fqdn);
+}
+
+
+void
 D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
                   const std::vector<uint8_t>& wire_fqdn) {
-    // DHCID created from DUID starts with two bytes representing
-    // a type of the identifier. The value of 0x0002 indicates that
-    // DHCID has been created from DUID. The 3rd byte is equal to 1
-    // which indicates that the SHA-256 algorithm is used to create
-    // a DHCID digest. This value is called digest-type.
-    static uint8_t dhcid_header[] = { 0x00, 0x02, 0x01 };
 
+    createDigest(DHCID_ID_DUID, duid.getDuid(), wire_fqdn);
+}
+
+void
+D2Dhcid::createDigest(const uint8_t identifier_type,
+                      const std::vector<uint8_t>& identifier_data,
+                      const std::vector<uint8_t>& wire_fqdn) {
     // We get FQDN in the wire format, so we don't know if it is
     // valid. It is caller's responsibility to make sure it is in
     // the valid format. Here we just make sure it is not empty.
     if (wire_fqdn.empty()) {
-        isc_throw(isc::dhcp_ddns::NcrMessageError,
+        isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
                   "empty FQDN used to create DHCID");
     }
 
-    // Get the wire representation of the DUID.
-    std::vector<uint8_t> data = duid.getDuid();
-    // It should be DUID class responsibility to validate the DUID
-    // but let's be on the safe side here and make sure that empty
-    // DUID is not returned.
-    if (data.empty()) {
-        isc_throw(isc::dhcp_ddns::NcrMessageError,
+    // It is a responsibility of the classes which encapsulate client
+    // identifiers, e.g. DUID, to validate the client identifier data.
+    // But let's be on the safe side and at least check that it is not
+    // empty.
+    if (identifier_data.empty()) {
+        isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
                   "empty DUID used to create DHCID");
     }
 
+    // A data buffer will be used to compute the digest.
+    std::vector<uint8_t> data = identifier_data;
+
     // Append FQDN in the wire format.
     data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
 
@@ -100,18 +154,32 @@ D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
         secure = sha.process(static_cast<const Botan::byte*>(&data[0]),
                              data.size());
     } catch (const std::exception& ex) {
-        isc_throw(isc::dhcp_ddns::NcrMessageError,
+        isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
                   "error while generating DHCID from DUID: "
                   << ex.what());
     }
 
-    // The exception unsafe part is finished, so we can finally replace
-    // the contents of bytes_.
-    bytes_.assign(dhcid_header, dhcid_header + sizeof(dhcid_header));
+    // The DHCID RDATA has the following structure:
+    // 
+    //    < identifier-type > < digest-type > < digest >
+    //
+    // where identifier type 
+
+    // Let's allocate the space for the identifier-type (2 bytes) and
+    // digest-type (1 byte). This is 3 bytes all together.
+    bytes_.resize(3);
+    // Leave first byte 0 and set the second byte. Those two bytes
+    // form the identifier-type.
+    bytes_[1] = identifier_type;
+    // Third byte is always equal to 1, which specifies SHA-256 digest type.
+    bytes_[2] = 1;
+    // Now let's append the digest.
     bytes_.insert(bytes_.end(), secure.begin(), secure.end());
 }
 
 
+
+
 /**************************** NameChangeRequest ******************************/
 
 NameChangeRequest::NameChangeRequest()
diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h
index ceb8715..a35d212 100644
--- a/src/lib/dhcp_ddns/ncr_msg.h
+++ b/src/lib/dhcp_ddns/ncr_msg.h
@@ -22,6 +22,7 @@
 
 #include <cc/data.h>
 #include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
 #include <util/encode/hex.h>
@@ -40,6 +41,15 @@ public:
         isc::Exception(file, line, what) { };
 };
 
+/// @brief Exception thrown when there is an error occured during computation
+/// of the DHCID.
+class DhcidRdataComputeError : public isc::Exception {
+public:
+    DhcidRdataComputeError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
 /// @brief Defines the types of DNS updates that can be requested.
 enum NameChangeType {
   CHG_ADD,
@@ -79,6 +89,22 @@ public:
     D2Dhcid(const std::string& data);
 
     /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+    /// HW address.
+    ///
+    /// @param A pointer to the object encapsulating HW address.
+    /// @param A on-wire canonical representation of the FQDN.
+    D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
+            const std::vector<uint8_t>& wire_fqdn);
+
+    /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+    /// client identifier carried in the Client Identifier option.
+    ///
+    /// @param clientid_data Holds the raw bytes representing client identifier.
+    /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+    D2Dhcid(const std::vector<uint8_t>& clientid_data,
+            const std::vector<uint8_t>& wire_fqdn);
+
+    /// @brief Constructor, creates an instance of the @c D2Dhcid from the
     /// @c isc::dhcp::DUID.
     ///
     /// @param duid An object representing DUID.
@@ -101,6 +127,13 @@ public:
     /// or there is an odd number of digits.
     void fromStr(const std::string& data);
 
+    /// @brief Sets the DHCID value based on the Client Identifier.
+    ///
+    /// @param clientid_data Holds the raw bytes representing client identifier.
+    /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+    void fromClientId(const std::vector<uint8_t>& clientid_data,
+                      const std::vector<uint8_t>& wire_fqdn);
+
     /// @brief Sets the DHCID value based on the DUID and FQDN.
     ///
     /// This function requires that the FQDN conforms to the section 3.5
@@ -112,6 +145,13 @@ public:
     void fromDUID(const isc::dhcp::DUID& duid,
                   const std::vector<uint8_t>& wire_fqdn);
 
+    /// @brief Sets the DHCID value based on the HW address and FQDN.
+    ///
+    /// @param A pointer to the object encapsulating HW address.
+    /// @param A on-wire canonical representation of the FQDN.
+    void fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
+                    const std::vector<uint8_t>& wire_fqdn);
+
     /// @brief Returns a reference to the DHCID byte vector.
     ///
     /// @return a reference to the vector.
@@ -135,6 +175,22 @@ public:
     }
 
 private:
+
+    /// @brief Creates the DHCID using specified indetifier.
+    ///
+    /// This function creates the DHCID RDATA as specified in RFC4701,
+    /// section 3.5.
+    ///
+    /// @param identifier_type is a less significant byte of the identifier-type
+    /// defined in RFC4701.
+    /// @param identifier_data A buffer holding client identifier raw data -
+    /// e.g. DUID, data carried in the Client Identifier option or client's
+    /// HW address.
+    /// @param A on-wire canonical representation of the FQDN.
+    void createDigest(const uint8_t identifier_type,
+                      const std::vector<uint8_t>& identifier_data,
+                      const std::vector<uint8_t>& wire_fqdn);
+
     /// @brief Storage for the DHCID value in unsigned bytes.
     std::vector<uint8_t> bytes_;
 };
diff --git a/src/lib/dhcp_ddns/tests/ncr_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
index df1a50a..33ec288 100644
--- a/src/lib/dhcp_ddns/tests/ncr_unittests.cc
+++ b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
@@ -14,6 +14,7 @@
 
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
 #include <util/time_utilities.h>
 
 #include <gtest/gtest.h>
@@ -236,6 +237,129 @@ TEST(NameChangeRequestTest, constructionTests) {
 
 }
 
+/// @brief Verifies the fundamentals of converting from and to JSON.
+/// It verifies that:
+/// 1. A NameChangeRequest can be created from a valid JSON string.
+/// 2. A valid JSON string can be created from a NameChangeRequest
+TEST(NameChangeRequestTest, basicJsonTest) {
+    // Define valid JSON rendition of a request.
+    std::string msg_str = "{"
+                            "\"change_type\":1,"
+                            "\"forward_change\":true,"
+                            "\"reverse_change\":false,"
+                            "\"fqdn\":\"walah.walah.com\","
+                            "\"ip_address\":\"192.168.2.1\","
+                            "\"dhcid\":\"010203040A7F8E3D\","
+                            "\"lease_expires_on\":\"20130121132405\","
+                            "\"lease_length\":1300"
+                          "}";
+
+    // Verify that a NameChangeRequests can be instantiated from the
+    // a valid JSON rendition.
+    NameChangeRequestPtr ncr;
+    ASSERT_NO_THROW(ncr  = NameChangeRequest::fromJSON(msg_str));
+    ASSERT_TRUE(ncr);
+
+    // Verify that the JSON string created by the new request equals the
+    // original input string.
+    std::string json_str = ncr->toJSON();
+    EXPECT_EQ(msg_str, json_str);
+}
+
+/// @brief Tests a variety of invalid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// content error. The list of messages is defined by the global array,
+/// invalid_messages. Currently that list contains the following invalid
+/// conditions:
+///  1. Invalid change type
+///  2. Invalid forward change
+///  3. Invalid reverse change
+///  4. Forward and reverse change both false
+///  5. Invalid forward change
+///  6. Blank FQDN
+///  7. Bad IP address
+///  8. Blank DHCID
+///  9. Odd number of digits in DHCID
+/// 10. Text in DHCID
+/// 11. Invalid lease expiration string
+/// 12. Non-integer for lease length.
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, invalidMsgChecks) {
+    // Iterate over the list of JSON strings, attempting to create a
+    // NameChangeRequest. The attempt should throw a NcrMessageError.
+    int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
+    for (int i = 0; i < num_msgs; i++) {
+        EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
+                     NcrMessageError) << "Invalid message not caught idx: "
+                     << i << std::endl << " text:[" << invalid_msgs[i] << "]"
+                     << std::endl;
+    }
+}
+
+/// @brief Tests a variety of valid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// valid request rendition. The list of messages is defined by the global
+/// array, valid_messages. Currently that list contains the following valid
+/// messages:
+///  1. Valid, IPv4 Add
+///  2. Valid, IPv4 Remove
+///  3. Valid, IPv6 Add
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, validMsgChecks) {
+    // Iterate over the list of JSON strings, attempting to create a
+    // NameChangeRequest. The attempt should succeed.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+    for (int i = 0; i < num_msgs; i++) {
+        EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
+                        << "Valid message failed,  message idx: " << i
+                        << std::endl << " text:[" << valid_msgs[i] << "]"
+                        << std::endl;
+    }
+}
+
+/// @brief Tests converting to and from JSON via isc::util buffer classes.
+/// This test verifies that:
+/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
+/// 2. A InputBuffer containing a valid JSON request rendition can be used
+/// to create a NameChangeRequest.
+TEST(NameChangeRequestTest, toFromBufferTest) {
+    // Define a string containing a valid JSON NameChangeRequest rendition.
+    std::string msg_str = "{"
+                            "\"change_type\":1,"
+                            "\"forward_change\":true,"
+                            "\"reverse_change\":false,"
+                            "\"fqdn\":\"walah.walah.com\","
+                            "\"ip_address\":\"192.168.2.1\","
+                            "\"dhcid\":\"010203040A7F8E3D\","
+                            "\"lease_expires_on\":\"20130121132405\","
+                            "\"lease_length\":1300"
+                          "}";
+
+    // Create a request from JSON directly.
+    NameChangeRequestPtr ncr;
+    ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+
+    // Verify that we output the request as JSON text to a buffer
+    // without error.
+    isc::util::OutputBuffer output_buffer(1024);
+    ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
+
+    // Make an InputBuffer from the OutputBuffer.
+    isc::util::InputBuffer input_buffer(output_buffer.getData(),
+                                        output_buffer.getLength());
+
+    // Verify that we can create a new request from the InputBuffer.
+    NameChangeRequestPtr ncr2;
+    ASSERT_NO_THROW(ncr2 =
+                    NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
+
+    // Convert the new request to JSON directly.
+    std::string final_str = ncr2->toJSON();
+
+    // Verify that the final string matches the original.
+    ASSERT_EQ(final_str, msg_str);
+}
+
 /// @brief Tests the basic workings of D2Dhcid to and from string conversions.
 /// It verifies that:
 /// 1. DHCID input strings must contain an even number of characters
@@ -290,6 +414,26 @@ TEST(NameChangeRequestTest, dhcidTest) {
 
 }
 
+/// @brief Test fixture class for testing DHCID creation.
+class DhcidTest : public ::testing::Test {
+public:
+    /// @brief Constructor
+    DhcidTest() {
+        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.
+        };
+        wire_fqdn_.assign(fqdn_data, fqdn_data + sizeof(fqdn_data));
+    }
+
+    /// @brief Destructor
+    virtual ~DhcidTest() {
+    }
+
+    std::vector<uint8_t> wire_fqdn_;
+};
+
 /// Tests that DHCID is correctly created from a DUID and FQDN. The final format
 /// of the DHCID is as follows:
 /// <identifier-type> <digest-type-code> <digest>
@@ -298,25 +442,15 @@ TEST(NameChangeRequestTest, dhcidTest) {
 /// - digest-type-code (1 octet) indicates SHA-256 hashing and is equal 0x1.
 /// - digest = SHA-256(<DUID> <FQDN>)
 /// Note: FQDN is given in the on-wire canonical format.
-TEST(NameChangeRequestTest, dhcidFromDUID) {
+TEST_F(DhcidTest, fromDUID) {
     D2Dhcid dhcid;
 
     // Create DUID.
     uint8_t duid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
     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));
+    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.
@@ -328,25 +462,15 @@ TEST(NameChangeRequestTest, dhcidFromDUID) {
 }
 
 // Test that DHCID is correctly created when the DUID has minimal length (1).
-TEST(NameChangeRequestTest, dhcidFromMinDUID) {
+TEST_F(DhcidTest, fromMinDUID) {
     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));
+    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.
@@ -358,25 +482,15 @@ TEST(NameChangeRequestTest, dhcidFromMinDUID) {
 }
 
 // Test that DHCID is correctly created when the DUID has maximal length (128).
-TEST(NameChangeRequestTest, dhcidFromMaxDUID) {
+TEST_F(DhcidTest, fromMaxDUID) {
     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));
+    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.
@@ -387,130 +501,70 @@ TEST(NameChangeRequestTest, dhcidFromMaxDUID) {
     EXPECT_EQ(dhcid_ref, dhcid.toStr());
 }
 
+// This test verifies that DHCID is properly computed from a buffer holding
+// client identifier data.
+TEST_F(DhcidTest, fromClientId) {
+    D2Dhcid dhcid;
 
-/// @brief Verifies the fundamentals of converting from and to JSON.
-/// It verifies that:
-/// 1. A NameChangeRequest can be created from a valid JSON string.
-/// 2. A valid JSON string can be created from a NameChangeRequest
-TEST(NameChangeRequestTest, basicJsonTest) {
-    // Define valid JSON rendition of a request.
-    std::string msg_str = "{"
-                            "\"change_type\":1,"
-                            "\"forward_change\":true,"
-                            "\"reverse_change\":false,"
-                            "\"fqdn\":\"walah.walah.com\","
-                            "\"ip_address\":\"192.168.2.1\","
-                            "\"dhcid\":\"010203040A7F8E3D\","
-                            "\"lease_expires_on\":\"20130121132405\","
-                            "\"lease_length\":1300"
-                          "}";
+    // Create a buffer holding client id..
+    uint8_t clientid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+    std::vector<uint8_t> clientid(clientid_data,
+                                  clientid_data + sizeof(clientid_data));
 
-    // Verify that a NameChangeRequests can be instantiated from the
-    // a valid JSON rendition.
-    NameChangeRequestPtr ncr;
-    ASSERT_NO_THROW(ncr  = NameChangeRequest::fromJSON(msg_str));
-    ASSERT_TRUE(ncr);
+    // Create DHCID.
+    ASSERT_NO_THROW(dhcid.fromClientId(clientid, wire_fqdn_));
 
-    // Verify that the JSON string created by the new request equals the
-    // original input string.
-    std::string json_str = ncr->toJSON();
-    EXPECT_EQ(msg_str, json_str);
-}
+    // The reference DHCID (represented as string of hexadecimal digits)
+    // has been calculated using one of the online calculators.
+    std::string dhcid_ref = "0001012191B7B21AF97E0E656DF887C5E2D"
+        "EF30E7758A207EDF4CCB2DE8CA37066021C";
+
+    // Make sure that the DHCID is valid.
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
+
+    // Make sure that the empty FQDN is not accepted.
+    std::vector<uint8_t> empty_wire_fqdn;
+    EXPECT_THROW(dhcid.fromClientId(clientid, empty_wire_fqdn),
+                 isc::dhcp_ddns::DhcidRdataComputeError);
+
+    // Make sure that the empty client identifier is not accepted.
+    clientid.clear();
+    EXPECT_THROW(dhcid.fromClientId(clientid, wire_fqdn_),
+                 isc::dhcp_ddns::DhcidRdataComputeError);
 
-/// @brief Tests a variety of invalid JSON message strings.
-/// This test iterates over a list of JSON messages, each containing a single
-/// content error. The list of messages is defined by the global array,
-/// invalid_messages. Currently that list contains the following invalid
-/// conditions:
-///  1. Invalid change type
-///  2. Invalid forward change
-///  3. Invalid reverse change
-///  4. Forward and reverse change both false
-///  5. Invalid forward change
-///  6. Blank FQDN
-///  7. Bad IP address
-///  8. Blank DHCID
-///  9. Odd number of digits in DHCID
-/// 10. Text in DHCID
-/// 11. Invalid lease expiration string
-/// 12. Non-integer for lease length.
-/// If more permutations arise they can easily be added to the list.
-TEST(NameChangeRequestTest, invalidMsgChecks) {
-    // Iterate over the list of JSON strings, attempting to create a
-    // NameChangeRequest. The attempt should throw a NcrMessageError.
-    int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
-    for (int i = 0; i < num_msgs; i++) {
-        EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
-                     NcrMessageError) << "Invalid message not caught idx: "
-                     << i << std::endl << " text:[" << invalid_msgs[i] << "]"
-                     << std::endl;
-    }
-}
 
-/// @brief Tests a variety of valid JSON message strings.
-/// This test iterates over a list of JSON messages, each containing a single
-/// valid request rendition. The list of messages is defined by the global
-/// array, valid_messages. Currently that list contains the following valid
-/// messages:
-///  1. Valid, IPv4 Add
-///  2. Valid, IPv4 Remove
-///  3. Valid, IPv6 Add
-/// If more permutations arise they can easily be added to the list.
-TEST(NameChangeRequestTest, validMsgChecks) {
-    // Iterate over the list of JSON strings, attempting to create a
-    // NameChangeRequest. The attempt should succeed.
-    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
-    for (int i = 0; i < num_msgs; i++) {
-        EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
-                        << "Valid message failed,  message idx: " << i
-                        << std::endl << " text:[" << valid_msgs[i] << "]"
-                        << std::endl;
-    }
 }
 
-/// @brief Tests converting to and from JSON via isc::util buffer classes.
-/// This test verifies that:
-/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
-/// 2. A InputBuffer containing a valid JSON request rendition can be used
-/// to create a NameChangeRequest.
-TEST(NameChangeRequestTest, toFromBufferTest) {
-    // Define a string containing a valid JSON NameChangeRequest rendition.
-    std::string msg_str = "{"
-                            "\"change_type\":1,"
-                            "\"forward_change\":true,"
-                            "\"reverse_change\":false,"
-                            "\"fqdn\":\"walah.walah.com\","
-                            "\"ip_address\":\"192.168.2.1\","
-                            "\"dhcid\":\"010203040A7F8E3D\","
-                            "\"lease_expires_on\":\"20130121132405\","
-                            "\"lease_length\":1300"
-                          "}";
+// This test verifies that DHCID is properly computed from a HW address.
+TEST_F(DhcidTest, fromHWAddr) {
+    D2Dhcid dhcid;
 
-    // Create a request from JSON directly.
-    NameChangeRequestPtr ncr;
-    ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+    // Create a buffer holding client id..
+    uint8_t hwaddr_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+    HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
+                                HTYPE_ETHER));
 
-    // Verify that we output the request as JSON text to a buffer
-    // without error.
-    isc::util::OutputBuffer output_buffer(1024);
-    ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
+    // Create DHCID.
+    ASSERT_NO_THROW(dhcid.fromHWAddr(hwaddr, wire_fqdn_));
 
-    // Make an InputBuffer from the OutputBuffer.
-    isc::util::InputBuffer input_buffer(output_buffer.getData(),
-                                        output_buffer.getLength());
+    // The reference DHCID (represented as string of hexadecimal digits)
+    // has been calculated using one of the online calculators.
+    std::string dhcid_ref = "0000012247F6DC4423C3E8627434A9D686860"
+        "9D88948F78018B215EDCAA30C0C135035";
 
-    // Verify that we can create a new request from the InputBuffer.
-    NameChangeRequestPtr ncr2;
-    ASSERT_NO_THROW(ncr2 =
-                    NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
+    // Make sure that the DHCID is valid.
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
 
-    // Convert the new request to JSON directly.
-    std::string final_str = ncr2->toJSON();
+    // Make sure that the empty FQDN is not accepted.
+    std::vector<uint8_t> empty_wire_fqdn;
+    EXPECT_THROW(dhcid.fromHWAddr(hwaddr, empty_wire_fqdn),
+                 isc::dhcp_ddns::DhcidRdataComputeError);
 
-    // Verify that the final string matches the original.
-    ASSERT_EQ(final_str, msg_str);
+    // Make sure that the NULL HW address is not accepted.
+    hwaddr.reset();
+    EXPECT_THROW(dhcid.fromHWAddr(hwaddr, wire_fqdn_),
+                 isc::dhcp_ddns::DhcidRdataComputeError);
 }
 
-
 } // end of anonymous namespace
 
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
index c4810fd..f226afe 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -156,11 +156,10 @@ Lease4::toText() const {
     return (stream.str());
 }
 
-
 bool
-Lease4::operator==(const Lease4& other) const {
-    if ( (client_id_ && !other.client_id_) ||
-         (!client_id_ && other.client_id_) ) {
+Lease4::matches(const Lease4& other) const {
+    if ((client_id_ && !other.client_id_) ||
+        (!client_id_ && other.client_id_)) {
         // One lease has client-id, but the other doesn't
         return false;
     }
@@ -171,43 +170,50 @@ Lease4::operator==(const Lease4& other) const {
         return false;
     }
 
-    return (
-        addr_ == other.addr_ &&
-        ext_ == other.ext_ &&
-        hwaddr_ == other.hwaddr_ &&
-        t1_ == other.t1_ &&
-        t2_ == other.t2_ &&
-        valid_lft_ == other.valid_lft_ &&
-        cltt_ == other.cltt_ &&
-        subnet_id_ == other.subnet_id_ &&
-        fixed_ == other.fixed_ &&
-        hostname_ == other.hostname_ &&
-        fqdn_fwd_ == other.fqdn_fwd_ &&
-        fqdn_rev_ == other.fqdn_rev_ &&
-        comments_ == other.comments_
-    );
+    return (addr_ == other.addr_ &&
+            ext_ == other.ext_ &&
+            hwaddr_ == other.hwaddr_);
+
+}
+
+bool
+Lease4::operator==(const Lease4& other) const {
+    return (matches(other) &&
+            subnet_id_ == other.subnet_id_ &&
+            t1_ == other.t1_ &&
+            t2_ == other.t2_ &&
+            valid_lft_ == other.valid_lft_ &&
+            cltt_ == other.cltt_ &&
+            fixed_ == other.fixed_ &&
+            hostname_ == other.hostname_ &&
+            fqdn_fwd_ == other.fqdn_fwd_ &&
+            fqdn_rev_ == other.fqdn_rev_ &&
+            comments_ == other.comments_);
+}
+
+bool
+Lease6::matches(const Lease6& other) const {
+    return (addr_ == other.addr_ &&
+            type_ == other.type_ &&
+            prefixlen_ == other.prefixlen_ &&
+            iaid_ == other.iaid_ &&
+            *duid_ == *other.duid_);
 }
 
 bool
 Lease6::operator==(const Lease6& other) const {
-    return (
-        addr_ == other.addr_ &&
-        type_ == other.type_ &&
-        prefixlen_ == other.prefixlen_ &&
-        iaid_ == other.iaid_ &&
-        *duid_ == *other.duid_ &&
-        preferred_lft_ == other.preferred_lft_ &&
-        valid_lft_ == other.valid_lft_ &&
-        t1_ == other.t1_ &&
-        t2_ == other.t2_ &&
-        cltt_ == other.cltt_ &&
-        subnet_id_ == other.subnet_id_ &&
-        fixed_ == other.fixed_ &&
-        hostname_ == other.hostname_ &&
-        fqdn_fwd_ == other.fqdn_fwd_ &&
-        fqdn_rev_ == other.fqdn_rev_ &&
-        comments_ == other.comments_
-    );
+    return (matches(other) &&
+            preferred_lft_ == other.preferred_lft_ &&
+            valid_lft_ == other.valid_lft_ &&
+            t1_ == other.t1_ &&
+            t2_ == other.t2_ &&
+            cltt_ == other.cltt_ &&
+            subnet_id_ == other.subnet_id_ &&
+            fixed_ == other.fixed_ &&
+            hostname_ == other.hostname_ &&
+            fqdn_fwd_ == other.fqdn_fwd_ &&
+            fqdn_rev_ == other.fqdn_rev_ &&
+            comments_ == other.comments_);
 }
 
 } // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index 3e0e092..d85651a 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -264,6 +264,20 @@ struct Lease4 : public Lease {
     /// @param other the @c Lease4 object to be copied.
     Lease4(const Lease4& other);
 
+    /// @brief Check if two objects encapsulate the lease for the same
+    /// client.
+    ///
+    /// Checks if two @c Lease4 objects have the same address, client id,
+    /// HW address and ext_ value.  If these parameters match it is an
+    /// indication that both objects describe the lease for the same
+    /// client but apparently one is a result of renewal of the other. The
+    /// special case of the matching lease is the one that is equal to another.
+    ///
+    /// @param other A lease to compare with.
+    ///
+    /// @return true if the selected parameters of the two leases match.
+    bool matches(const Lease4& other) const;
+
     /// @brief Assignment operator.
     ///
     /// @param other the @c Lease4 object to be assigned.
@@ -352,6 +366,20 @@ struct Lease6 : public Lease {
         type_(LEASE_IA_NA) {
     }
 
+    /// @brief Checks if two lease objects encapsulate the lease for the same
+    /// client.
+    ///
+    /// This function compares address, type, prefix length, IAID and DUID
+    /// parameters between two @c Lease6 objects. If these parameters match
+    /// it is an indication that both objects describe the lease for the same
+    /// client but apparently one is a result of renewal of the other. The
+    /// special case of the matching lease is the one that is equal to another.
+    ///
+    /// @param other A lease to compare to.
+    ///
+    /// @return true if selected parameters of the two leases match.
+    bool matches(const Lease6& other) const;
+
     /// @brief Compare two leases for equality
     ///
     /// @param other lease6 object with which to compare
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 883874a..bdbc168 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -221,6 +221,17 @@ public:
 
 namespace {
 
+/// Hardware address used by different tests.
+const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+/// Client id used by different tests.
+const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+/// Valid lifetime value used by different tests.
+const uint32_t VALID_LIFETIME = 500;
+/// Subnet ID used by different tests.
+const uint32_t SUBNET_ID = 42;
+/// IAID value used by different tests.
+const uint32_t IAID = 7;
+
 /// @brief getParameter test
 ///
 /// This test checks if the LeaseMgr can be instantiated and that it
@@ -248,20 +259,14 @@ TEST(LeaseMgr, getParameter) {
 TEST(Lease4, constructor) {
 
     // Random values for the tests
-    const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
     std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
 
-    const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
     std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
     ClientId clientid(clientid_vec);
 
     // ...and a time
     const time_t current_time = time(NULL);
 
-    // Other random constants.
-    const uint32_t SUBNET_ID = 42;
-    const uint32_t VALID_LIFETIME = 500;
-
     // We want to check that various addresses work, so let's iterate over
     // these.
     const uint32_t ADDRESS[] = {
@@ -296,20 +301,14 @@ TEST(Lease4, constructor) {
 TEST(Lease4, copyConstructor) {
 
     // Random values for the tests
-    const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
     std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
 
-    const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
     std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
     ClientId clientid(clientid_vec);
 
     // ...and a time
     const time_t current_time = time(NULL);
 
-    // Other random constants.
-    const uint32_t SUBNET_ID = 42;
-    const uint32_t VALID_LIFETIME = 500;
-
     // Create the lease
     Lease4 lease(0xffffffff, HWADDR, sizeof(HWADDR),
                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
@@ -330,20 +329,14 @@ TEST(Lease4, copyConstructor) {
 TEST(Lease4, operatorAssign) {
 
     // Random values for the tests
-    const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
     std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
 
-    const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
     std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
     ClientId clientid(clientid_vec);
 
     // ...and a time
     const time_t current_time = time(NULL);
 
-    // Other random constants.
-    const uint32_t SUBNET_ID = 42;
-    const uint32_t VALID_LIFETIME = 500;
-
     // Create the lease
     Lease4 lease(0xffffffff, HWADDR, sizeof(HWADDR),
                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
@@ -359,6 +352,52 @@ TEST(Lease4, operatorAssign) {
     EXPECT_FALSE(lease.client_id_ == copied_lease.client_id_);
 }
 
+// This test verifies that the matches() returns true if two leases differ
+// by values other than address, HW address, Client ID and ext_.
+TEST(Lease4, matches) {
+    // Create two leases which share the same address, HW address, client id
+    // and ext_ value.
+    const time_t current_time = time(NULL);
+    Lease4 lease1(IOAddress("192.0.2.3"), HWADDR, sizeof(HWADDR), CLIENTID,
+                  sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
+                  SUBNET_ID);
+    lease1.hostname_ = "lease1.example.com.";
+    lease1.fqdn_fwd_ = true;
+    lease1.fqdn_rev_ = true;
+    Lease4 lease2(IOAddress("192.0.2.3"), HWADDR, sizeof(HWADDR), CLIENTID,
+                  sizeof(CLIENTID), VALID_LIFETIME + 10, current_time - 10,
+                  100, 200, SUBNET_ID);
+    lease2.hostname_ = "lease2.example.com.";
+    lease2.fqdn_fwd_ = false;
+    lease2.fqdn_rev_ = true;
+
+    // Leases should match.
+    EXPECT_TRUE(lease1.matches(lease2));
+    EXPECT_TRUE(lease2.matches(lease1));
+
+    // Change address, leases should not match anymore.
+    lease1.addr_ = IOAddress("192.0.2.4");
+    EXPECT_FALSE(lease1.matches(lease2));
+    lease1.addr_ = lease2.addr_;
+
+    // Change HW address, leases should not match.
+    lease1.hwaddr_[1] += 1;
+    EXPECT_FALSE(lease1.matches(lease2));
+    lease1.hwaddr_ = lease2.hwaddr_;
+
+    // Chanage client id, leases should not match.
+    std::vector<uint8_t> client_id = lease1.client_id_->getClientId();
+    client_id[1] += 1;
+    lease1.client_id_.reset(new ClientId(client_id));
+    EXPECT_FALSE(lease1.matches(lease2));
+    lease1.client_id_ = lease2.client_id_;
+
+    // Change ext_, leases should not match.
+    lease1.ext_ += 1;
+    EXPECT_FALSE(lease1.matches(lease2));
+    lease1.ext_ = lease2.ext_;
+}
+
 /// @brief Lease4 Equality Test
 ///
 /// Checks that the operator==() correctly compares two leases for equality.
@@ -368,14 +407,11 @@ TEST(Lease4, operatorEquals) {
 
     // Random values for the tests
     const uint32_t ADDRESS = 0x01020304;
-    const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
     std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
     const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
     std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
     ClientId clientid(clientid_vec);
     const time_t current_time = time(NULL);
-    const uint32_t SUBNET_ID = 42;
-    const uint32_t VALID_LIFETIME = 500;
 
     // Check when the leases are equal.
     Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
@@ -507,18 +543,17 @@ TEST(Lease6, Lease6Constructor) {
     // Other values
     uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
     DuidPtr duid(new DUID(llt, sizeof(llt)));
-    uint32_t iaid = 7;      // Just a number
     SubnetID subnet_id = 8; // Just another number
 
     for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
         IOAddress addr(ADDRESS[i]);
         Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr,
-                               duid, iaid, 100, 200, 50, 80,
+                               duid, IAID, 100, 200, 50, 80,
                                subnet_id));
 
         EXPECT_TRUE(lease->addr_ == addr);
         EXPECT_TRUE(*lease->duid_ == *duid);
-        EXPECT_TRUE(lease->iaid_ == iaid);
+        EXPECT_TRUE(lease->iaid_ == IAID);
         EXPECT_TRUE(lease->subnet_id_ == subnet_id);
         EXPECT_TRUE(lease->type_ == Lease6::LEASE_IA_NA);
         EXPECT_TRUE(lease->preferred_lft_ == 100);
@@ -531,16 +566,70 @@ TEST(Lease6, Lease6Constructor) {
     IOAddress addr(ADDRESS[0]);
     Lease6Ptr lease2;
     EXPECT_THROW(lease2.reset(new Lease6(Lease6::LEASE_IA_NA, addr,
-                                         DuidPtr(), iaid, 100, 200, 50, 80,
+                                         DuidPtr(), IAID, 100, 200, 50, 80,
                                          subnet_id)), InvalidOperation);
 }
 
+// This test verifies that the matches() function returns true if two leases
+// differ by values other than address, type, prefix length, IAID and DUID.
+TEST(Lease6, matches) {
+
+    // Create two matching leases.
+    uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+    DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+    Lease6 lease1(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1"), duid,
+                                                 IAID, 100, 200, 50, 80,
+                                                 SUBNET_ID);
+    lease1.hostname_ = "lease1.example.com.";
+    lease1.fqdn_fwd_ = true;
+    lease1.fqdn_rev_ = true;
+    Lease6 lease2(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1"), duid,
+                                                 IAID, 200, 300, 90, 70,
+                                                 SUBNET_ID);
+    lease2.hostname_ = "lease1.example.com.";
+    lease2.fqdn_fwd_ = false;
+    lease2.fqdn_rev_ = true;
+
+    EXPECT_TRUE(lease1.matches(lease2));
+
+    // Modify each value used to match both leases, and make sure that
+    // leases don't match.
+
+    // Modify address.
+    lease1.addr_ = IOAddress("2001:db8:1::2");
+    EXPECT_FALSE(lease1.matches(lease2));
+    lease1.addr_ = lease2.addr_;
+
+    // Modify lease type.
+    lease1.type_ = Lease6::LEASE_IA_TA;
+    EXPECT_FALSE(lease1.matches(lease2));
+    lease1.type_ = lease2.type_;
+
+    // Modify prefix length.
+    lease1.prefixlen_ += 1;
+    EXPECT_FALSE(lease1.matches(lease2));
+    lease1.prefixlen_ = lease2.prefixlen_;
+
+    // Modify IAID.
+    lease1.iaid_ += 1;
+    EXPECT_FALSE(lease1.matches(lease2));
+    lease1.iaid_ = lease2.iaid_;
+
+    // Modify DUID.
+    llt[1] += 1;
+    duid.reset(new DUID(llt, sizeof(llt)));
+    lease1.duid_ = duid;
+    EXPECT_FALSE(lease1.matches(lease2));
+    lease1.duid_ = lease2.duid_;
+}
+
 /// @brief Lease6 Equality Test
 ///
 /// Checks that the operator==() correctly compares two leases for equality.
 /// As operator!=() is also defined for this class, every check on operator==()
 /// is followed by the reverse check on operator!=().
-TEST(Lease6, OperatorEquals) {
+TEST(Lease6, operatorEquals) {
 
     // check a variety of addresses with different bits set.
     const IOAddress addr("2001:db8:1::456");



More information about the bind10-changes mailing list