Browse Source

[3008] Initial implementation of classes for sending and
receiving NameChangeRequests for use with DHCP-DDNS. This includes
abstract listener and sender classes, as well as a derivations
supporting traffic over UDP sockets.

New files added to src/bin/d2

ncr_io.h - base classes
ncr_io.cc

ncr_udp.h - UDP derivations
ncr_udp.cc
tests/ncr_udp_unittests.cc

Thomas Markwalder 11 years ago
parent
commit
f683ad20d4

+ 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(),

+ 32 - 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,40 @@ 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 recevied by the application.  Either the format or the content
+of the request is incorret. 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_UDP_LISTEN_CLOSE application encountered an error while closing the UDP socket used to receive NameChangeRequests : %1
+This is an error message that indicates the application was unable to close the
+UDP socket being used to receive NameChangeRequests.  Socket closure may occur
+during the course of error recovery or duing 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_UDP_RECV_ERROR UDP socket receive error while listening for DNS Update requests: %1
+This is an error message indicating that an IO error occured while listening
+over a UDP socket for DNS update requests. in the request. This could indicate
+a network connectivity or system resource issue.
+
+% DHCP_DDNS_NCR_UDP_SEND_CLOSE application encountered an error while closing the UDP socket used to send NameChangeRequests : %1
+This is an error message that indicates the DHCP-DDNS client was unable to close
+the UDP socket being used to send NameChangeRequests.  Socket closure may occur
+during the course of error recovery or duing 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_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.

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

@@ -0,0 +1,217 @@
+// 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(const RequestReceiveHandler*
+                                       recv_handler)
+    : listening_(false), recv_handler_(recv_handler) {
+    if (!recv_handler) {
+        isc_throw(NcrListenerError,
+                      "NameChangeListener ctor - recv_handler cannot be null");
+    }
+};
+
+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_UDP_LISTEN_CLOSE).arg(ex.what());
+    }
+
+    setListening(false);
+}
+
+void
+NameChangeListener::invokeRecvHandler(Result result, NameChangeRequestPtr ncr) {
+    // Call the registered application layer handler.
+    (*(const_cast<RequestReceiveHandler*>(recv_handler_)))(result, ncr);
+
+    // Start the next IO layer asynchronous receive.
+    doReceive();
+};
+
+//************************* NameChangeSender ******************************
+
+NameChangeSender::NameChangeSender(const RequestSendHandler* send_handler,
+                                   size_t send_que_max)
+    : sending_(false), send_handler_(send_handler),
+      send_que_max_(send_que_max) {
+    if (!send_handler) {
+        isc_throw(NcrSenderError,
+                  "NameChangeSender ctor - send_handler cannot be null");
+    }
+
+    // Queue size must be big enough to hold at least 1 entry.
+    if (send_que_max == 0) {
+        isc_throw(NcrSenderError, "NameChangeSender ctor -"
+                  " 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_UDP_SEND_CLOSE).arg(ex.what());
+    }
+    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_que_.size() >= send_que_max_) {
+        isc_throw(NcrSenderQueFull, "send queue has reached maximum capacity:"
+                  << send_que_max_ );
+    }
+
+    // Put it on the queue.
+    send_que_.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_que_.size()) {
+        ncr_to_send_ = send_que_.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(NameChangeSender::Result result) {
+    // @todo reset defense timer
+    if (result == SUCCESS) {
+        // It shipped so pull it off the queue.
+        send_que_.pop_front();
+    }
+
+    // Invoke the completion handler passing in the result and a pointer
+    // the request involved.
+    (*(const_cast<RequestSendHandler*>(send_handler_))) (result, ncr_to_send_);
+
+    // Clear the pending ncr pointer.
+    ncr_to_send_.reset();
+
+    // Set up the next send
+    sendNext();
+};
+
+void
+NameChangeSender::skipNext() {
+    if (send_que_.size()) {
+        // Discards the request at the front of the queue.
+        send_que_.pop_front();
+    }
+}
+
+void
+NameChangeSender::flushSendQue() {
+    if (amSending()) {
+        isc_throw(NcrSenderError, "Cannot flush queue while sending");
+    }
+
+    send_que_.clear();
+}
+
+} // namespace isc::d2
+} // namespace isc

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

