Browse Source

[master] Merge branch 'trac3008'. This adds classes to support
sending and receiving update requests with DHCP-DDNS.

Thomas Markwalder 12 years ago
parent
commit
b54530b453

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

@@ -57,7 +57,9 @@ 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 += dns_client.cc dns_client.h
+b10_dhcp_ddns_SOURCES += ncr_io.cc ncr_io.h
 b10_dhcp_ddns_SOURCES += ncr_msg.cc ncr_msg.h
+b10_dhcp_ddns_SOURCES += ncr_udp.cc ncr_udp.h
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes

+ 5 - 9
src/bin/d2/d2_cfg_mgr.cc

@@ -17,12 +17,6 @@
 
 #include <boost/foreach.hpp>
 
-using namespace std;
-using namespace isc;
-using namespace isc::dhcp;
-using namespace isc::data;
-using namespace isc::asiolink;
-
 namespace isc {
 namespace d2 {
 
@@ -102,12 +96,14 @@ D2CfgMgr::createConfigParser(const std::string& config_id) {
     D2CfgContextPtr context = getD2CfgContext();
 
     // Create parser instance based on element_id.
-    DhcpConfigParser* parser = NULL;
+    isc::dhcp::DhcpConfigParser* parser = NULL;
     if ((config_id == "interface")  ||
         (config_id == "ip_address")) {
-        parser = new StringParser(config_id, context->getStringStorage());
+        parser = new isc::dhcp::StringParser(config_id, 
+                                             context->getStringStorage());
     } else if (config_id == "port") {
-        parser = new Uint32Parser(config_id, context->getUint32Storage());
+        parser = new isc::dhcp::Uint32Parser(config_id, 
+                                             context->getUint32Storage());
     } else if (config_id ==  "forward_ddns") {
         parser = new DdnsDomainListMgrParser("forward_mgr",
                                              context->getForwardMgr(),

+ 56 - 8
src/bin/d2/d2_messages.mes

@@ -70,7 +70,7 @@ application when it is not running.
 
 % DCTL_ORDER_ERROR configuration contains more elements than the parsing order
 An error message which indicates that configuration being parsed includes
-element ids not specified the configuration manager's parse order list. This 
+element ids not specified the configuration manager's parse order list. This
 is a programmatic error.
 
 % DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
@@ -78,7 +78,7 @@ An error message output during a configuration update.  The program is
 expecting an item but has not found it in the new configuration.  This may
 mean that the BIND 10 configuration database is corrupt.
 
-% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2 
+% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2
 On receipt of message containing details to a change of its configuration,
 the server failed to create a parser to decode the contents of the named
 configuration element, or the creation succeeded but the parsing actions
@@ -124,16 +124,52 @@ has been invoked.
 This is a debug message issued when the Dhcp-Ddns application encounters an
 unrecoverable error from within the event loop.
 
+% DHCP_DDNS_INVALID_NCR application received an invalid DNS update request: %1
+This is an error message that indicates that an invalid request to update
+a DNS entry was received by the application.  Either the format or the content
+of the request is incorrect. The request will be ignored.
+
+% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
+This is a debug message issued when the DHCP-DDNS application encountered an
+error while decoding a response to DNS Update message. Typically, this error
+will be encountered when a response message is malformed.
+
+% DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR application encountered an error while closing the listener used to receive NameChangeRequests : %1
+This is an error message that indicates the application was unable to close the
+listener connection used to receive NameChangeRequests.  Closure may occur
+during the course of error recovery or during normal shutdown procedure.  In
+either case the error is unlikely to impair the application's ability to
+process requests but it should be reported for analysis.
+
+% DHCP_DDNS_NCR_RECV_NEXT_ERROR application could not initiate the next read following a request receive.
+This is a error message indicating that NameChangeRequest listener could not
+start another read after receiving a request.  While possible, this is highly
+unlikely and is probably a programmatic error.  The application should recover
+on its own.
+
+% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests : %1
+This is an error message that indicates the DHCP-DDNS client was unable to
+close the connection used to send NameChangeRequests.  Closure may occur during
+the course of error recovery or during normal shutdown procedure.  In either
+case the error is unlikely to impair the client's ability to send requests but
+it should be reported for analysis.
+
+% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion.
+This is a error message indicating that NameChangeRequest sender could not
+start another send after completing the send of the previous request.  While
+possible, this is highly unlikely and is probably a programmatic error.  The
+application should recover on its own.
+
+% DHCP_DDNS_NCR_UDP_RECV_ERROR UDP socket receive error while listening for DNS Update requests: %1
+This is an error message indicating that an IO error occurred while listening
+over a UDP socket for DNS update requests. This could indicate a network
+connectivity or system resource issue.
+
 % DHCP_DDNS_NO_MATCH No DNS servers match FQDN: %1
 This is warning message issued when there are no domains in the configuration
-which match the cited fully qualified domain name (FQDN).  The DNS Update 
+which match the cited fully qualified domain name (FQDN).  The DNS Update
 request for the FQDN cannot be processed.
 
-% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
-This is a debug message issued when the DHCP-DDNS application encountered an error
-while decoding a response to DNS Update message. Typically, this error will be
-encountered when a response message is malformed.
-
 % DHCP_DDNS_PROCESS_INIT application init invoked
 This is a debug message issued when the Dhcp-Ddns application enters
 its init method.
@@ -149,3 +185,15 @@ in event loop.
 % DHCP_DDNS_SHUTDOWN application is performing a normal shut down
 This is a debug message issued when the application has been instructed
 to shut down by the controller.
+
+% DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR unexpected exception thrown from the application receive completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's request receive completion handler.  This is a
+programmatic error that needs to be reported.  Dependent upon the nature of
+the error the application may or may not continue operating normally.
+
+% DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR unexpected exception thrown from the DHCP-DDNS client send completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's send completion handler.  This is a programmatic
+error that needs to be reported.  Dependent upon the nature of the error the
+client may or may not continue operating normally.

+ 281 - 0
src/bin/d2/ncr_io.cc

@@ -0,0 +1,281 @@
+// 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/d2_log.h>
+#include <d2/ncr_io.h>
+
+namespace isc {
+namespace d2 {
+
+//************************** NameChangeListener ***************************
+
+NameChangeListener::NameChangeListener(RequestReceiveHandler&
+                                       recv_handler)
+    : listening_(false), recv_handler_(recv_handler) {
+};
+
+void
+NameChangeListener::startListening(isc::asiolink::IOService& io_service) {
+    if (amListening()) {
+        // This amounts to a programmatic error.
+        isc_throw(NcrListenerError, "NameChangeListener is already listening");
+    }
+
+    // Call implementation dependent open.
+    try {
+        open(io_service);
+    } catch (const isc::Exception& ex) {
+        stopListening();
+        isc_throw(NcrListenerOpenError, "Open failed:" << ex.what());
+    }
+
+    // Set our status to listening.
+    setListening(true);
+
+    // Start the first asynchronous receive.
+    try {
+        doReceive();
+    } catch (const isc::Exception& ex) {
+        stopListening();
+        isc_throw(NcrListenerReceiveError, "doReceive failed:" << ex.what());
+    }
+}
+
+void
+NameChangeListener::stopListening() {
+    try {
+        // Call implementation dependent close.
+        close();
+    } catch (const isc::Exception &ex) {
+        // Swallow exceptions. If we have some sort of error we'll log
+        // it but we won't propagate the throw.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR).arg(ex.what());
+    }
+
+    // Set it false, no matter what.  This allows us to at least try to
+    // re-open via startListening().
+    setListening(false);
+}
+
+void
+NameChangeListener::invokeRecvHandler(const Result result,
+                                      NameChangeRequestPtr& ncr) {
+    // Call the registered application layer handler.
+    // Surround the invocation with a try-catch. The invoked handler is
+    // not supposed to throw, but in the event it does we will at least
+    // report it.
+    try {
+        recv_handler_(result, ncr);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dctl_logger, DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+                  .arg(ex.what());
+    }
+
+    // Start the next IO layer asynchronous receive.
+    // In the event the handler above intervened and decided to stop listening
+    // we need to check that first.
+    if (amListening()) {
+        try {
+            doReceive();
+        } catch (const isc::Exception& ex) {
+            // It is possible though unlikely, for doReceive to fail without
+            // scheduling the read. While, unlikely, it does mean the callback
+            // will not get called with a failure. A throw here would surface
+            // at the IOService::run (or run variant) invocation.  So we will
+            // close the window by invoking the application handler with
+            // a failed result, and let the application layer sort it out.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_RECV_NEXT_ERROR)
+                      .arg(ex.what());
+
+            // Call the registered application layer handler.
+            // Surround the invocation with a try-catch. The invoked handler is
+            // not supposed to throw, but in the event it does we will at least
+            // report it.
+            NameChangeRequestPtr empty;
+            try {
+                recv_handler_(ERROR, empty);
+            } catch (const std::exception& ex) {
+                LOG_ERROR(dctl_logger,
+                          DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+                          .arg(ex.what());
+            }
+        }
+    }
+}
+
+//************************* NameChangeSender ******************************
+
+NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
+                                   size_t send_queue_max)
+    : sending_(false), send_handler_(send_handler),
+      send_queue_max_(send_queue_max) {
+
+    // Queue size must be big enough to hold at least 1 entry.
+    if (send_queue_max == 0) {
+        isc_throw(NcrSenderError, "NameChangeSender constructor"
+                  " queue size must be greater than zero");
+    }
+}
+
+void
+NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
+    if (amSending()) {
+        // This amounts to a programmatic error.
+        isc_throw(NcrSenderError, "NameChangeSender is already sending");
+    }
+
+    // Clear send marker.
+    ncr_to_send_.reset();
+
+    // Call implementation dependent open.
+    try {
+        open(io_service);
+    } catch (const isc::Exception& ex) {
+        stopSending();
+        isc_throw(NcrSenderOpenError, "Open failed: " << ex.what());
+    }
+
+    // Set our status to sending.
+    setSending(true);
+}
+
+void
+NameChangeSender::stopSending() {
+    try {
+        // Call implementation dependent close.
+        close();
+    } catch (const isc::Exception &ex) {
+        // Swallow exceptions. If we have some sort of error we'll log
+        // it but we won't propagate the throw.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_SEND_CLOSE_ERROR).arg(ex.what());
+    }
+
+    // Set it false, no matter what.  This allows us to at least try to
+    // re-open via startSending().
+    setSending(false);
+}
+
+void
+NameChangeSender::sendRequest(NameChangeRequestPtr& ncr) {
+    if (!amSending()) {
+        isc_throw(NcrSenderError, "sender is not ready to send");
+    }
+
+    if (!ncr) {
+        isc_throw(NcrSenderError, "request to send is empty");
+    }
+
+    if (send_queue_.size() >= send_queue_max_) {
+        isc_throw(NcrSenderQueueFull, "send queue has reached maximum capacity:"
+                  << send_queue_max_ );
+    }
+
+    // Put it on the queue.
+    send_queue_.push_back(ncr);
+
+    // Call sendNext to schedule the next one to go.
+    sendNext();
+}
+
+void
+NameChangeSender::sendNext() {
+    if (ncr_to_send_) {
+        // @todo Not sure if there is any risk of getting stuck here but
+        // an interval timer to defend would be good.
+        // In reality, the derivation should ensure they timeout themselves
+        return;
+    }
+
+    // If queue isn't empty, then get one from the front. Note we leave
+    // it on the front of the queue until we successfully send it.
+    if (send_queue_.size()) {
+        ncr_to_send_ = send_queue_.front();
+
+       // @todo start defense timer
+       // If a send were to hang and we timed it out, then timeout
+       // handler need to cycle thru open/close ?
+
+       // Call implementation dependent send.
+       doSend(ncr_to_send_);
+    }
+}
+
+void
+NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
+    // @todo reset defense timer
+    if (result == SUCCESS) {
+        // It shipped so pull it off the queue.
+        send_queue_.pop_front();
+    }
+
+    // Invoke the completion handler passing in the result and a pointer
+    // the request involved.
+    // Surround the invocation with a try-catch. The invoked handler is
+    // not supposed to throw, but in the event it does we will at least
+    // report it.
+    try {
+        send_handler_(result, ncr_to_send_);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dctl_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
+                  .arg(ex.what());
+    }
+
+    // Clear the pending ncr pointer.
+    ncr_to_send_.reset();
+
+    // Set up the next send
+    try {
+        sendNext();
+    } catch (const isc::Exception& ex) {
+        // It is possible though unlikely, for sendNext to fail without
+        // scheduling the send. While, unlikely, it does mean the callback
+        // will not get called with a failure. A throw here would surface
+        // at the IOService::run (or run variant) invocation.  So we will
+        // close the window by invoking the application handler with
+        // a failed result, and let the application layer sort it out.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_SEND_NEXT_ERROR)
+                  .arg(ex.what());
+
+        // Invoke the completion handler passing in failed result.
+        // Surround the invocation with a try-catch. The invoked handler is
+        // not supposed to throw, but in the event it does we will at least
+        // report it.
+        try {
+            send_handler_(ERROR, ncr_to_send_);
+        } catch (const std::exception& ex) {
+            LOG_ERROR(dctl_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
+                      .arg(ex.what());
+        }
+    }
+}
+
+void
+NameChangeSender::skipNext() {
+    if (send_queue_.size()) {
+        // Discards the request at the front of the queue.
+        send_queue_.pop_front();
+    }
+}
+
+void
+NameChangeSender::clearSendQueue() {
+    if (amSending()) {
+        isc_throw(NcrSenderError, "Cannot clear queue while sending");
+    }
+
+    send_queue_.clear();
+}
+
+} // namespace isc::d2
+} // namespace isc

+ 632 - 0
src/bin/d2/ncr_io.h

@@ -0,0 +1,632 @@
+// 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_IO_H
+#define NCR_IO_H
+
+/// @file ncr_io.h
+/// @brief This file defines abstract classes for exchanging NameChangeRequests.
+///
+/// These classes are used for sending and receiving requests to update DNS
+/// information for FQDNs embodied as NameChangeRequests (aka NCRs). Ultimately,
+/// NCRs must move through the following three layers in order to implement
+/// DHCP-DDNS:
+///
+///    * Application layer - the business layer which needs to
+///    transport NameChangeRequests, and is unaware of the means by which
+///    they are transported.
+///
+///    * NameChangeRequest layer - This is the layer which acts as the
+///    intermediary between the Application layer and the IO layer.  It must
+///    be able to move NameChangeRequests to the IO layer as raw data and move
+///    raw data from the IO layer in the Application layer as
+///    NameChangeRequests.
+///
+///    * IO layer - the low-level layer that is directly responsible for
+///    sending and receiving data asynchronously and is supplied through
+///    other libraries.  This layer is largely unaware of the nature of the
+///    data being transmitted.  In other words, it doesn't know beans about
+///    NCRs.
+///
+/// The abstract classes defined here implement the latter, middle layer,
+/// the NameChangeRequest layer.  There are two types of participants in this
+/// middle ground:
+///
+///    * listeners - Receive NCRs from one or more sources. The DHCP-DDNS
+///   application, (aka D2), is a listener. Listeners are embodied by the
+///   class, NameChangeListener.
+///
+///    * senders - sends NCRs to a given target.  DHCP servers are senders.
+///   Senders are embodied by the class, NameChangeListener.
+///
+/// These two classes present a public interface for asynchronous
+/// communications that is independent of the IO layer mechanisms.  While the
+/// type and details of the IO mechanism are not relevant to either class, it
+/// is presumed to use isc::asiolink library for asynchronous event processing.
+///
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <d2/ncr_msg.h>
+#include <exceptions/exceptions.h>
+
+#include <deque>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if an NcrListenerError encounters a general error.
+class NcrListenerError : public isc::Exception {
+public:
+    NcrListenerError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrListenerOpenError : public isc::Exception {
+public:
+    NcrListenerOpenError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO receive.
+class NcrListenerReceiveError : public isc::Exception {
+public:
+    NcrListenerReceiveError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for receiving NameChangeRequests.
+///
+/// NameChangeListener provides the means to:
+/// -  Supply a callback to invoke upon receipt of an NCR or a listening
+/// error
+/// -  Start listening using a given IOService instance to process events
+/// -  Stop listening
+///
+/// It implements the high level logic flow to listen until a request arrives,
+/// invoke the implementation's handler and return to listening for the next
+/// request.
+///
+/// It provides virtual methods that allow derivations supply implementations
+/// to open the appropriate IO source, perform a listen, and close the IO
+/// source.
+///
+/// The overall design is based on a callback chain. The listener's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// receive inbound NameChangeRequests.  The listener derivation will supply
+/// its own callback to the IO layer to process receive events from its IO
+/// source.  This is referred to as the NameChangeRequest completion handler.
+/// It is through this handler that the NameChangeRequest layer gains access
+/// to the low level IO read service results.  It is expected to assemble
+/// NameChangeRequests from the inbound data and forward them to the
+/// application layer by invoking the application layer callback registered
+/// with the listener.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestReceiveHandler.  It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive inbound NCRs, a caller implements a derivation of the
+/// RequestReceiveHandler and supplies an instance of this derivation to the
+/// NameChangeListener constructor. This "registers" the handler with the
+/// listener.
+///
+/// To begin listening, the caller invokes the listener's startListener()
+/// method, passing in an IOService instance.  This in turn will pass the
+/// IOService into the virtual method, open().  The open method is where the
+/// listener derivation performs the steps necessary to prepare its IO source
+/// for reception (e.g. opening a socket, connecting to a database).
+///
+/// Assuming the open is successful, startListener will call the virtual
+/// doReceive() method.  The listener derivation uses this method to
+/// instigate an IO layer asynchronous passing in its IO layer callback to
+/// handle receive events from its IO source.
+///
+/// As stated earlier, the derivation's NameChangeRequest completion handler
+/// MUST invoke the application layer handler registered with the listener.
+/// This is done by passing in either a success status and a populated
+/// NameChangeRequest or an error status and an empty request into the
+/// listener's invokeRecvHandler method. This is the mechanism by which the
+/// listener's caller is handed inbound NCRs.
+class NameChangeListener {
+public:
+
+    /// @brief Defines the outcome of an asynchronous NCR receive
+    enum Result {
+      SUCCESS,
+      TIME_OUT,
+      STOPPED,
+      ERROR
+    };
+
+    /// @brief Abstract class for defining application layer receive callbacks.
+    ///
+    /// Applications which will receive NameChangeRequests must provide a
+    /// derivation of this class to the listener constructor in order to
+    /// receive NameChangeRequests.
+    class RequestReceiveHandler {
+      public:
+        /// @brief Function operator implementing a NCR receive callback.
+        ///
+        /// This method allows the application to receive the inbound
+        /// NameChangeRequests. It is intended to function as a hand off of
+        /// information and should probably not be time-consuming.
+        ///
+        /// @param result contains that receive outcome status.
+        /// @param ncr is a pointer to the newly received NameChangeRequest if
+        /// result is NameChangeListener::SUCCESS.  It is indeterminate other
+        /// wise.
+        /// @throw This method MUST NOT throw.
+        virtual void operator ()(const Result result,
+                                 NameChangeRequestPtr& ncr) = 0;
+    };
+
+    /// @brief Constructor
+    ///
+    /// @param recv_handler is a pointer the application layer handler to be
+    /// invoked each time a NCR is received or a receive error occurs.
+    NameChangeListener(RequestReceiveHandler& recv_handler);
+
+    /// @brief Destructor
+    virtual ~NameChangeListener() {
+    };
+
+    /// @brief Prepares the IO for reception and initiates the first receive.
+    ///
+    /// Calls the derivation's open implementation to initialize the IO layer
+    /// source for receiving inbound requests.  If successful, it issues first
+    /// asynchronous read by calling the derivation's doReceive implementation.
+    ///
+    /// @param io_service is the IOService that will handle IO event processing.
+    ///
+    /// @throw NcrListenError if the listener is already "listening" or
+    /// in the event the open or doReceive methods fail.
+    void startListening(isc::asiolink::IOService& io_service);
+
+    /// @brief Closes the IO source and stops listen logic.
+    ///
+    /// Calls the derivation's implementation of close and marks the state
+    /// as not listening.
+    void stopListening();
+
+    /// @brief Calls the NCR receive handler registered with the listener.
+    ///
+    /// This is the hook by which the listener's caller's NCR receive handler
+    /// is called.  This method MUST be invoked by the derivation's
+    /// implementation of doReceive.
+    ///
+    /// NOTE:
+    /// The handler invoked by this method MUST NOT THROW. The handler is
+    /// at application level and should trap and handle any errors at
+    /// that level, rather than throw exceptions.  If an error has occurred
+    /// prior to invoking the handler, it will be expressed in terms a failed
+    /// result being passed to the handler, not a throw.  Therefore any
+    /// exceptions at the handler level are application issues and should be
+    /// dealt with at that level.
+    ///
+    /// This method does wrap the handler invocation within a try-catch
+    /// block as a fail-safe.  The exception will be logged but the
+    /// receive logic will continue.  What this implies is that continued
+    /// operation may or may not succeed as the application has violated
+    /// the interface contract.
+    ///
+    /// @param result contains that receive outcome status.
+    /// @param ncr is a pointer to the newly received NameChangeRequest if
+    /// result is NameChangeListener::SUCCESS.  It is indeterminate other
+    /// wise.
+    void invokeRecvHandler(const Result result, NameChangeRequestPtr& ncr);
+
+    /// @brief Abstract method which opens the IO source for reception.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// prepare the IO source to receive requests.
+    ///
+    /// @param io_service is the IOService that process IO events.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+    /// @brief Abstract method which closes the IO source.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// "close" the IO source.
+    ///
+    /// @throw If the implementation encounters an error it  MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void close() = 0;
+
+    /// @brief Initiates an IO layer asynchronous read.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// initiate an asynchronous read of the IO source with the
+    /// derivation's IO layer handler as the IO completion callback.
+    ///
+    /// @throw If the implementation encounters an error it  MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void doReceive() = 0;
+
+    /// @brief Returns true if the listener is listening, false otherwise.
+    ///
+    /// A true value indicates that the IO source has been opened successfully,
+    /// and that receive loop logic is active.
+    bool amListening() const {
+        return (listening_);
+    }
+
+private:
+    /// @brief Sets the listening indicator to the given value.
+    ///
+    /// Note, this method is private as it is used the base class is solely
+    /// responsible for managing the state.
+    ///
+    /// @param value is the new value to assign to the indicator.
+    void setListening(bool value) {
+        listening_ = value;
+    }
+
+    /// @brief Indicates if the listener is listening.
+    bool listening_;
+
+    /// @brief Application level NCR receive completion handler.
+    RequestReceiveHandler& recv_handler_;
+};
+
+/// @brief Defines a smart pointer to an instance of a listener.
+typedef boost::shared_ptr<NameChangeListener> NameChangeListenerPtr;
+
+
+/// @brief Thrown when a NameChangeSender encounters an error.
+class NcrSenderError : public isc::Exception {
+public:
+    NcrSenderError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrSenderOpenError : public isc::Exception {
+public:
+    NcrSenderOpenError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderQueueFull : public isc::Exception {
+public:
+    NcrSenderQueueFull(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderSendError : public isc::Exception {
+public:
+    NcrSenderSendError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for sending NameChangeRequests.
+///
+/// NameChangeSender provides the means to:
+/// - Supply a callback to invoke upon completing the delivery of an NCR or a
+/// send error
+/// - Start sending using a given IOService instance to process events
+/// - Queue NCRs for delivery
+/// - Stop sending
+///
+/// It implements the high level logic flow to queue requests for delivery,
+/// and ship them one at a time, waiting for the send to complete prior to
+/// sending the next request in the queue.  If a send fails, the request
+/// will remain at the front of queue and will be the send will be retried
+/// endlessly unless the caller dequeues the request.  Note, it is presumed that
+/// a send failure is some form of IO error such as loss of connectivity and
+/// not a message content error.  It should not be possible to queue an invalid
+/// message.
+///
+/// It should be noted that once a request is placed onto the send queue it
+/// will remain there until one of three things occur:
+///     * It is successfully delivered
+///     * @c NameChangeSender::skipNext() is called
+///     * @c NameChangeSender::clearSendQueue() is called
+///
+/// The queue contents are preserved across start and stop listening
+/// transitions. This is to provide for error recovery without losing
+/// undelivered requests.
+
+/// It provides virtual methods so derivations may supply implementations to
+/// open the appropriate IO sink, perform a send, and close the IO sink.
+///
+/// The overall design is based on a callback chain.  The sender's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// be given send completion notifications. The sender derivation will employ
+/// its own callback at the IO layer to process send events from its IO sink.
+/// This callback is expected to forward the outcome of each asynchronous send
+/// to the application layer by invoking the application layer callback
+/// registered with the sender.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestSendHandler.  It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive send completion notifications, a caller implements a
+/// derivation of the RequestSendHandler and supplies an instance of this
+/// derivation to the NameChangeSender constructor. This "registers" the
+/// handler with the sender.
+///
+/// To begin sending, the caller invokes the listener's startSending()
+/// method, passing in an IOService instance.  This in turn will pass the
+/// IOService into the virtual method, open().  The open method is where the
+/// sender derivation performs the steps necessary to prepare its IO sink for
+/// output (e.g. opening a socket, connecting to a database).  At this point,
+/// the sender is ready to send messages.
+///
+/// In order to send a request, the application layer invokes the sender
+/// method, sendRequest(), passing in the NameChangeRequest to send.  This
+/// method places the request onto the back of the send queue, and then invokes
+/// the sender method, sendNext().
+///
+/// If there is already a send in progress when sendNext() is called, the method
+/// will return immediately rather than initiate the next send.  This is to
+/// ensure that sends are processed sequentially.
+///
+/// If there is not a send in progress and the send queue is not empty,
+/// the sendNext method will pass the NCR at the front of the send queue into
+/// the virtual doSend() method.
+///
+/// The sender derivation uses this doSend() method to instigate an IO layer
+/// asynchronous send with its IO layer callback to handle send events from its
+/// IO sink.
+///
+/// As stated earlier, the derivation's IO layer callback MUST invoke the
+/// application layer handler registered with the sender.  This is done by
+/// passing in  a status indicating the outcome of the send into the sender's
+/// invokeSendHandler method. This is the mechanism by which the sender's
+/// caller is handed outbound notifications.
+
+/// After invoking the application layer handler, the invokeSendHandler method
+/// will call the sendNext() method to initiate the next send.  This ensures
+/// that requests continue to dequeue and ship.
+///
+class NameChangeSender {
+public:
+
+    /// @brief Defines the type used for the request send queue.
+    typedef std::deque<NameChangeRequestPtr> SendQueue;
+
+    /// @brief Defines a default maximum number of entries in the send queue.
+    static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+    /// @brief Defines the outcome of an asynchronous NCR send.
+    enum Result {
+        SUCCESS,
+        TIME_OUT,
+        STOPPED,
+        ERROR
+    };
+
+    /// @brief Abstract class for defining application layer send callbacks.
+    ///
+    /// Applications which will send NameChangeRequests must provide a
+    /// derivation of this class to the sender constructor in order to
+    /// receive send outcome notifications.
+    class RequestSendHandler {
+      public:
+        /// @brief Function operator implementing a NCR send callback.
+        ///
+        /// This method allows the application to receive the outcome of
+        /// each send.  It is intended to function as a hand off of information
+        /// and should probably not be time-consuming.
+        ///
+        /// @param result contains that send outcome status.
+        /// @param ncr is a pointer to the NameChangeRequest that was
+        /// delivered (or attempted).
+        ///
+        /// @throw This method MUST NOT throw.
+        virtual void operator ()(const Result result,
+                                 NameChangeRequestPtr& ncr) = 0;
+    };
+
+    /// @brief Constructor
+    ///
+    /// @param send_handler is a pointer the application layer handler to be
+    /// invoked each time a NCR send attempt completes.
+    /// @param send_queue_max is the maximum number of entries allowed in the
+    /// send queue.  Once the maximum number is reached, all calls to
+    /// sendRequest will fail with an exception.
+    NameChangeSender(RequestSendHandler& send_handler,
+            size_t send_queue_max = MAX_QUEUE_DEFAULT);
+
+    /// @brief Destructor
+    virtual ~NameChangeSender() {
+    }
+
+    /// @brief Prepares the IO for transmission.
+    ///
+    /// Calls the derivation's open implementation to initialize the IO layer
+    /// sink for sending outbound requests.
+    ///
+    /// @param io_service is the IOService that will handle IO event processing.
+    ///
+    /// @throw NcrSenderError if the sender is already "sending" or
+    /// NcrSenderOpenError if the open fails.
+    void startSending(isc::asiolink::IOService & io_service);
+
+    /// @brief Closes the IO sink and stops send logic.
+    ///
+    /// Calls the derivation's implementation of close and marks the state
+    /// as not sending.
+    void stopSending();
+
+    /// @brief Queues the given request to be sent.
+    ///
+    /// The given request is placed at the back of the send queue and then
+    /// sendNext is invoked.
+
+    ///
+    /// @param ncr is the NameChangeRequest to send.
+    ///
+    /// @throw NcrSenderError if the sender is not in sending state or
+    /// the request is empty; NcrSenderQueueFull if the send queue has reached
+    /// capacity.
+    void sendRequest(NameChangeRequestPtr& ncr);
+
+    /// @brief Dequeues and sends the next request on the send queue.
+    ///
+    /// If there is already a send in progress just return. If there is not
+    /// a send in progress and the send queue is not empty the grab the next
+    /// message on the front of the queue and call doSend().
+    ///
+    void sendNext();
+
+    /// @brief Calls the NCR send completion handler registered with the
+    /// sender.
+    ///
+    /// This is the hook by which the sender's caller's NCR send completion
+    /// handler is called.  This method MUST be invoked by the derivation's
+    /// implementation of doSend.   Note that if the send was a success,
+    /// the entry at the front of the queue is removed from the queue.
+    /// If not we leave it there so we can retry it.  After we invoke the
+    /// handler we clear the pending ncr value and queue up the next send.
+    ///
+    /// NOTE:
+    /// The handler invoked by this method MUST NOT THROW. The handler is
+    /// application level logic and should trap and handle any errors at
+    /// that level, rather than throw exceptions.  If IO errors have occurred
+    /// prior to invoking the handler, they are expressed in terms a failed
+    /// result being passed to the handler.  Therefore any exceptions at the
+    /// handler level are application issues and should be dealt with at that
+    /// level.
+    ///
+    /// This method does wrap the handler invocation within a try-catch
+    /// block as a fail-safe.  The exception will be logged but the
+    /// send logic will continue.  What this implies is that continued
+    /// operation may or may not succeed as the application has violated
+    /// the interface contract.
+    ///
+    /// @param result contains that send outcome status.
+    void invokeSendHandler(const NameChangeSender::Result result);
+
+    /// @brief Removes the request at the front of the send queue
+    ///
+    /// This method can be used to avoid further retries of a failed
+    /// send. It is provided primarily as a just-in-case measure. Since
+    /// a failed send results in the same request being retried continuously
+    /// this method makes it possible to remove that entry, causing the
+    /// subsequent entry in the queue to be attempted on the next send.
+    /// It is presumed that sends will only fail due to some sort of
+    /// communications issue. In the unlikely event that a request is
+    /// somehow tainted and causes an send failure based on its content,
+    /// this method provides a means to remove th message.
+    void skipNext();
+
+    /// @brief Flushes all entries in the send queue
+    ///
+    /// This method can be used to discard all of the NCRs currently in the
+    /// the send queue.  Note it may not be called while the sender is in
+    /// the sending state.
+    /// @throw NcrSenderError if called and sender is in sending state.
+    void clearSendQueue();
+
+    /// @brief Abstract method which opens the IO sink for transmission.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// prepare the IO sink to send requests.
+    ///
+    /// @param io_service is the IOService that process IO events.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+    /// @brief Abstract method which closes the IO sink.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// "close" the IO sink.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void close() = 0;
+
+    /// @brief Initiates an IO layer asynchronous send
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// initiate an asynchronous send through the IO sink of the given NCR.
+    ///
+    /// @param ncr is a pointer to the NameChangeRequest to send.
+    /// derivation's IO layer handler as the IO completion callback.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void doSend(NameChangeRequestPtr& ncr) = 0;
+
+    /// @brief Returns true if the sender is in send mode, false otherwise.
+    ///
+    /// A true value indicates that the IO sink has been opened successfully,
+    /// and that send loop logic is active.
+    bool amSending() const {
+        return (sending_);
+    }
+
+    /// @brief Returns true when a send is in progress.
+    ///
+    /// A true value indicates that a request is actively in the process of
+    /// being delivered.
+    bool isSendInProgress() const {
+        return ((ncr_to_send_) ? true : false);
+    }
+
+    /// @brief Returns the maximum number of entries allowed in the send queue.
+    size_t getQueueMaxSize() const  {
+        return (send_queue_max_);
+    }
+
+    /// @brief Returns the number of entries currently in the send queue.
+    size_t getQueueSize() const {
+        return (send_queue_.size());
+    }
+
+private:
+    /// @brief Sets the sending indicator to the given value.
+    ///
+    /// Note, this method is private as it is used the base class is solely
+    /// responsible for managing the state.
+    ///
+    /// @param value is the new value to assign to the indicator.
+    void setSending(bool value) {
+            sending_ = value;
+    }
+
+    /// @brief Boolean indicator which tracks sending status.
+    bool sending_;
+
+    /// @brief A pointer to regisetered send completion handler.
+    RequestSendHandler& send_handler_;
+
+    /// @brief Maximum number of entries permitted in the send queue.
+    size_t send_queue_max_;
+
+    /// @brief Queue of the requests waiting to be sent.
+    SendQueue send_queue_;
+
+    /// @brief Pointer to the request which is in the process of being sent.
+    NameChangeRequestPtr ncr_to_send_;
+};
+
+/// @brief Defines a smart pointer to an instance of a sender.
+typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr;
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif

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

@@ -455,5 +455,23 @@ NameChangeRequest::toText() const {
     return (stream.str());
 }
 
+bool
+NameChangeRequest::operator == (const NameChangeRequest& other) {
+    return ((change_type_ == other.change_type_) &&
+            (forward_change_ == other.forward_change_) &&
+            (reverse_change_ == other.reverse_change_) &&
+            (fqdn_ == other.fqdn_) &&
+            (ip_address_ == other.ip_address_) &&
+            (dhcid_ == other.dhcid_) &&
+            (lease_expires_on_ == other.lease_expires_on_) &&
+            (lease_length_ == other.lease_length_));
+}
+
+bool
+NameChangeRequest::operator != (const NameChangeRequest& other) {
+    return (!(*this == other));
+}
+
+
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 46 - 33
src/bin/d2/ncr_msg.h

@@ -73,13 +73,13 @@ public:
     /// 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
+    /// @throw 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.
+    /// @return a string containing a contiguous stream of digits.
     std::string toStr() const;
 
     /// @brief Sets the DHCID value based on the given string.
@@ -88,17 +88,27 @@ public:
     /// 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
+    /// @throw 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.
+    /// @return a reference to the vector.
     const std::vector<uint8_t>& getBytes() {
         return (bytes_);
     }
 
+    /// @brief Compares two D2Dhcids for equality
+    bool operator==(const D2Dhcid& other) const {
+        return (this->bytes_ == other.bytes_);
+    }
+
+    /// @brief Compares two D2Dhcids for inequality
+    bool operator!=(const D2Dhcid other) const {
+        return (this->bytes_ != other.bytes_);
+}
+
 private:
     /// @brief Storage for the DHCID value in unsigned bytes.
     std::vector<uint8_t> bytes_;
@@ -163,9 +173,9 @@ public:
     /// @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
+    /// @return a pointer to the new NameChangeRequest
     ///
-    /// @throw throws NcrMessageError if an error occurs creating new
+    /// @throw NcrMessageError if an error occurs creating new
     /// request.
     static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
                                            isc::util::InputBuffer& buffer);
@@ -194,15 +204,15 @@ public:
     ///
     /// @param json is a string containing the JSON text
     ///
-    /// @return returns a pointer to the new NameChangeRequest
+    /// @return a pointer to the new NameChangeRequest
     ///
-    /// @throw throws NcrMessageError if an error occurs creating new request.
+    /// @throw 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
+    /// @return a string containing the JSON rendition of the request
     std::string toJSON() const;
 
     /// @brief Validates the content of a populated request.  This method is
@@ -221,13 +231,13 @@ public:
     /// 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
+    /// @throw NcrMessageError if the request content violates any
     /// of the validation rules.
     void validateContent();
 
     /// @brief Fetches the request change type.
     ///
-    /// @return returns the change type
+    /// @return the change type
     NameChangeType getChangeType() const {
         return (change_type_);
     }
@@ -241,13 +251,13 @@ public:
     ///
     /// @param element is an integer Element containing the change type value.
     ///
-    /// @throw throws a NcrMessageError if the element is not an integer
+    /// @throw 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.
+    /// @return a true if the forward change flag is true.
     bool isForwardChange() const {
         return (forward_change_);
     }
@@ -263,13 +273,13 @@ public:
     /// @param element is a boolean Element containing the forward change flag
     /// value.
     ///
-    /// @throw throws a NcrMessageError if the element is not a boolean
+    /// @throw 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.
+    /// @return a true if the reverse change flag is true.
     bool isReverseChange() const {
         return (reverse_change_);
     }
@@ -285,13 +295,13 @@ public:
     /// @param element is a boolean Element containing the reverse change flag
     /// value.
     ///
-    /// @throw throws a NcrMessageError if the element is not a boolean
+    /// @throw 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
+    /// @return a string containing the FQDN
     const std::string getFqdn() const {
         return (fqdn_);
     }
@@ -305,13 +315,13 @@ public:
     ///
     /// @param element is a string Element containing the FQDN
     ///
-    /// @throw throws a NcrMessageError if the element is not a string
+    /// @throw 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
+    /// @return a string containing the IP address
     const std::string& getIpAddress() const {
         return (ip_address_);
     }
@@ -325,13 +335,13 @@ public:
     ///
     /// @param element is a string Element containing the IP address
     ///
-    /// @throw throws a NcrMessageError if the element is not a string
+    /// @throw 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
+    /// @return a reference to the request's D2Dhcid
     const D2Dhcid& getDhcid() const {
         return (dhcid_);
     }
@@ -342,7 +352,7 @@ public:
     /// 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
+    /// @throw NcrMessageError if the input data contains non-digits
     /// or there is an odd number of digits.
     void setDhcid(const std::string& value);
 
@@ -351,13 +361,13 @@ public:
     /// @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
+    /// @throw 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
     ///
-    /// @return returns the lease expiration as the number of seconds since
+    /// @return the lease expiration as the number of seconds since
     /// the (00:00:00 January 1, 1970)
     uint64_t getLeaseExpiresOn() const {
         return (lease_expires_on_);
@@ -372,7 +382,7 @@ public:
     /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
     /// NOTE This is always UTC time.
     ///
-    /// @return returns a ISO date-time string of the lease expiration.
+    /// @return a ISO date-time string of the lease expiration.
     std::string getLeaseExpiresOnStr() const;
 
     /// @brief Sets the lease expiration based on the given string.
@@ -385,20 +395,20 @@ public:
     /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
     /// NOTE This is always UTC time.
     ///
-    /// @throw throws a NcrMessageError if the ISO string is invalid.
+    /// @throw 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 a date-time string.
     ///
-    /// @throw throws a NcrMessageError if the element is not a string
+    /// @throw NcrMessageError if the element is not a string
     /// Element, or if the element value is an invalid date-time string.
     void setLeaseExpiresOn(isc::data::ConstElementPtr element);
 
     /// @brief Fetches the request lease length.
     ///
-    /// @return returns an integer containing the lease length
+    /// @return an integer containing the lease length
     uint32_t getLeaseLength() const {
         return (lease_length_);
     }
@@ -412,13 +422,13 @@ public:
     ///
     /// @param element is a integer Element containing the lease length
     ///
-    /// @throw throws a NcrMessageError if the element is not a string
+    /// @throw 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
+    /// @return the request status as a NameChangeStatus
     NameChangeStatus getStatus() const {
         return (status_);
     }
@@ -434,8 +444,8 @@ public:
     /// @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
+    /// @return a pointer to the element if located
+    /// @throw NcrMessageError if the element cannot be found within
     /// the map
     isc::data::ConstElementPtr getElement(const std::string& name,
                                           const ElementMap& element_map) const;
@@ -443,9 +453,12 @@ public:
     /// @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.
+    /// @return a string containing the text.
     std::string toText() const;
 
+    bool operator == (const NameChangeRequest& b);
+    bool operator != (const NameChangeRequest& b);
+
 private:
     /// @brief Denotes the type of this change as either an Add or a Remove.
     NameChangeType change_type_;

+ 299 - 0
src/bin/d2/ncr_udp.cc

@@ -0,0 +1,299 @@
+// 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/d2_log.h>
+#include <d2/ncr_udp.h>
+
+#include <asio/ip/udp.hpp>
+#include <asio/error_code.hpp>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace d2 {
+
+//*************************** UDPCallback ***********************
+UDPCallback::UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+                          UDPEndpointPtr& data_source,
+                          const UDPCompletionHandler& handler)
+    : handler_(handler), data_(new Data(buffer, buf_size, data_source)) {
+    if (handler.empty()) {
+        isc_throw(NcrUDPError, "UDPCallback - handler can't be null");
+    }
+
+    if (!buffer) {
+        isc_throw(NcrUDPError, "UDPCallback - buffer can't be null");
+    }
+}
+
+void
+UDPCallback::operator ()(const asio::error_code error_code,
+                         const size_t bytes_transferred) {
+
+    // Save the result state and number of bytes transferred.
+    setErrorCode(error_code);
+    setBytesTransferred(bytes_transferred);
+
+    // Invoke the NameChangeRequest layer completion handler.
+    // First argument is a boolean indicating success or failure.
+    // The second is a pointer to "this" callback object. By passing
+    // ourself in, we make all of the service related data available
+    // to the completion handler.
+    handler_(!error_code, this);
+}
+
+void
+UDPCallback::putData(const uint8_t* src, size_t len) {
+    if (!src) {
+        isc_throw(NcrUDPError, "UDPCallback putData, data source is NULL");
+    }
+
+    if (len > data_->buf_size_) {
+        isc_throw(NcrUDPError, "UDPCallback putData, data length too large");
+    }
+
+    memcpy (data_->buffer_.get(), src, len);
+    data_->put_len_ = len;
+}
+
+
+//*************************** NameChangeUDPListener ***********************
+NameChangeUDPListener::
+NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+                      const uint32_t port, NameChangeFormat format,
+                      RequestReceiveHandler& ncr_recv_handler,
+                      const bool reuse_address)
+    : NameChangeListener(ncr_recv_handler), ip_address_(ip_address),
+      port_(port), format_(format), reuse_address_(reuse_address) {
+    // Instantiate the receive callback.  This gets passed into each receive.
+    // Note that the callback constructor is passed an instance method
+    // pointer to our completion handler method, receiveCompletionHandler.
+    RawBufferPtr buffer(new uint8_t[RECV_BUF_MAX]);
+    UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+    recv_callback_.reset(new
+                         UDPCallback(buffer, RECV_BUF_MAX, data_source,
+                                     boost::bind(&NameChangeUDPListener::
+                                     receiveCompletionHandler, this, _1, _2)));
+}
+
+NameChangeUDPListener::~NameChangeUDPListener() {
+    // Clean up.
+    stopListening();
+}
+
+void
+NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
+    // create our endpoint and bind the the low level socket to it.
+    isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+
+    // Create the low level socket.
+    try {
+        asio_socket_.reset(new asio::ip::udp::
+                           socket(io_service.get_io_service(),
+                                  (ip_address_.isV4() ? asio::ip::udp::v4() :
+                                   asio::ip::udp::v6())));
+
+        // Set the socket option to reuse addresses if it is enabled.
+        if (reuse_address_) {
+            asio_socket_->set_option(asio::socket_base::reuse_address(true));
+        }
+
+        // Bind the low level socket to our endpoint.
+        asio_socket_->bind(endpoint.getASIOEndpoint());
+    } catch (asio::system_error& ex) {
+        isc_throw (NcrUDPError, ex.code().message());
+    }
+
+    // Create the asiolink socket from the low level socket.
+    socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+}
+
+
+void
+NameChangeUDPListener::doReceive() {
+    // Call the socket's asychronous receiving, passing ourself in as callback.
+    RawBufferPtr recv_buffer = recv_callback_->getBuffer();
+    socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
+                          0, recv_callback_->getDataSource().get(),
+                          *recv_callback_);
+}
+
+void
+NameChangeUDPListener::close() {
+    // Whether we think we are listening or not, make sure we aren't.
+    // Since we are managing our own socket, we need to cancel and close
+    // it ourselves.
+    if (asio_socket_) {
+        try {
+            asio_socket_->cancel();
+            asio_socket_->close();
+        } catch (asio::system_error& ex) {
+            // It is really unlikely that this will occur.
+            // If we do reopen later it will be with a new socket instance.
+            // Repackage exception as one that is conformant with the interface.
+            isc_throw (NcrUDPError, ex.code().message());
+        }
+    }
+}
+
+void
+NameChangeUDPListener::receiveCompletionHandler(const bool successful,
+                                                const UDPCallback *callback) {
+    NameChangeRequestPtr ncr;
+    Result result = SUCCESS;
+
+    if (successful) {
+        // Make an InputBuffer from our internal array
+        isc::util::InputBuffer input_buffer(callback->getData(),
+                                            callback->getBytesTransferred());
+
+        try {
+            ncr = NameChangeRequest::fromFormat(format_, input_buffer);
+        } catch (const NcrMessageError& ex) {
+            // log it and go back to listening
+            LOG_ERROR(dctl_logger, DHCP_DDNS_INVALID_NCR).arg(ex.what());
+
+            // Queue up the next recieve.
+            doReceive();
+            return;
+        }
+    } else {
+        asio::error_code error_code = callback->getErrorCode();
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+                  .arg(error_code.message());
+        result = ERROR;
+    }
+
+    // Call the application's registered request receive handler.
+    invokeRecvHandler(result, ncr);
+}
+
+
+//*************************** NameChangeUDPSender ***********************
+
+NameChangeUDPSender::
+NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+                    const uint32_t port,
+                    const isc::asiolink::IOAddress& server_address,
+                    const uint32_t server_port, const NameChangeFormat format,
+                    RequestSendHandler& ncr_send_handler,
+                    const size_t send_que_max, const bool reuse_address)
+    : NameChangeSender(ncr_send_handler, send_que_max),
+      ip_address_(ip_address), port_(port), server_address_(server_address),
+      server_port_(server_port), format_(format),
+      reuse_address_(reuse_address) {
+    // Instantiate the send callback.  This gets passed into each send.
+    // Note that the callback constructor is passed the an instance method
+    // pointer to our completion handler, sendCompletionHandler.
+    RawBufferPtr buffer(new uint8_t[SEND_BUF_MAX]);
+    UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+    send_callback_.reset(new UDPCallback(buffer, SEND_BUF_MAX, data_source,
+                                         boost::bind(&NameChangeUDPSender::
+                                         sendCompletionHandler, this,
+                                         _1, _2)));
+}
+
+NameChangeUDPSender::~NameChangeUDPSender() {
+    // Clean up.
+    stopSending();
+}
+
+void
+NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
+    // create our endpoint and bind the the low level socket to it.
+    isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+
+    // Create the low level socket.
+    try {
+        asio_socket_.reset(new asio::ip::udp::
+                           socket(io_service.get_io_service(),
+                                  (ip_address_.isV4() ? asio::ip::udp::v4() :
+                                   asio::ip::udp::v6())));
+
+        // Set the socket option to reuse addresses if it is enabled.
+        if (reuse_address_) {
+            asio_socket_->set_option(asio::socket_base::reuse_address(true));
+        }
+
+        // Bind the low leve socket to our endpoint.
+        asio_socket_->bind(endpoint.getASIOEndpoint());
+    } catch (asio::system_error& ex) {
+        isc_throw (NcrUDPError, ex.code().message());
+    }
+
+    // Create the asiolink socket from the low level socket.
+    socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+
+    // Create the server endpoint
+    server_endpoint_.reset(new isc::asiolink::
+                           UDPEndpoint(server_address_.getAddress(),
+                                       server_port_));
+
+    send_callback_->setDataSource(server_endpoint_);
+}
+
+void
+NameChangeUDPSender::close() {
+    // Whether we think we are sending or not, make sure we aren't.
+    // Since we are managing our own socket, we need to cancel and close
+    // it ourselves.
+    if (asio_socket_) {
+        try {
+            asio_socket_->cancel();
+            asio_socket_->close();
+        } catch (asio::system_error& ex) {
+            // It is really unlikely that this will occur.
+            // If we do reopen later it will be with a new socket instance.
+            // Repackage exception as one that is conformant with the interface.
+            isc_throw (NcrUDPError, ex.code().message());
+        }
+    }
+}
+
+void
+NameChangeUDPSender::doSend(NameChangeRequestPtr& ncr) {
+    // Now use the NCR to write JSON to an output buffer.
+    isc::util::OutputBuffer ncr_buffer(SEND_BUF_MAX);
+    ncr->toFormat(format_, ncr_buffer);
+
+    // Copy the wire-ized request to callback.  This way we know after
+    // send completes what we sent (or attempted to send).
+    send_callback_->putData(static_cast<const uint8_t*>(ncr_buffer.getData()),
+                            ncr_buffer.getLength());
+
+    // Call the socket's asychronous send, passing our callback
+    socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
+                       send_callback_->getDataSource().get(), *send_callback_);
+}
+
+void
+NameChangeUDPSender::sendCompletionHandler(const bool successful,
+                                           const UDPCallback *send_callback) {
+    Result result;
+    if (successful) {
+        result = SUCCESS;
+    }
+    else {
+        // On a failure, log the error and set the result to ERROR.
+        asio::error_code error_code = send_callback->getErrorCode();
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+                  .arg(error_code.message());
+
+        result = ERROR;
+    }
+
+    // Call the application's registered request send handler.
+    invokeSendHandler(result);
+}
+}; // end of isc::d2 namespace
+}; // end of isc namespace

+ 562 - 0
src/bin/d2/ncr_udp.h

@@ -0,0 +1,562 @@
+// 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_UDP_LISTENER_H
+#define NCR_UDP_LISTENER_H
+
+/// @file ncr_udp.h
+/// @brief This file provides UDP socket based implementation for sending and
+/// receiving NameChangeRequests
+///
+/// These classes are derived from the abstract classes, NameChangeListener
+/// and NameChangeSender (see ncr_io.h).
+///
+/// The following discussion will refer to three layers of communications:
+///
+///    * Application layer - This is the business layer which needs to
+///    transport NameChangeRequests, and is unaware of the means by which
+///    they are transported.
+///
+///    * IO layer - This is the low-level layer that is directly responsible
+///    for sending and receiving data asynchronously and is supplied through
+///    other libraries.  This layer is largely unaware of the nature of the
+///    data being transmitted.  In other words, it doesn't know beans about
+///    NCRs.
+///
+///    * NameChangeRequest layer - This is the layer which acts as the
+///    intermediary between the Application layer and the IO layer.  It must
+///    be able to move NameChangeRequests to the IO layer as raw data and move
+///    raw data from the IO layer in the Application layer as
+///    NameChangeRequests.
+///
+/// This file defines NameChangeUDPListener class for receiving NCRs, and
+/// NameChangeUDPSender for sending NCRs.
+///
+/// Both the listener and sender implementations utilize the same underlying
+/// construct to move NCRs to and from a UDP socket. This construct consists
+/// of a set of classes centered around isc::asiolink::UDPSocket.  UDPSocket
+/// is a templated class that supports asio asynchronous event processing; and
+/// which accepts as its parameter, the name of a callback class.
+///
+/// The asynchronous services provided by UDPSocket typically accept a buffer
+/// for transferring data (either in or out depending on the service direction)
+/// and an object which supplies a callback to invoke upon completion of the
+/// service.
+///
+/// The callback class must provide an operator() with the following signature:
+/// @code
+///    void operator ()(const asio::error_code error_code,
+///                     const size_t bytes_transferred);
+/// @endcode
+///
+/// Upon completion of the service, the callback instance's operator() is
+/// invoked by the asio layer.  It is given both a outcome result and the
+/// number of bytes either read or written, to or from the buffer supplied
+/// to the service.
+///
+/// Typically, an asiolink based implementation would simply implement the
+/// callback operator directly.  However, the nature of the asiolink library
+/// is such that the callback object may be copied several times during course
+/// of a service invocation.  This implies that any class being used as a
+/// callback class must be copyable.  This is not always desirable.  In order
+/// to separate the callback class from the NameChangeRequest, the construct
+/// defines the UDPCallback class for use as a copyable, callback object.
+///
+/// The UDPCallback class provides the asiolink layer callback operator(),
+/// which is invoked by the asiolink layer upon service completion.  It
+/// contains:
+///    * a pointer to the transfer buffer
+///    * the capacity of the transfer buffer
+///    * a IO layer outcome result
+///    * the number of bytes transferred
+///    * a method pointer to a NameChangeRequest layer completion handler
+///
+/// This last item, is critical. It points to an instance method that
+/// will be invoked by the UDPCallback operator.  This provides access to
+/// the outcome of the service call to the NameChangeRequest layer without
+/// that layer being used as the actual callback object.
+///
+/// The completion handler method signature is codified in the typedef,
+/// UDPCompletionHandler, and must be as follows:
+///
+/// @code
+///     void(const bool, const UDPCallback*)
+/// @endcode
+///
+/// Note that is accepts two parameters.  The first is a boolean indicator
+/// which indicates if the service call completed successfully or not.  The
+/// second is a pointer to the callback object invoked by the IOService upon
+/// completion of the service.  The callback instance will contain all of the
+/// pertinent information about the invocation and outcome of the service.
+///
+/// Using the contents of the callback, it is the responsibility of the
+/// UDPCompletionHandler to interpret the results of the service invocation and
+/// pass the interpretation to the application layer via either
+/// NameChangeListener::invokeRecvHandler in the case of the UDP listener, or
+/// NameChangeSender::invokeSendHandler in the case of UDP sender.
+///
+#include <asio.hpp>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <d2/ncr_io.h>
+#include <util/buffer.h>
+
+#include <boost/shared_array.hpp>
+
+/// responsibility of the completion handler to perform the steps necessary
+/// to interpret the raw data provided by the service outcome.   The
+/// UDPCallback operator implementation is mostly a pass through.
+///
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown when a UDP level exception occurs.
+class NcrUDPError : public isc::Exception {
+public:
+    NcrUDPError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+class UDPCallback;
+/// @brief Defines a function pointer for NameChangeRequest completion handlers.
+typedef boost::function<void(const bool, const UDPCallback*)>
+          UDPCompletionHandler;
+
+/// @brief Defines a dynamically allocated shared array.
+typedef boost::shared_array<uint8_t> RawBufferPtr;
+
+typedef boost::shared_ptr<asiolink::UDPEndpoint> UDPEndpointPtr;
+
+/// @brief Implements the callback class passed into UDPSocket calls.
+///
+/// It serves as the link between the asiolink::UDPSocket asynchronous services
+/// and the NameChangeRequest layer. The class provides the asiolink layer
+/// callback operator(), which is invoked by the asiolink layer upon service
+/// completion.  It contains all of the data pertinent to both the invocation
+/// and completion of a service, as well as a pointer to NameChangeRequest
+/// layer completion handler to invoke.
+///
+class UDPCallback {
+
+public:
+    /// @brief Container class which stores service invocation related data.
+    ///
+    /// Because the callback object may be copied numerous times during the
+    /// course of service invocation, it does not directly contain data values.
+    /// Rather it will retain a shared pointer to an instance of this structure
+    /// thus ensuring that all copies of the callback object, ultimately refer
+    /// to the same data values.
+    struct Data {
+
+        /// @brief Constructor
+        ///
+        /// @param buffer is a pointer to the data transfer buffer. This is
+        /// the buffer data will be written to on a read, or read from on a
+        /// send.
+        /// @param buf_size is the capacity of the buffer
+        /// @param data_source storage for UDP endpoint which supplied the data
+        Data(RawBufferPtr& buffer, const size_t buf_size,
+             UDPEndpointPtr& data_source)
+            : buffer_(buffer), buf_size_(buf_size), data_source_(data_source),
+              put_len_(0), error_code_(), bytes_transferred_(0) {
+        };
+
+        /// @brief A pointer to the data transfer buffer.
+        RawBufferPtr buffer_;
+
+        /// @brief Storage capacity of the buffer.
+        size_t buf_size_;
+
+        /// @brief The UDP endpoint that is the origin of the data transferred.
+        UDPEndpointPtr data_source_;
+
+        /// @brief Stores this size of the data within the buffer when written
+        /// there manually. (See UDPCallback::putData()) .
+        size_t put_len_;
+
+        /// @brief Stores the IO layer result code of the completed IO service.
+        asio::error_code error_code_;
+
+        /// @brief Stores the number of bytes transferred by completed IO
+        /// service.
+        /// For a read it is the number of bytes written into the
+        /// buffer.  For a write it is the number of bytes read from the
+        /// buffer.
+        size_t bytes_transferred_;
+
+    };
+
+    /// @brief Used as the callback object for UDPSocket services.
+    ///
+    /// @param buffer is a pointer to the data transfer buffer. This is
+    /// the buffer data will be written to on a read, or read from on a
+    /// send.
+    /// @param buf_size is the capacity of the buffer
+    /// @param data_source storage for UDP endpoint which supplied the data
+    /// @param handler is a method pointer to the completion handler that
+    /// is to be called by the operator() implementation.
+    ///
+    /// @throw NcrUDPError if either the handler or buffer pointers
+    /// are invalid.
+    UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+                 UDPEndpointPtr& data_source,
+                 const UDPCompletionHandler& handler);
+
+    /// @brief Operator that will be invoked by the asiolink layer.
+    ///
+    /// @param error_code is the IO layer result code of the
+    /// completed IO service.
+    /// @param bytes_transferred is the number of bytes transferred by
+    /// completed IO.
+    /// For a read it is the number of bytes written into the
+    /// buffer.  For a write it is the number of bytes read from the
+    /// buffer.
+    void operator ()(const asio::error_code error_code,
+                     const size_t bytes_transferred);
+
+    /// @brief Returns the number of bytes transferred by the completed IO
+    /// service.
+    ///
+    /// For a read it is the number of bytes written into the
+    /// buffer.  For a write it is the number of bytes read from the
+    /// buffer.
+    size_t getBytesTransferred() const {
+        return (data_->bytes_transferred_);
+    }
+
+    /// @brief Sets the number of bytes transferred.
+    ///
+    /// @param value is the new value to assign to bytes transferred.
+    void setBytesTransferred(const size_t value) {
+        data_->bytes_transferred_ = value;
+    }
+
+    /// @brief Returns the completed IO layer service outcome status.
+    asio::error_code getErrorCode() const {
+        return (data_->error_code_);
+    }
+
+    /// @brief Sets the completed IO layer service outcome status.
+    ///
+    /// @param value is the new value to assign to outcome status.
+    void setErrorCode(const asio::error_code value) {
+        data_->error_code_  = value;
+    }
+
+    /// @brief Returns the data transfer buffer.
+    RawBufferPtr getBuffer() const {
+        return (data_->buffer_);
+    }
+
+    /// @brief Returns the data transfer buffer capacity.
+    size_t getBufferSize() const {
+        return (data_->buf_size_);
+    }
+
+    /// @brief Returns a pointer the data transfer buffer content.
+    const uint8_t* getData() const {
+        return (data_->buffer_.get());
+    }
+
+    /// @brief Copies data into the data transfer buffer.
+    ///
+    /// Copies the given number of bytes from the given source buffer
+    /// into the data transfer buffer, and updates the value of put length.
+    /// This method may be used when performing sends to make a copy of
+    /// the "raw data" that was shipped (or attempted) accessible to the
+    /// upstream callback.
+    ///
+    /// @param src is a pointer to the data source from which to copy
+    /// @param len is the number of bytes to copy
+    ///
+    /// @throw NcrUDPError if the number of bytes to copy exceeds
+    /// the buffer capacity or if the source pointer is invalid.
+    void putData(const uint8_t* src, size_t len);
+
+    /// @brief Returns the number of bytes manually written into the
+    /// transfer buffer.
+    size_t getPutLen() const {
+        return (data_->put_len_);
+    }
+
+    /// @brief Sets the data source to the given endpoint.
+    ///
+    /// @param endpoint is the new value to assign to data source.
+    void setDataSource(UDPEndpointPtr& endpoint) {
+        data_->data_source_ = endpoint;
+    }
+
+    /// @brief Returns the UDP endpoint that provided the transferred data.
+    const UDPEndpointPtr& getDataSource() {
+        return (data_->data_source_);
+    }
+
+  private:
+    /// @brief NameChangeRequest layer completion handler to invoke.
+    UDPCompletionHandler handler_;
+
+    /// @brief Shared pointer to the service data container.
+    boost::shared_ptr<Data> data_;
+};
+
+/// @brief Convenience type for UDP socket based listener
+typedef isc::asiolink::UDPSocket<UDPCallback> NameChangeUDPSocket;
+
+/// @brief Provides the ability to receive NameChangeRequests via  UDP socket
+///
+/// This class is a derivation of the NameChangeListener which is capable of
+/// receiving NameChangeRequests through a UDP socket.  The caller need only
+/// supply network addressing and a RequestReceiveHandler instance to receive
+/// NameChangeRequests asynchronously.
+class NameChangeUDPListener : public NameChangeListener {
+public:
+    /// @brief Defines the maximum size packet that can be received.
+    static const size_t RECV_BUF_MAX = isc::asiolink::
+                                       UDPSocket<UDPCallback>::MIN_SIZE;
+
+    /// @brief Constructor
+    ///
+    /// @param ip_address is the network address on which to listen
+    /// @param port is the UDP port on which to listen
+    /// @param format is the wire format of the inbound requests. Currently
+    /// only JSON is supported
+    /// @param ncr_recv_handler the receive handler object to notify when
+    /// a receive completes.
+    /// @param reuse_address enables IP address sharing when true
+    /// It defaults to false.
+    ///
+    /// @throw base class throws NcrListenerError if handler is invalid.
+    NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+                          const uint32_t port,
+                          const NameChangeFormat format,
+                          RequestReceiveHandler& ncr_recv_handler,
+                          const bool reuse_address = false);
+
+    /// @brief Destructor.
+    virtual ~NameChangeUDPListener();
+
+    /// @brief Opens a UDP socket using the given IOService.
+    ///
+    /// Creates a NameChangeUDPSocket bound to the listener's ip address
+    /// and port, that is monitored by the given IOService instance.
+    ///
+    /// @param io_service the IOService which will monitor the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void open(isc::asiolink::IOService& io_service);
+
+    /// @brief Closes the UDPSocket.
+    ///
+    /// It first invokes the socket's cancel method which should stop any
+    /// pending read and remove the socket callback from the IOService. It
+    /// then calls the socket's close method to actually close the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void close();
+
+    /// @brief Initiates an asynchronous read on the socket.
+    ///
+    /// Invokes the asyncReceive() method on the socket passing in the
+    /// recv_callback_ member's transfer buffer as the receive buffer, and
+    /// recv_callback_ itself as the callback object.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    void doReceive();
+
+    /// @brief Implements the NameChangeRequest level receive completion
+    /// handler.
+    ///
+    /// This method is invoked by the UPDCallback operator() implementation,
+    /// passing in the boolean success indicator and pointer to itself.
+    ///
+    /// If the indicator denotes success, then the method will attempt to
+    /// to construct a NameChangeRequest from the received data.  If the
+    /// construction was successful, it will send the new NCR to the
+    /// application layer by calling invokeRecvHandler() with a success
+    /// status and a pointer to the new NCR.
+    ///
+    /// If the buffer contains invalid data such that construction fails,
+    /// the method will log the failure and then call doReceive() to start a
+    /// initiate the next receive.
+    ///
+    /// If the indicator denotes failure the method will log the failure and
+    /// notify the application layer by calling invokeRecvHandler() with
+    /// an error status and an empty pointer.
+    ///
+    /// @param successful boolean indicator that should be true if the
+    /// socket receive completed without error, false otherwise.
+    /// @param recv_callback pointer to the callback instance which handled
+    /// the socket receive completion.
+    void receiveCompletionHandler(const bool successful,
+                                  const UDPCallback* recv_callback);
+private:
+    /// @brief IP address on which to listen for requests.
+    isc::asiolink::IOAddress ip_address_;
+
+    /// @brief Port number on which to listen for requests.
+    uint32_t port_;
+
+    /// @brief Wire format of the inbound requests.
+    NameChangeFormat format_;
+
+    /// @brief Low level socket underneath the listening socket
+    boost::shared_ptr<asio::ip::udp::socket> asio_socket_;
+
+    /// @brief NameChangeUDPSocket listening socket
+    boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+    /// @brief Pointer to the receive callback
+    boost::shared_ptr<UDPCallback> recv_callback_;
+
+    /// @brief Flag which enables the reuse address socket option if true.
+    bool reuse_address_;
+
+    ///
+    /// @name Copy and constructor assignment operator
+    ///
+    /// The copy constructor and assignment operator are private to avoid
+    /// potential issues with multiple listeners attempting to share sockets
+    /// and callbacks.
+private:
+    NameChangeUDPListener(const NameChangeUDPListener& source);
+    NameChangeUDPListener& operator=(const NameChangeUDPListener& source);
+    //@}
+};
+
+
+/// @brief Provides the ability to send NameChangeRequests via  UDP socket
+///
+/// This class is a derivation of the NameChangeSender which is capable of
+/// sending NameChangeRequests through a UDP socket.  The caller need only
+/// supply network addressing and a RequestSendHandler instance to send
+/// NameChangeRequests asynchronously.
+class NameChangeUDPSender : public NameChangeSender {
+public:
+
+    /// @brief Defines the maximum size packet that can be sent.
+    static const size_t SEND_BUF_MAX =  NameChangeUDPListener::RECV_BUF_MAX;
+
+    /// @brief Constructor
+    ///
+    /// @param ip_address the IP address from which to send
+    /// @param port the port from which to send
+    /// @param server_address the IP address of the target listener
+    /// @param server_port is the IP port  of the target listener
+    /// @param format is the wire format of the outbound requests.
+    /// @param ncr_send_handler the send handler object to notify when
+    /// when a send completes.
+    /// @param send_que_max sets the maximum number of entries allowed in
+    /// the send queue.
+    /// It defaults to NameChangeSender::MAX_QUEUE_DEFAULT
+    /// @param reuse_address enables IP address sharing when true
+    /// It defaults to false.
+    ///
+    NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+        const uint32_t port, const isc::asiolink::IOAddress& server_address,
+        const uint32_t server_port, const NameChangeFormat format,
+        RequestSendHandler& ncr_send_handler,
+        const size_t send_que_max = NameChangeSender::MAX_QUEUE_DEFAULT,
+        const bool reuse_address = false);
+
+    /// @brief Destructor
+    virtual ~NameChangeUDPSender();
+
+
+    /// @brief Opens a UDP socket using the given IOService.
+    ///
+    /// Creates a NameChangeUDPSocket bound to the sender's IP address
+    /// and port, that is monitored by the given IOService instance.
+    ///
+    /// @param io_service the IOService which will monitor the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void open(isc::asiolink::IOService& io_service);
+
+
+    /// @brief Closes the UDPSocket.
+    ///
+    /// It first invokes the socket's cancel method which should stop any
+    /// pending send and remove the socket callback from the IOService. It
+    /// then calls the socket's close method to actually close the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void close();
+
+    /// @brief Sends a given request asynchronously over the socket
+    ///
+    /// The given NameChangeRequest is converted to wire format and copied
+    /// into the send callback's transfer buffer.  Then the socket's
+    /// asyncSend() method is called, passing in send_callback_ member's
+    /// transfer buffer as the send buffer and the send_callback_ itself
+    /// as the callback object.
+    virtual void doSend(NameChangeRequestPtr& ncr);
+
+    /// @brief Implements the NameChangeRequest level send completion handler.
+    ///
+    /// This method is invoked by the UDPCallback operator() implementation,
+    /// passing in the boolean success indicator and pointer to itself.
+    ///
+    /// If the indicator denotes success, then the method will notify the
+    /// application layer by calling invokeSendHandler() with a success
+    /// status.
+    ///
+    /// If the indicator denotes failure the method will log the failure and
+    /// notify the application layer by calling invokeRecvHandler() with
+    /// an error status.
+    ///
+    /// @param successful boolean indicator that should be true if the
+    /// socket send completed without error, false otherwise.
+    /// @param send_callback pointer to the callback instance which handled
+    /// the socket receive completion.
+    void sendCompletionHandler(const bool successful,
+                               const UDPCallback* send_callback);
+
+private:
+    /// @brief IP address from which to send.
+    isc::asiolink::IOAddress ip_address_;
+
+    /// @brief Port from which to send.
+    uint32_t port_;
+
+    /// @brief IP address of the target listener.
+    isc::asiolink::IOAddress server_address_;
+
+    /// @brief Port of the target listener.
+    uint32_t server_port_;
+
+    /// @brief Wire format of the outbound requests.
+    NameChangeFormat format_;
+
+    /// @brief Low level socket underneath the sending socket.
+    boost::shared_ptr<asio::ip::udp::socket> asio_socket_;
+
+    /// @brief NameChangeUDPSocket sending socket.
+    boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+    /// @brief Endpoint of the target listener.
+    boost::shared_ptr<isc::asiolink::UDPEndpoint> server_endpoint_;
+
+    /// @brief Pointer to the send callback
+    boost::shared_ptr<UDPCallback> send_callback_;
+
+    /// @brief Flag which enables the reuse address socket option if true.
+    bool reuse_address_;
+};
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif

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

@@ -62,7 +62,9 @@ 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 += ../dns_client.cc ../dns_client.h
+d2_unittests_SOURCES += ../ncr_io.cc ../ncr_io.h
 d2_unittests_SOURCES += ../ncr_msg.cc ../ncr_msg.h
+d2_unittests_SOURCES += ../ncr_udp.cc ../ncr_udp.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
@@ -74,6 +76,7 @@ d2_unittests_SOURCES += d2_update_message_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += ncr_unittests.cc
+d2_unittests_SOURCES += ncr_udp_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 498 - 0
src/bin/d2/tests/ncr_udp_unittests.cc

@@ -0,0 +1,498 @@
+// 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 <asiolink/interval_timer.h>
+#include <d2/ncr_io.h>
+#include <d2/ncr_udp.h>
+#include <util/time_utilities.h>
+
+#include <asio/ip/udp.hpp>
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+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\" : \"20130121132405\" , "
+     " \"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\" : \"20130121132405\" , "
+     " \"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\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}"
+};
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleListenHandler : public NameChangeListener::RequestReceiveHandler {
+public:
+    virtual void operator ()(const NameChangeListener::Result,
+                             NameChangeRequestPtr&) {
+    }
+};
+
+/// @brief Tests the NameChangeUDPListener constructors.
+/// This test verifies that:
+/// 1. Given valid parameters, the listener constructor works
+TEST(NameChangeUDPListenerBasicTest, constructionTests) {
+    // Verify the default constructor works.
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = LISTENER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleListenHandler ncr_handler;
+    // Verify that valid constructor works.
+    EXPECT_NO_THROW(NameChangeUDPListener(ip_address, port, FMT_JSON,
+                                          ncr_handler));
+}
+
+/// @brief Tests NameChangeUDPListener starting and stopping listening .
+/// This test verifies that the listener will:
+/// 1. Enter listening state
+/// 2. If in the listening state, does not allow calls to start listening
+/// 3. Exist the listening state
+/// 4. Return to the listening state after stopping
+TEST(NameChangeUDPListenerBasicTest, basicListenTests) {
+    // Verify the default constructor works.
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = LISTENER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleListenHandler ncr_handler;
+
+    NameChangeListenerPtr listener;
+    ASSERT_NO_THROW(listener.reset(
+        new NameChangeUDPListener(ip_address, port, FMT_JSON, ncr_handler)));
+
+    // Verify that we can start listening.
+    EXPECT_NO_THROW(listener->startListening(io_service));
+    EXPECT_TRUE(listener->amListening());
+
+    // Verify that attempting to listen when we already are is an error.
+    EXPECT_THROW(listener->startListening(io_service), NcrListenerError);
+
+    // Verify that we can stop listening.
+    EXPECT_NO_THROW(listener->stopListening());
+    EXPECT_FALSE(listener->amListening());
+
+    // Verify that attempting to stop listening when we are not is ok.
+    EXPECT_NO_THROW(listener->stopListening());
+
+    // Verify that we can re-enter listening.
+    EXPECT_NO_THROW(listener->startListening(io_service));
+    EXPECT_TRUE(listener->amListening());
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+                         NameChangeRequestPtr received_ncr) {
+    return ((sent_ncr && received_ncr) &&
+        (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture for testing NameChangeUDPListener
+class NameChangeUDPListenerTest : public virtual ::testing::Test,
+                                  NameChangeListener::RequestReceiveHandler {
+public:
+    isc::asiolink::IOService io_service_;
+    NameChangeListener::Result result_;
+    NameChangeRequestPtr sent_ncr_;
+    NameChangeRequestPtr received_ncr_;
+    NameChangeListenerPtr listener_;
+    isc::asiolink::IntervalTimer test_timer_;
+
+    /// @brief Constructor
+    //
+    // Instantiates the listener member and the test timer. The timer is used
+    // to ensure a test doesn't go awry and hang forever.
+    NameChangeUDPListenerTest()
+        : io_service_(), result_(NameChangeListener::SUCCESS),
+          test_timer_(io_service_) {
+        isc::asiolink::IOAddress addr(TEST_ADDRESS);
+        listener_.reset(new NameChangeUDPListener(addr, LISTENER_PORT,
+                                              FMT_JSON, *this, true));
+
+        // Set the test timeout to break any running tasks if they hang.
+        test_timer_.setup(boost::bind(&NameChangeUDPListenerTest::
+                                      testTimeoutHandler, this),
+                          TEST_TIMEOUT);
+    }
+
+    virtual ~NameChangeUDPListenerTest(){
+    }
+    
+
+    /// @brief Converts JSON string into an NCR and sends it to the listener.
+    ///
+    void sendNcr(const std::string& msg) {
+        // Build an NCR  from json string. This verifies that the
+        // test string is valid.
+        ASSERT_NO_THROW(sent_ncr_ = NameChangeRequest::fromJSON(msg));
+
+        // Now use the NCR to write JSON to an output buffer.
+        isc::util::OutputBuffer ncr_buffer(1024);
+        ASSERT_NO_THROW(sent_ncr_->toFormat(FMT_JSON, ncr_buffer));
+
+        // Create a UDP socket through which our "sender" will send the NCR.
+        asio::ip::udp::socket
+            udp_socket(io_service_.get_io_service(), asio::ip::udp::v4());
+
+        // Create an endpoint pointed at the listener.
+        asio::ip::udp::endpoint
+            listener_endpoint(asio::ip::address::from_string(TEST_ADDRESS),
+                              LISTENER_PORT);
+
+        // A response message is now ready to send. Send it!
+        // Note this uses a synchronous send so it ships immediately.
+        // If listener isn't in listening mode, it will get missed.
+        udp_socket.send_to(asio::buffer(ncr_buffer.getData(),
+                                     ncr_buffer.getLength()),
+                            listener_endpoint);
+    }
+
+    /// @brief RequestReceiveHandler operator implementation for receiving NCRs.
+    ///
+    /// The fixture acts as the "application" layer.  It derives from
+    /// RequestReceiveHandler and as such implements operator() in order to
+    /// receive NCRs.
+    virtual void operator ()(const NameChangeListener::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR we received
+        result_ = result;
+        received_ncr_ = ncr;
+    }
+    // @brief Handler invoked when test timeout is hit.
+    //
+    // This callback stops all running (hanging) tasks on IO service.
+    void testTimeoutHandler() {
+        io_service_.stop();
+        FAIL() << "Test timeout hit.";
+    }
+};
+
+/// @brief  Tests NameChangeUDPListener ability to receive NCRs.
+/// This test verifies that a listener can enter listening mode and
+/// receive NCRs in wire format on its UDP socket; reconstruct the
+/// NCRs and delivery them to the "application" layer.
+TEST_F(NameChangeUDPListenerTest, basicReceivetest) {
+    // Verify we can enter listening mode.
+    ASSERT_FALSE(listener_->amListening());
+    ASSERT_NO_THROW(listener_->startListening(io_service_));
+    ASSERT_TRUE(listener_->amListening());
+
+    // Iterate over a series of requests, sending and receiving one
+    /// at time.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+    for (int i = 0; i < num_msgs; i++) {
+        // We are not verifying ability to send, so if we can't test is over.
+        ASSERT_NO_THROW(sendNcr(valid_msgs[i]));
+
+        // Execute no more then one event, which should be receive complete.
+        EXPECT_NO_THROW(io_service_.run_one());
+
+        // Verify the "application" status value for a successful complete.
+        EXPECT_EQ(NameChangeListener::SUCCESS, result_);
+
+        // Verify the received request matches the sent request.
+        EXPECT_TRUE(checkSendVsReceived(sent_ncr_, received_ncr_));
+    }
+
+    // Verify we can gracefully stop listening.
+    EXPECT_NO_THROW(listener_->stopListening());
+    EXPECT_FALSE(listener_->amListening());
+}
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleSendHandler : public NameChangeSender::RequestSendHandler {
+public:
+    virtual void operator ()(const NameChangeSender::Result,
+                             NameChangeRequestPtr&) {
+    }
+};
+
+/// @brief Tests the NameChangeUDPSender constructors.
+/// This test verifies that:
+/// 1. Constructing with a max queue size of 0 is not allowed
+/// 2. Given valid parameters, the sender constructor works
+/// 3. Default construction provides default max queue size
+/// 4. Construction with a custom max queue size works
+TEST(NameChangeUDPSenderBasicTest, constructionTests) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = SENDER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Verify that constructing with an queue size of zero is not allowed.
+    EXPECT_THROW(NameChangeUDPSender(ip_address, port,
+        ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError);
+
+    NameChangeSenderPtr sender;
+    // Verify that valid constructor works.
+    EXPECT_NO_THROW(sender.reset(
+                    new NameChangeUDPSender(ip_address, port, ip_address, port,
+                                            FMT_JSON, ncr_handler)));
+
+    // Verify that send queue default max is correct.
+    size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT;
+    EXPECT_EQ(expected, sender->getQueueMaxSize());
+
+    // Verify that constructor with a valid custom queue size works.
+    EXPECT_NO_THROW(sender.reset(
+                    new NameChangeUDPSender(ip_address, port, ip_address, port,
+                                            FMT_JSON, ncr_handler, 100)));
+
+    EXPECT_EQ(100, sender->getQueueMaxSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send functionality
+/// This test verifies that:
+TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = SENDER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Tests are based on a list of messages, get the count now.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+    // Create the sender, setting the queue max equal to the number of
+    // messages we will have in the list.
+    NameChangeUDPSender sender(ip_address, port, ip_address, port,
+                               FMT_JSON, ncr_handler, num_msgs);
+
+    // Verify that we can start sending.
+    EXPECT_NO_THROW(sender.startSending(io_service));
+    EXPECT_TRUE(sender.amSending());
+
+    // Verify that attempting to send when we already are is an error.
+    EXPECT_THROW(sender.startSending(io_service), NcrSenderError);
+
+    // Verify that we can stop sending.
+    EXPECT_NO_THROW(sender.stopSending());
+    EXPECT_FALSE(sender.amSending());
+
+    // Verify that attempting to stop sending when we are not is ok.
+    EXPECT_NO_THROW(sender.stopSending());
+
+    // Verify that we can re-enter sending after stopping.
+    EXPECT_NO_THROW(sender.startSending(io_service));
+    EXPECT_TRUE(sender.amSending());
+
+    // Iterate over a series of messages, sending each one. Since we
+    // do not invoke IOService::run, then the messages should accumulate
+    // in the queue.
+    NameChangeRequestPtr ncr;
+    for (int i = 0; i < num_msgs; i++) {
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        EXPECT_NO_THROW(sender.sendRequest(ncr));
+        // Verify that the queue count increments in step with each send.
+        EXPECT_EQ(i+1, sender.getQueueSize());
+    }
+
+    // Verify that attempting to send an additional message results in a
+    // queue full exception.
+    EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
+
+    // Loop for the number of valid messages and invoke IOService::run_one.
+    // This should send exactly one message and the queue count should
+    // decrement accordingly.
+    for (int i = num_msgs; i > 0; i--) {
+        io_service.run_one();
+        // Verify that the queue count decrements in step with each run.
+        EXPECT_EQ(i-1, sender.getQueueSize());
+    }
+
+    // Verify that the queue is empty.
+    EXPECT_EQ(0, sender.getQueueSize());
+
+    // Verify that we can add back to the queue
+    EXPECT_NO_THROW(sender.sendRequest(ncr));
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // Verify that we can remove the current entry at the front of the queue.
+    EXPECT_NO_THROW(sender.skipNext());
+    EXPECT_EQ(0, sender.getQueueSize());
+
+    // Verify that flushing the queue is not allowed in sending state.
+    EXPECT_THROW(sender.clearSendQueue(), NcrSenderError);
+
+    // Put a message on the queue.
+    EXPECT_NO_THROW(sender.sendRequest(ncr));
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // Verify that we can gracefully stop sending.
+    EXPECT_NO_THROW(sender.stopSending());
+    EXPECT_FALSE(sender.amSending());
+
+    // Verify that the queue is preserved after leaving sending state.
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // Verify that flushing the queue works when not sending.
+    EXPECT_NO_THROW(sender.clearSendQueue());
+    EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class NameChangeUDPTest : public virtual ::testing::Test,
+                          NameChangeListener::RequestReceiveHandler,
+                          NameChangeSender::RequestSendHandler {
+public:
+    isc::asiolink::IOService io_service_;
+    NameChangeListener::Result recv_result_;
+    NameChangeSender::Result send_result_;
+    NameChangeListenerPtr listener_;
+    NameChangeSenderPtr   sender_;
+    isc::asiolink::IntervalTimer test_timer_;
+
+    std::vector<NameChangeRequestPtr> sent_ncrs_;
+    std::vector<NameChangeRequestPtr> received_ncrs_;
+
+    NameChangeUDPTest()
+        : io_service_(), recv_result_(NameChangeListener::SUCCESS),
+          send_result_(NameChangeSender::SUCCESS), test_timer_(io_service_) {
+        isc::asiolink::IOAddress addr(TEST_ADDRESS);
+        // Create our listener instance. Note that reuse_address is true.
+        listener_.reset(
+            new NameChangeUDPListener(addr, LISTENER_PORT, FMT_JSON,
+                                      *this, true));
+
+        // Create our sender instance. Note that reuse_address is true.
+        sender_.reset(
+            new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT,
+                                    FMT_JSON, *this, 100, true));
+
+        // Set the test timeout to break any running tasks if they hang.
+        test_timer_.setup(boost::bind(&NameChangeUDPTest::testTimeoutHandler,
+                                      this),
+                          TEST_TIMEOUT);
+    }
+
+    void reset_results() {
+        sent_ncrs_.clear();
+        received_ncrs_.clear();
+    }
+
+    /// @brief Implements the receive completion handler.
+    virtual void operator ()(const NameChangeListener::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR received.
+        recv_result_ = result;
+        received_ncrs_.push_back(ncr);
+    }
+
+    /// @brief Implements the send completion handler.
+    virtual void operator ()(const NameChangeSender::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR sent.
+        send_result_ = result;
+        sent_ncrs_.push_back(ncr);
+    }
+
+    // @brief Handler invoked when test timeout is hit.
+    //
+    // This callback stops all running (hanging) tasks on IO service.
+    void testTimeoutHandler() {
+        io_service_.stop();
+        FAIL() << "Test timeout hit.";
+    }
+};
+
+/// @brief Uses a sender and listener to test UDP-based NCR delivery
+/// Conducts a "round-trip" test using a sender to transmit a set of valid
+/// NCRs to a listener.  The test verifies that what was sent matches what
+/// was received both in quantity and in content.
+TEST_F (NameChangeUDPTest, roundTripTest) {
+    // Place the listener into listening state.
+    ASSERT_NO_THROW(listener_->startListening(io_service_));
+    EXPECT_TRUE(listener_->amListening());
+
+    // Get the number of messages in the list of test messages.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+    // Place the sender into sending state.
+    ASSERT_NO_THROW(sender_->startSending(io_service_));
+    EXPECT_TRUE(sender_->amSending());
+
+    for (int i = 0; i < num_msgs; i++) {
+        NameChangeRequestPtr ncr;
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        sender_->sendRequest(ncr);
+        EXPECT_EQ(i+1, sender_->getQueueSize());
+    }
+
+    // Execute callbacks until we have sent and received all of messages.
+    while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
+        EXPECT_NO_THROW(io_service_.run_one());
+    }
+
+    // Send queue should be empty.
+    EXPECT_EQ(0, sender_->getQueueSize());
+
+    // We should have the same number of sends and receives as we do messages.
+    ASSERT_EQ(num_msgs, sent_ncrs_.size());
+    ASSERT_EQ(num_msgs, received_ncrs_.size());
+
+    // Verify that what we sent matches what we received.
+    for (int i = 0; i < num_msgs; i++) {
+        EXPECT_TRUE (checkSendVsReceived(sent_ncrs_[i], received_ncrs_[i]));
+    }
+
+    // Verify that we can gracefully stop listening.
+    EXPECT_NO_THROW(listener_->stopListening());
+    EXPECT_FALSE(listener_->amListening());
+
+    // Verify that we can gracefully stop sending.
+    EXPECT_NO_THROW(sender_->stopSending());
+    EXPECT_FALSE(sender_->amSending());
+}
+
+} // end of anonymous namespace