Parcourir la source

[master] Merge branch 'trac3007'. Adds DHCP-DDNS
request message class, NameChangeRequest.

Thomas Markwalder il y a 11 ans
Parent
commit
f33bdd59c6

+ 2 - 0
src/bin/d2/Makefile.am

@@ -56,6 +56,7 @@ b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
 b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
 b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
+b10_dhcp_ddns_SOURCES += ncr_msg.cc ncr_msg.h
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
@@ -67,6 +68,7 @@ b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la 
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 
 b10_dhcp_ddnsdir = $(pkgdatadir)
 b10_dhcp_ddns_DATA = dhcp-ddns.spec

+ 485 - 0
src/bin/d2/ncr_msg.cc

@@ -0,0 +1,485 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/ncr_msg.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
+#include <sstream>
+
+namespace isc {
+namespace d2 {
+
+using namespace boost::posix_time;
+
+/********************************* D2Dhcid ************************************/
+
+D2Dhcid::D2Dhcid() {
+}
+
+D2Dhcid::D2Dhcid(const std::string& data) {
+    fromStr(data);
+}
+
+void
+D2Dhcid::fromStr(const std::string& data) {
+    bytes_.clear();
+    try {
+        isc::util::encode::decodeHex(data, bytes_);
+    } catch (const isc::Exception& ex) {
+        isc_throw(NcrMessageError, "Invalid data in Dhcid:" << ex.what());
+    }
+}
+
+std::string
+D2Dhcid::toStr() const {
+    return (isc::util::encode::encodeHex(bytes_));
+
+
+}
+
+
+/**************************** NameChangeRequest ******************************/
+
+NameChangeRequest::NameChangeRequest()
+    : change_type_(CHG_ADD), forward_change_(false),
+    reverse_change_(false), fqdn_(""), ip_address_(""),
+    dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
+}
+
+NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
+            const bool forward_change, const bool reverse_change,
+            const std::string& fqdn, const std::string& ip_address,
+            const D2Dhcid& dhcid,
+            const boost::posix_time::ptime& lease_expires_on,
+            const uint32_t lease_length)
+    : change_type_(change_type), forward_change_(forward_change),
+    reverse_change_(reverse_change), fqdn_(fqdn), ip_address_(ip_address),
+    dhcid_(dhcid), lease_expires_on_(new ptime(lease_expires_on)),
+    lease_length_(lease_length), status_(ST_NEW) {
+
+    // Validate the contents. This will throw a NcrMessageError if anything
+    // is invalid.
+    validateContent();
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromFormat(const NameChangeFormat format,
+                              isc::util::InputBuffer& buffer) {
+    // Based on the format requested, pull the marshalled request from
+    // InputBuffer and pass it into the appropriate format-specific factory.
+    NameChangeRequestPtr ncr;
+    switch (format) {
+    case FMT_JSON: {
+        try {
+            // Get the length of the JSON text.
+            size_t len = buffer.readUint16();
+
+            // Read the text from the buffer into a vector.
+            std::vector<uint8_t> vec;
+            buffer.readVector(vec, len);
+
+            // Turn the vector into a string.
+            std::string string_data(vec.begin(), vec.end());
+
+            // Pass the string of JSON text into JSON factory to create the
+            // NameChangeRequest instance.  Note the factory may throw
+            // NcrMessageError.
+            ncr = NameChangeRequest::fromJSON(string_data);
+        } catch (isc::util::InvalidBufferPosition& ex) {
+            // Read error accessing data in InputBuffer.
+            isc_throw(NcrMessageError, "fromFormat: buffer read error: "
+                      << ex.what());
+        }
+
+        break;
+        }
+    default:
+        // Programmatic error, shouldn't happen.
+        isc_throw(NcrMessageError, "fromFormat - invalid format");
+        break;
+    }
+
+    return (ncr);
+}
+
+void
+NameChangeRequest::toFormat(const NameChangeFormat format,
+                            isc::util::OutputBuffer& buffer) const {
+    // Based on the format requested, invoke the appropriate format handler
+    // which will marshal this request's contents into the OutputBuffer.
+    switch (format) {
+    case FMT_JSON: {
+        // Invoke toJSON to create a JSON text of this request's contents.
+        std::string json = toJSON();
+        uint16_t length = json.size();
+
+        // Write the length of the JSON text to the OutputBuffer first, then
+        // write the JSON text itself.
+        buffer.writeUint16(length);
+        buffer.writeData(json.c_str(), length);
+        break;
+        }
+    default:
+        // Programmatic error, shouldn't happen.
+        isc_throw(NcrMessageError, "toFormat - invalid format");
+        break;
+    }
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromJSON(const std::string& json) {
+    // This method leverages the existing JSON parsing provided by isc::data
+    // library.  Should this prove to be a performance issue, it may be that
+    // lighter weight solution would be appropriate.
+
+    // Turn the string of JSON text into an Element set.
+    isc::data::ElementPtr elements;
+    try {
+        elements = isc::data::Element::fromJSON(json);
+    } catch (isc::data::JSONError& ex) {
+        isc_throw(NcrMessageError,
+                  "Malformed NameChangeRequest JSON: " << ex.what());
+    }
+
+    // Get a map of the Elements, keyed by element name.
+    ElementMap element_map = elements->mapValue();
+    isc::data::ConstElementPtr element;
+
+    // Use default constructor to create a "blank" NameChangeRequest.
+    NameChangeRequestPtr ncr(new NameChangeRequest());
+
+    // For each member of NameChangeRequest, find its element in the map and
+    // call the appropriate Element-based setter.  These setters may throw
+    // NcrMessageError if the given Element is the wrong type or its data
+    // content is lexically invalid.   If the element is NOT found in the
+    // map, getElement will throw NcrMessageError indicating the missing
+    // member. Currently there are no optional values.
+    element = ncr->getElement("change_type", element_map);
+    ncr->setChangeType(element);
+
+    element = ncr->getElement("forward_change", element_map);
+    ncr->setForwardChange(element);
+
+    element = ncr->getElement("reverse_change", element_map);
+    ncr->setReverseChange(element);
+
+    element = ncr->getElement("fqdn", element_map);
+    ncr->setFqdn(element);
+
+    element = ncr->getElement("ip_address", element_map);
+    ncr->setIpAddress(element);
+
+    element = ncr->getElement("dhcid", element_map);
+    ncr->setDhcid(element);
+
+    element = ncr->getElement("lease_expires_on", element_map);
+    ncr->setLeaseExpiresOn(element);
+
+    element = ncr->getElement("lease_length", element_map);
+    ncr->setLeaseLength(element);
+
+    // All members were in the Element set and were correct lexically. Now
+    // validate the overall content semantically.  This will throw an
+    // NcrMessageError if anything is amiss.
+    ncr->validateContent();
+
+    // Everything is valid, return the new instance.
+    return (ncr);
+}
+
+std::string
+NameChangeRequest::toJSON() const  {
+    // Create a JSON string of this request's contents.  Note that this method
+    // does NOT use the isc::data library as generating the output is straight
+    // forward.
+    std::ostringstream stream;
+
+    stream << "{\"change_type\":" << getChangeType() << ","
+        << "\"forward_change\":"
+        << (isForwardChange() ? "true" : "false") << ","
+        << "\"reverse_change\":"
+        << (isReverseChange() ? "true" : "false") << ","
+        << "\"fqdn\":\"" << getFqdn() << "\","
+        << "\"ip_address\":\"" << getIpAddress() << "\","
+        << "\"dhcid\":\"" << getDhcid().toStr() << "\","
+        << "\"lease_expires_on\":\""  << getLeaseExpiresOnStr() << "\","
+        << "\"lease_length\":" << getLeaseLength() << "}";
+
+    return (stream.str());
+}
+
+
+void
+NameChangeRequest::validateContent() {
+    //@todo This is an initial implementation which provides a minimal amount
+    // of validation.  FQDN, DHCID, and IP Address members are all currently
+    // strings, these may be replaced with richer classes.
+    if (fqdn_ == "") {
+        isc_throw(NcrMessageError, "FQDN cannot be blank");
+    }
+
+    // Validate IP Address.
+    try {
+        isc::asiolink::IOAddress io_addr(ip_address_);
+    } catch (const isc::asiolink::IOError& ex) {
+        isc_throw(NcrMessageError,
+                  "Invalid ip address string for ip_address: " << ip_address_);
+    }
+
+    // Validate the DHCID.
+    if (dhcid_.getBytes().size()  == 0) {
+        isc_throw(NcrMessageError, "DHCID cannot be blank");
+    }
+
+    // Validate lease expiration.
+    if (lease_expires_on_->is_not_a_date_time()) {
+        isc_throw(NcrMessageError, "Invalid value for lease_expires_on");
+    }
+
+    // Ensure the request specifies at least one direction to update.
+    if (!forward_change_ && !reverse_change_) {
+        isc_throw(NcrMessageError,
+                  "Invalid Request, forward and reverse flags are both false");
+    }
+}
+
+isc::data::ConstElementPtr
+NameChangeRequest::getElement(const std::string& name,
+                              const ElementMap& element_map) const {
+    // Look for "name" in the element map.
+    ElementMap::const_iterator it = element_map.find(name);
+    if (it == element_map.end()) {
+        // Didn't find the element, so throw.
+        isc_throw(NcrMessageError,
+                  "NameChangeRequest value missing for: " << name );
+    }
+
+    // Found the element, return it.
+    return (it->second);
+}
+
+void
+NameChangeRequest::setChangeType(const NameChangeType value) {
+    change_type_ = value;
+}
+
+
+void
+NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
+    long raw_value = -1;
+    try {
+        // Get the element's integer value.
+        raw_value = element->intValue();
+    } catch (isc::data::TypeError& ex) {
+        // We expect a integer Element type, don't have one.
+        isc_throw(NcrMessageError,
+                  "Wrong data type for change_type: " << ex.what());
+    }
+
+    if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
+        // Value is not a valid change type.
+        isc_throw(NcrMessageError,
+                  "Invalid data value for change_type: " << raw_value);
+    }
+
+    // Good to go, make the assignment.
+    setChangeType(static_cast<NameChangeType>(raw_value));
+}
+
+void
+NameChangeRequest::setForwardChange(const bool value) {
+    forward_change_ = value;
+}
+
+void
+NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
+    bool value;
+    try {
+        // Get the element's boolean value.
+        value = element->boolValue();
+    } catch (isc::data::TypeError& ex) {
+        // We expect a boolean Element type, don't have one.
+        isc_throw(NcrMessageError,
+                  "Wrong data type for forward_change :" << ex.what());
+    }
+
+    // Good to go, make the assignment.
+    setForwardChange(value);
+}
+
+void
+NameChangeRequest::setReverseChange(const bool value) {
+    reverse_change_ = value;
+}
+
+void
+NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
+    bool value;
+    try {
+        // Get the element's boolean value.
+        value = element->boolValue();
+    } catch (isc::data::TypeError& ex) {
+        // We expect a boolean Element type, don't have one.
+        isc_throw(NcrMessageError,
+                  "Wrong data type for reverse_change :" << ex.what());
+    }
+
+    // Good to go, make the assignment.
+    setReverseChange(value);
+}
+
+
+void
+NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
+    setFqdn(element->stringValue());
+}
+
+void
+NameChangeRequest::setFqdn(const std::string& value) {
+    fqdn_ = value;
+}
+
+void
+NameChangeRequest::setIpAddress(const std::string& value) {
+    ip_address_ = value;
+}
+
+
+void
+NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
+    setIpAddress(element->stringValue());
+}
+
+
+void
+NameChangeRequest::setDhcid(const std::string& value) {
+    dhcid_.fromStr(value);
+}
+
+void
+NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
+    setDhcid(element->stringValue());
+}
+
+std::string
+NameChangeRequest::getLeaseExpiresOnStr() const {
+    if (!lease_expires_on_) {
+        // This is a programmatic error, should not happen.
+        isc_throw(NcrMessageError,
+            "lease_expires_on_ is null, cannot convert to string");
+    }
+
+    // Return the ISO date-time string for the value of lease_expires_on_.
+    return (to_iso_string(*lease_expires_on_));
+}
+
+void
+NameChangeRequest::setLeaseExpiresOn(const std::string&  value) {
+    try {
+        // Create a new ptime instance from the ISO date-time string in value
+        // add assign it to lease_expires_on_.
+        ptime* tptr = new ptime(from_iso_string(value));
+        lease_expires_on_.reset(tptr);
+    } catch(...) {
+        // We were given an invalid string, so throw.
+        isc_throw(NcrMessageError,
+            "Invalid ISO date-time string: [" << value << "]");
+    }
+
+}
+
+void
+NameChangeRequest::setLeaseExpiresOn(const boost::posix_time::ptime&  value) {
+    if (lease_expires_on_->is_not_a_date_time()) {
+        isc_throw(NcrMessageError, "Invalid value for lease_expires_on");
+    }
+
+    // Go to go, make the assignment.
+    lease_expires_on_.reset(new ptime(value));
+}
+
+void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
+    // Pull out the string value and pass it into the string setter.
+    setLeaseExpiresOn(element->stringValue());
+}
+
+void
+NameChangeRequest::setLeaseLength(const uint32_t value) {
+    lease_length_ = value;
+}
+
+void
+NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
+    long value = -1;
+    try {
+        // Get the element's integer value.
+        value = element->intValue();
+    } catch (isc::data::TypeError& ex) {
+        // We expect a integer Element type, don't have one.
+        isc_throw(NcrMessageError,
+                  "Wrong data type for lease_length: " << ex.what());
+    }
+
+    // Make sure we the range is correct and value is positive.
+    if (value > std::numeric_limits<uint32_t>::max()) {
+        isc_throw(NcrMessageError, "lease_length value " << value <<
+                "is too large for unsigned 32-bit integer.");
+    }
+    if (value < 0) {
+        isc_throw(NcrMessageError, "lease_length value " << value <<
+             "is negative.  It must greater than or equal to zero ");
+    }
+
+    // Good to go, make the assignment.
+    setLeaseLength(static_cast<uint32_t>(value));
+}
+
+void
+NameChangeRequest::setStatus(const NameChangeStatus value) {
+    status_ = value;
+}
+
+std::string
+NameChangeRequest::toText() const {
+    std::ostringstream stream;
+
+    stream << "Type: " << static_cast<int>(change_type_) << " (";
+    switch (change_type_) {
+    case CHG_ADD:
+        stream << "CHG_ADD)\n";
+        break;
+    case CHG_REMOVE:
+        stream << "CHG_REMOVE)\n";
+        break;
+    default:
+        // Shouldn't be possible.
+        stream << "Invalid Value\n";
+    }
+
+    stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
+           << std::endl
+           << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
+           << std::endl
+           << "FQDN: [" << fqdn_ << "]" << std::endl
+           << "IP Address: [" << ip_address_  << "]" << std::endl
+           << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
+           << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
+           << "Lease Length: " << lease_length_ << std::endl;
+
+    return (stream.str());
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 503 - 0
src/bin/d2/ncr_msg.h

@@ -0,0 +1,503 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NCR_MSG_H
+#define NCR_MSG_H
+
+/// @file ncr_msg.h
+/// @brief This file provides the classes needed to embody, compose, and
+/// decompose DNS update requests that are sent by DHCP-DDNS clients to
+/// DHCP-DDNS. These requests are referred to as NameChangeRequests.
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <time.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown when NameChangeRequest marshalling error occurs.
+class NcrMessageError : public isc::Exception {
+public:
+    NcrMessageError(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,
+  CHG_REMOVE
+};
+
+/// @brief Defines the runtime processing status values for requests.
+enum NameChangeStatus  {
+  ST_NEW,
+  ST_PENDING,
+  ST_COMPLETED,
+  ST_FAILED,
+};
+
+/// @brief Defines the list of data wire formats supported.
+enum NameChangeFormat {
+  FMT_JSON
+};
+
+/// @brief Container class for handling the DHCID value within a
+/// NameChangeRequest. It provides conversion to and from string for JSON
+/// formatting, but stores the data internally as unsigned bytes.
+class D2Dhcid {
+public:
+    /// @brief Default constructor
+    D2Dhcid();
+
+    /// @brief Constructor - Creates a new instance, populated by converting
+    /// a given string of digits into an array of unsigned bytes.
+    ///
+    /// @param data is a string of hexadecimal digits. The format is simply
+    /// a contiguous stream of digits, with no delimiters. For example a string
+    /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
+    ///
+    /// @throw throws a NcrMessageError if the input data contains non-digits
+    /// or there is an odd number of digits.
+    D2Dhcid(const std::string& data);
+
+    /// @brief Returns the DHCID value as a string of hexadecimal digits.
+    ///
+    /// @return returns a string containing a contiguous stream of digits.
+    std::string toStr() const;
+
+    /// @brief Sets the DHCID value based on the given string.
+    ///
+    /// @param data is a string of hexadecimal digits. The format is simply
+    /// a contiguous stream of digits, with no delimiters. For example a string
+    /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
+    ///
+    /// @throw throws a NcrMessageError if the input data contains non-digits
+    /// or there is an odd number of digits.
+    void fromStr(const std::string& data);
+
+    /// @brief Returns a reference to the DHCID byte vector.
+    ///
+    /// @return returns a reference to the vector.
+    const std::vector<uint8_t>& getBytes() {
+        return (bytes_);
+    }
+
+private:
+    /// @brief Storage for the DHCID value in unsigned bytes.
+    std::vector<uint8_t> bytes_;
+};
+
+/// @brief Defines a pointer to a ptime.
+/// NameChangeRequest member(s) that are timestamps are ptime instances.
+/// Boost ptime was chosen because it supports converting to and from ISO
+/// strings in GMT.  The Unix style time.h classes convert to GMT but
+/// conversion back assumes local time.  This is problematic if the "wire"
+/// format is string (i.e. JSON) and the request were to cross time zones.
+/// Additionally, time_t values should never be used directly so shipping them
+/// as string integers across platforms could theoretically be a problem.
+typedef boost::shared_ptr<boost::posix_time::ptime> TimePtr;
+
+class NameChangeRequest;
+/// @brief Defines a pointer to a NameChangeRequest.
+typedef boost::shared_ptr<NameChangeRequest> NameChangeRequestPtr;
+
+/// @brief Defines a map of Elements, keyed by their string name.
+typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
+
+/// @brief  Represents a DHCP-DDNS client request.
+/// This class is used by DHCP-DDNS clients (e.g. DHCP4, DHCP6) to
+/// request DNS updates.  Each message contains a single DNS change (either an
+/// add/update or a remove) for a single FQDN.  It provides marshalling services
+/// for moving instances to and from the wire.  Currently, the only format
+/// supported is JSON, however the class provides an interface such that other
+/// formats can be readily supported.
+class NameChangeRequest {
+public:
+    /// @brief Default Constructor.
+    NameChangeRequest();
+
+    /// @brief Constructor.  Full constructor, which provides parameters for
+    /// all of the class members, except status.
+    ///
+    /// @param change_type the type of change (Add or Update)
+    /// @param forward_change indicates if this change should be sent to forward
+    /// DNS servers.
+    /// @param reverse_change indicates if this change should be sent to reverse
+    /// DNS servers.
+    /// @param fqdn the domain name whose pointer record(s) should be
+    /// updated.
+    /// @param ip_address the ip address leased to the given FQDN.
+    /// @param dhcid the lease client's unique DHCID.
+    /// @param lease_expires_on a timestamp containing the date/time the lease 
+    /// expires.
+    /// @param lease_length the amount of time in seconds for which the
+    /// lease is valid (TTL).
+    NameChangeRequest(const NameChangeType change_type,
+                      const bool forward_change, const bool reverse_change,
+                      const std::string& fqdn, const std::string& ip_address,
+                      const D2Dhcid& dhcid,
+                      const boost::posix_time::ptime& lease_expires_on,
+                      const uint32_t lease_length);
+
+    /// @brief Static method for creating a NameChangeRequest from a
+    /// buffer containing a marshalled request in a given format.
+    ///
+    /// When the format is:
+    ///
+    /// JSON: The buffer is expected to contain a two byte unsigned integer
+    /// which specified the length of the JSON text; followed by the JSON
+    /// text itself.  This method attempts to extract "length" characters
+    /// from the buffer. This data is used to create a character string that
+    /// is than treated as JSON which is then parsed into the data needed
+    /// to create a request instance.
+    ///
+    /// (NOTE currently only JSON is supported.)
+    ///
+    /// @param format indicates the data format to use
+    /// @param buffer is the input buffer containing the marshalled request
+    ///
+    /// @return returns a pointer to the new NameChangeRequest
+    ///
+    /// @throw throws NcrMessageError if an error occurs creating new
+    /// request.
+    static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
+                                           isc::util::InputBuffer& buffer);
+
+    /// @brief Instance method for marshalling the contents of the request
+    /// into the given buffer in the given format.
+    ///
+    /// When the format is:
+    ///
+    /// JSON: Upon completion, the buffer will contain a two byte unsigned
+    /// integer which specifies the length of the JSON text; followed by the
+    /// JSON text itself. The JSON text contains the names and values for all
+    /// the request data needed to reassemble the request on the receiving
+    /// end. The JSON text in the buffer is NOT null-terminated.
+    ///
+    /// (NOTE currently only JSON is supported.)
+    ///
+    /// @param format indicates the data format to use
+    /// @param buffer is the output buffer to which the request should be
+    /// marshalled.
+    void toFormat(const NameChangeFormat format,
+                  isc::util::OutputBuffer& buffer) const;
+
+    /// @brief Static method for creating a NameChangeRequest from a
+    /// string containing a JSON rendition of a request.
+    ///
+    /// @param json is a string containing the JSON text
+    ///
+    /// @return returns a pointer to the new NameChangeRequest
+    ///
+    /// @throw throws NcrMessageError if an error occurs creating new request.
+    static NameChangeRequestPtr fromJSON(const std::string& json);
+
+    /// @brief Instance method for marshalling the contents of the request
+    /// into a string of JSON text.
+    ///
+    /// @return returns a string containing the JSON rendition of the request
+    std::string toJSON() const;
+
+    /// @brief Validates the content of a populated request.  This method is
+    /// used by both the full constructor and from-wire marshalling to ensure
+    /// that the request is content valid.  Currently it enforces the
+    /// following rules:
+    ///
+    ///  - FQDN must not be blank.
+    ///  - The IP address must be a valid address.
+    ///  - The DHCID must not be blank.
+    ///  - The lease expiration date must be a valid date/time.
+    ///  - That at least one of the two direction flags, forward change and
+    ///    reverse change is true.
+    ///
+    /// @todo This is an initial implementation which provides a minimal amount
+    /// of validation.  FQDN, DHCID, and IP Address members are all currently
+    /// strings, these may be replaced with richer classes.
+    ///
+    /// @throw throws a NcrMessageError if the request content violates any
+    /// of the validation rules.
+    void validateContent();
+
+    /// @brief Fetches the request change type.
+    ///
+    /// @return returns the change type
+    NameChangeType getChangeType() const {
+        return (change_type_);
+    }
+
+    /// @brief Sets the change type to the given value.
+    ///
+    /// @param value is the NameChangeType value to assign to the request.
+    void setChangeType(const NameChangeType value);
+
+    /// @brief Sets the change type to the value of the given Element.
+    ///
+    /// @param element is an integer Element containing the change type value.
+    ///
+    /// @throw throws a NcrMessageError if the element is not an integer
+    /// Element or contains an invalid value.
+    void setChangeType(isc::data::ConstElementPtr element);
+
+    /// @brief Checks forward change flag.
+    ///
+    /// @return returns a true if the forward change flag is true.
+    bool isForwardChange() const {
+        return (forward_change_);
+    }
+
+    /// @brief Sets the forward change flag to the given value.
+    ///
+    /// @param value contains the new value to assign to the forward change
+    /// flag
+    void setForwardChange(const bool value);
+
+    /// @brief Sets the forward change flag to the value of the given Element.
+    ///
+    /// @param element is a boolean Element containing the forward change flag
+    /// value.
+    ///
+    /// @throw throws a NcrMessageError if the element is not a boolean
+    /// Element
+    void setForwardChange(isc::data::ConstElementPtr element);
+
+    /// @brief Checks reverse change flag.
+    ///
+    /// @return returns a true if the reverse change flag is true.
+    bool isReverseChange() const {
+        return (reverse_change_);
+    }
+
+    /// @brief Sets the reverse change flag to the given value.
+    ///
+    /// @param value contains the new value to assign to the reverse change
+    /// flag
+    void setReverseChange(const bool value);
+
+    /// @brief Sets the reverse change flag to the value of the given Element.
+    ///
+    /// @param element is a boolean Element containing the reverse change flag
+    /// value.
+    ///
+    /// @throw throws a NcrMessageError if the element is not a boolean
+    /// Element
+    void setReverseChange(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request FQDN
+    ///
+    /// @return returns a string containing the FQDN
+    const std::string getFqdn() const {
+        return (fqdn_);
+    }
+
+    /// @brief Sets the FQDN to the given value.
+    ///
+    /// @param value contains the new value to assign to the FQDN
+    void setFqdn(const std::string& value);
+
+    /// @brief Sets the FQDN to the value of the given Element.
+    ///
+    /// @param element is a string Element containing the FQDN
+    ///
+    /// @throw throws a NcrMessageError if the element is not a string
+    /// Element
+    void setFqdn(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request IP address.
+    ///
+    /// @return returns a string containing the IP address
+    const std::string& getIpAddress() const {
+        return (ip_address_);
+    }
+
+    /// @brief Sets the IP address to the given value.
+    ///
+    /// @param value contains the new value to assign to the IP address
+    void setIpAddress(const std::string& value);
+
+    /// @brief Sets the IP address to the value of the given Element.
+    ///
+    /// @param element is a string Element containing the IP address
+    ///
+    /// @throw throws a NcrMessageError if the element is not a string
+    /// Element
+    void setIpAddress(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request DHCID
+    ///
+    /// @return returns a reference to the request's D2Dhcid
+    const D2Dhcid& getDhcid() const {
+        return (dhcid_);
+    }
+
+    /// @brief Sets the DHCID based on the given string value.
+    ///
+    /// @param value is a string of hexadecimal digits. The format is simply
+    /// a contiguous stream of digits, with no delimiters. For example a string
+    /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
+    ///
+    /// @throw throws a NcrMessageError if the input data contains non-digits
+    /// or there is an odd number of digits.
+    void setDhcid(const std::string& value);
+
+    /// @brief Sets the DHCID based on the value of the given Element.
+    ///
+    /// @param element is a string Element containing the string of hexadecimal
+    /// digits. (See setDhcid(std::string&) above.)
+    ///
+    /// @throw throws a NcrMessageError if the input data contains non-digits
+    /// or there is an odd number of digits.
+    void setDhcid(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request lease expiration as a timestamp.
+    ///
+    /// @return returns a pointer to the ptime containing the lease expiration
+    const TimePtr& getLeaseExpiresOn() const {
+        return (lease_expires_on_);
+    }
+
+    /// @brief Fetches the request lease expiration as string.
+    ///
+    /// The format of the string returned is:
+    ///
+    ///    YYYYMMDDTHHMMSS where T is the date-time separator
+    ///
+    /// Example: 18:54:54 June 26, 2013 would be: 20130626T185455
+    ///
+    /// @return returns a ISO date-time string of the lease expiration.
+    std::string getLeaseExpiresOnStr() const;
+
+    /// @brief Sets the lease expiration to given ptime value.
+    ///
+    /// @param value is the ptime value to assign to the lease expiration.
+    ///
+    /// @throw throws a NcrMessageError if the value is not a valid
+    /// timestamp.
+    void setLeaseExpiresOn(const boost::posix_time::ptime& value);
+
+    /// @brief Sets the lease expiration based on the given string.
+    ///
+    /// @param value is an ISO date-time string from which to set the
+    /// lease expiration. The format of the input is:
+    ///
+    ///    YYYYMMDDTHHMMSS where T is the date-time separator
+    ///
+    /// Example: 18:54:54 June 26, 2013 would be: 20130626T185455
+    ///
+    /// @throw throws a NcrMessageError if the ISO string is invalid.
+    void setLeaseExpiresOn(const std::string& value);
+
+    /// @brief Sets the lease expiration based on the given Element.
+    ///
+    /// @param element is string Element containing an ISO date-time string.
+    ///
+    /// @throw throws a NcrMessageError if the element is not a string
+    /// Element, or if the element value is an invalid ISO date-time string.
+    void setLeaseExpiresOn(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request lease length.
+    ///
+    /// @return returns an integer containing the lease length
+    uint32_t getLeaseLength() const {
+        return (lease_length_);
+    }
+
+    /// @brief Sets the lease length to the given value.
+    ///
+    /// @param value contains the new value to assign to the lease length
+    void setLeaseLength(const uint32_t value);
+
+    /// @brief Sets the lease length to the value of the given Element.
+    ///
+    /// @param element is a integer Element containing the lease length
+    ///
+    /// @throw throws a NcrMessageError if the element is not a string
+    /// Element
+    void setLeaseLength(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request status.
+    ///
+    /// @return returns the request status as a NameChangeStatus
+    NameChangeStatus getStatus() const {
+        return (status_);
+    }
+
+    /// @brief Sets the request status to the given value.
+    ///
+    /// @param value contains the new value to assign to request status
+    void setStatus(const NameChangeStatus value);
+
+    /// @brief Given a name, finds and returns an element from a map of
+    /// elements.
+    ///
+    /// @param name is the name of the desired element
+    /// @param element_map is the map of elements to search
+    ///
+    /// @return returns a pointer to the element if located
+    /// @throw throws a NcrMessageError if the element cannot be found within
+    /// the map
+    isc::data::ConstElementPtr getElement(const std::string& name,
+                                          const ElementMap& element_map) const;
+
+    /// @brief Returns a text rendition of the contents of the request.
+    /// This method is primarily for logging purposes.
+    ///
+    /// @return returns a string containing the text.
+    std::string toText() const;
+
+private:
+    /// @brief Denotes the type of this change as either an Add or a Remove.
+    NameChangeType change_type_;
+
+    /// @brief Indicates if this change should sent to forward DNS servers.
+    bool forward_change_;
+
+    /// @brief Indicates if this change should sent to reverse DNS servers.
+    bool reverse_change_;
+
+    /// @brief The domain name whose DNS entry(ies) are to be updated.
+    /// @todo Currently, this is a std::string but may be replaced with
+    /// dns::Name which provides additional validation and domain name
+    /// manipulation.
+    std::string fqdn_;
+
+    /// @brief The ip address leased to the FQDN.
+    std::string ip_address_;
+
+    /// @brief The lease client's unique DHCID.
+    /// @todo Currently, this is uses D2Dhcid it but may be replaced with
+    /// dns::DHCID which provides additional validation.
+    D2Dhcid dhcid_;
+
+    /// @brief The date-time the lease expires.
+    TimePtr lease_expires_on_;
+
+    /// @brief The amount of time in seconds for which the lease is valid (TTL).
+    uint32_t lease_length_;
+
+    /// @brief The processing status of the request.  Used internally.
+    NameChangeStatus status_;
+};
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif

+ 4 - 0
src/bin/d2/tests/Makefile.am

@@ -61,6 +61,7 @@ d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
 d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
 d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
+d2_unittests_SOURCES += ../ncr_msg.cc ../ncr_msg.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
@@ -70,6 +71,7 @@ d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
 d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
 d2_unittests_SOURCES += d2_update_message_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
+d2_unittests_SOURCES += ncr_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -82,6 +84,8 @@ d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+
 endif
 
 noinst_PROGRAMS = $(TESTS)

+ 428 - 0
src/bin/d2/tests/ncr_unittests.cc

@@ -0,0 +1,428 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/ncr_msg.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *valid_msgs[] =
+{
+    // Valid Add.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Valid Remove.
+     "{"
+     " \"change_type\" : 1 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+     // Valid Add with IPv6 address
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}"
+};
+
+/// @brief Defines a list of invalid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *invalid_msgs[] =
+{
+    // Invalid change type.
+     "{"
+     " \"change_type\" : 7 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Invalid forward change.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : \"bogus\" , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Invalid reverse change.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : 500 , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Forward and reverse change both false.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : false , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Blank FQDN
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Bad IP address
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"xxxxxx\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Blank DHCID
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Odd number of digits in DHCID
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Text in DHCID
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"THIS IS BOGUS!!!\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Invalid lease expiration string
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Non-integer for lease length.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"19620121T132405\" , "
+     " \"lease_length\" : \"BOGUS\" "
+     "}"
+
+};
+
+/// @brief Tests the NameChangeRequest constructors.
+/// This test verifies that:
+/// 1. Default constructor works.
+/// 2. "Full" constructor, when given valid parameter values, works.
+/// 3. "Full" constructor, given a blank FQDN fails
+/// 4. "Full" constructor, given an invalid IP Address FQDN fails
+/// 5. "Full" constructor, given a blank DHCID fails
+/// 6. "Full" constructor, given an invalid lease expiration fails
+/// 7. "Full" constructor, given false for both forward and reverse fails
+TEST(NameChangeRequestTest, constructionTests) {
+    // Verify the default constructor works.
+    NameChangeRequestPtr ncr;
+    EXPECT_NO_THROW(ncr.reset(new NameChangeRequest()));
+    EXPECT_TRUE(ncr);
+
+    // Verify that full constructor works.
+    ptime expiry(second_clock::universal_time());
+    D2Dhcid dhcid("010203040A7F8E3D");
+
+    EXPECT_NO_THROW(ncr.reset(new NameChangeRequest(
+                    CHG_ADD, true, true, "walah.walah.com",
+                    "192.168.1.101", dhcid, expiry, 1300)));
+    EXPECT_TRUE(ncr);
+    ncr.reset();
+
+    // Verify blank FQDN is detected.
+    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "",
+                 "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+    // Verify that an invalid IP address is detected.
+    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
+                 "xxx.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+    // Verify that a blank DHCID is detected.
+    D2Dhcid blank_dhcid;
+    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "walah.walah.com",
+                 "192.168.1.101", blank_dhcid, expiry, 1300), NcrMessageError);
+
+    // Verify that an invalid lease expiration is detected.
+    ptime blank_expiry;
+    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
+                 "192.168.1.101", dhcid, blank_expiry, 1300), NcrMessageError);
+
+    // Verify that one or both of direction flags must be true.
+    EXPECT_THROW(NameChangeRequest(CHG_ADD, false, false, "valid.fqdn",
+                "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+}
+
+/// @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
+/// 2. DHCID input strings must contain only hexadecimal character digits
+/// 3. A valid DHCID string converts correctly.
+/// 4. Converting a D2Dhcid to a string works correctly.
+TEST(NameChangeRequestTest, dhcidTest) {
+    D2Dhcid dhcid;
+
+    // Odd number of digits should be rejected.
+    std::string test_str = "010203040A7F8E3";
+    EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+    // Non digit content should be rejected.
+    test_str = "0102BOGUSA7F8E3D";
+    EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+    // Verify that valid input converts into a proper byte array.
+    test_str = "010203040A7F8E3D";
+    ASSERT_NO_THROW(dhcid.fromStr(test_str));
+
+    // Create a test vector of expected byte contents.
+    const uint8_t bytes[] = { 0x1, 0x2, 0x3, 0x4, 0xA, 0x7F, 0x8E, 0x3D };
+    std::vector<uint8_t> expected_bytes(bytes, bytes + sizeof(bytes));
+
+    // Fetch the byte vector from the dhcid and verify if equals the expected
+    // content.
+    const std::vector<uint8_t>& converted_bytes = dhcid.getBytes();
+    EXPECT_EQ(expected_bytes.size(), converted_bytes.size());
+    EXPECT_TRUE (std::equal(expected_bytes.begin(),
+                            expected_bytes.begin()+expected_bytes.size(),
+                            converted_bytes.begin()));
+
+    // Convert the new dhcid back to string and verify it matches the original
+    // DHCID input string.
+    std::string next_str = dhcid.toStr();
+    EXPECT_EQ(test_str, next_str);
+}
+
+/// @brief Tests that boost::posix_time library functions as expected.
+/// This test verifies that converting ptimes to and from ISO strings
+/// works properly. This test is perhaps unnecessary but just to avoid any
+/// OS specific surprises it is better safe than sorry.
+TEST(NameChangeRequestTest, boostTime) {
+   // Create a ptime with the time now.
+   ptime pt1(second_clock::universal_time());
+
+   // Get the ISO date-time string.
+   std::string pt1_str = to_iso_string(pt1);
+
+   // Use the ISO date-time string to create a new ptime.
+   ptime pt2 = from_iso_string(pt1_str);
+
+   // Verify the two times are equal.
+   EXPECT_EQ (pt1, pt2);
+}
+
+/// @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\":\"19620121T132405\","
+                            "\"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\":\"19620121T132405\","
+                            "\"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);
+}
+
+
+} // end of anonymous namespace
+