@@ -0,0 +1,611 @@
+// 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.
+///
+///    * 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.
+///
+///    * 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.
+///
+/// The abstract classes defined here implement 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 occurs.
+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 ()(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.
+    ///
+    /// @throw throws NcrListenerError if recv_handler is NULL.
+    NameChangeListener(const 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 throws 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.
+    ///
+    /// @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(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.
+    const 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 NcrSenderQueFull : public isc::Exception {
+public:
+    NcrSenderQueFull(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::flushSendQue() 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> SendQue;
+
+    /// @brief Defines a default maximum number of entries in the send queue.
+    static const size_t MAX_QUE_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 ()(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_que_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.
+    ///
+    /// @throw throws NcrSenderError if send_handler is NULL.
+    NameChangeSender(const RequestSendHandler* send_handler,
+            size_t send_que_max = MAX_QUE_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 throws 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 throws NcrSenderError if the sender is not in sending state or
+    /// the request is empty; NcrSenderQueFull 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.
+    ///
+    /// @param result contains that send outcome status.
+    void invokeSendHandler(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 flush 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 throws NcrSenderError if called and sender is in sending state.
+    void flushSendQue();
+
+    /// @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 request that is in the process of being sent.
+    ///
+    /// The pointer returned by this method will be populated with the
+    /// request that has been passed into doSend() and for which the
+    /// completion callback has not yet been invoked.
+    const NameChangeRequestPtr& getNcrToSend() {
+        return (ncr_to_send_);
+    }
+
+    /// @brief Returns the maximum number of entries allowed in the send queue.
+    size_t getQueMaxSize() const  {
+        return (send_que_max_);
+    }
+
+    /// @brief Returns the number of entries currently in the send queue.
+    size_t getQueSize() const {
+        return (send_que_.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.
+    const RequestSendHandler* send_handler_;
+
+    /// @brief Maximum number of entries permitted in the send queue.
+    size_t send_que_max_;
+
+    /// @brief Queue of the requests waiting to be sent.
+    SendQue send_que_;
+
+    /// @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

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

@@ -0,0 +1,305 @@
+// 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, 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,
+            const 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 recv_completion_handler.
+    recv_callback_.reset(new UDPCallback(
+                                       RawBufferPtr(new uint8_t[RECV_BUF_MAX]),
+                                       RECV_BUF_MAX,
+                                       UDPEndpointPtr(new asiolink::
+                                                      UDPEndpoint()),
+                                       boost::bind(&NameChangeUDPListener::
+                                       recv_completion_handler, 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())));
+
+        // If in test mode, enable address reuse.
+        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_));
+}
+
+
+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::recv_completion_handler(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 send_completion_handler.
+    send_callback_.reset(new UDPCallback(
+                                       RawBufferPtr(new uint8_t[SEND_BUF_MAX]),
+                                       SEND_BUF_MAX,
+                                       UDPEndpointPtr(new asiolink::
+                                                      UDPEndpoint()),
+                                       boost::bind(&NameChangeUDPSender::
+                                       send_completion_handler, 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())));
+
+        // If in test mode, enable address reuse.
+        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::send_completion_handler(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

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

@@ -0,0 +1,564 @@
+// 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 aiso 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, 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 throws a NcrUDPError if either the handler or buffer pointers
+    /// are invalid.
+    UDPCallback (RawBufferPtr buffer, 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.
+    const 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 throws a 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.
+    const 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.
+    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 IP 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
+    /// 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,
+                          const 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 throws a 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 throws a 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 throws a 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 recv_completion_handler(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 indicator that signifies listener is being used
+    /// in test mode
+    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_QUE_DEFAULT
+    /// @param reuse_address enables IP address sharing when true
+    /// It defaults to false.
+    ///
+    /// @throw base class throws NcrSenderError if handler is invalid.
+    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_QUE_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 throws a 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 throws a 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 send_completion_handler(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 boolean indicator that signifies sender is being used
+    /// in test mode
+    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)

+ 8 - 6
src/bin/d2/tests/d_test_stubs.h

@@ -608,11 +608,12 @@ public:
         try  {
             config_set_ = isc::data::Element::fromJSON(json_text);
         } catch (const isc::Exception &ex) {
-            return  ::testing::AssertionFailure() 
-                << "JSON text failed to parse:" << ex.what();
+            return (::testing::AssertionFailure(::testing::Message() << 
+                                                "JSON text failed to parse:" 
+                                                << ex.what())); 
         }
 
-        return ::testing::AssertionSuccess();
+        return (::testing::AssertionSuccess());
     }
 
 
@@ -628,11 +629,12 @@ public:
         isc::data::ConstElementPtr comment;
         comment = isc::config::parseAnswer(rcode, answer_);
         if (rcode == should_be) {
-            return testing::AssertionSuccess();
+            return (testing::AssertionSuccess());
         }
 
-        return ::testing::AssertionFailure() << "checkAnswer rcode:" 
-               << rcode << " comment: " << *comment;
+        return (::testing::AssertionFailure(::testing::Message() << 
+                                            "checkAnswer rcode:" << rcode 
+                                            << " comment: " << *comment));
     }
 
     /// @brief Configuration set being tested.

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

@@ -0,0 +1,501 @@
+// 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 ()(NameChangeListener::Result, NameChangeRequestPtr) {
+    }
+};
+
+/// @brief Tests the NameChangeUDPListener constructors.
+/// This test verifies that:
+/// 1. Listener constructor requires valid completion handler
+/// 2. 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 constructing with an empty receive handler is not allowed.
+    EXPECT_THROW(NameChangeUDPListener(ip_address, port, FMT_JSON, NULL),
+                                       NcrListenerError);
+
+    // 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_) {
+    // @todo NameChangeRequest message doesn't currently have a comparison
+    // operator, so we will cheat and compare the text form.
+    return ((sent_ncr_ && received_ncr_ ) &&
+        ((sent_ncr_->toText()) == (received_ncr_->toText())));
+}
+
+/// @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_;
+    NameChangeUDPListener *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_ = 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);
+    }
+
+    /// @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 ()(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.
+    EXPECT_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 ()(NameChangeSender::Result, NameChangeRequestPtr) {
+    }
+};
+
+/// @brief Tests the NameChangeUDPSender constructors.
+/// This test verifies that:
+/// 1. Sender constructor requires valid completion handler
+/// 2. Given valid parameters, the sender constructor 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 empty send handler is not allowed.
+    EXPECT_THROW(NameChangeUDPSender(ip_address, port,
+        ip_address, port, FMT_JSON, NULL), NcrSenderError);
+
+    // 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_QUE_DEFAULT;
+    EXPECT_EQ(expected, sender->getQueMaxSize());
+
+    // 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)));
+}
+
+/// @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.getQueSize());
+    }
+
+    // Verify that attempting to send an additional message results in a
+    // queue full exception.
+    EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueFull);
+
+    // 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.getQueSize());
+    }
+
+    // Verify that the queue is empty.
+    EXPECT_EQ(0, sender.getQueSize());
+
+    // Verify that we can add back to the queue
+    EXPECT_NO_THROW(sender.sendRequest(ncr));
+    EXPECT_EQ(1, sender.getQueSize());
+
+    // Verify that we can remove the current entry at the front of the queue.
+    EXPECT_NO_THROW(sender.skipNext());
+    EXPECT_EQ(0, sender.getQueSize());
+
+    // Verify that flushing the queue is not allowed in sending state.
+    EXPECT_THROW(sender.flushSendQue(), NcrSenderError);
+
+    // Put a message on the queue.
+    EXPECT_NO_THROW(sender.sendRequest(ncr));
+    EXPECT_EQ(1, sender.getQueSize());
+
+    // 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.getQueSize());
+
+    // Verify that flushing the queue works when not sending.
+    EXPECT_NO_THROW(sender.flushSendQue());
+    EXPECT_EQ(0, sender.getQueSize());
+}
+
+/// @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 ()(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 ()(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_->getQueSize());
+    }
+
+    // Execute callbacks until we have sent and received all of messages.
+    while (sender_->getQueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
+        EXPECT_NO_THROW(io_service_.run_one());
+    }
+
+    // Send queue should be empty.
+    EXPECT_EQ(0, sender_->getQueSize());
+
+    // 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