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_update_message.cc d2_update_message.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.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 += 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_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
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
 EXTRA_DIST += d2_messages.mes

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

@@ -17,12 +17,6 @@
 
 
 #include <boost/foreach.hpp>
 #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 isc {
 namespace d2 {
 namespace d2 {
 
 
@@ -102,12 +96,14 @@ D2CfgMgr::createConfigParser(const std::string& config_id) {
     D2CfgContextPtr context = getD2CfgContext();
     D2CfgContextPtr context = getD2CfgContext();
 
 
     // Create parser instance based on element_id.
     // Create parser instance based on element_id.
-    DhcpConfigParser* parser = NULL;
+    isc::dhcp::DhcpConfigParser* parser = NULL;
     if ((config_id == "interface")  ||
     if ((config_id == "interface")  ||
         (config_id == "ip_address")) {
         (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") {
     } 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") {
     } else if (config_id ==  "forward_ddns") {
         parser = new DdnsDomainListMgrParser("forward_mgr",
         parser = new DdnsDomainListMgrParser("forward_mgr",
                                              context->getForwardMgr(),
                                              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
 % DCTL_ORDER_ERROR configuration contains more elements than the parsing order
 An error message which indicates that configuration being parsed includes
 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.
 is a programmatic error.
 
 
 % DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
 % 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
 expecting an item but has not found it in the new configuration.  This may
 mean that the BIND 10 configuration database is corrupt.
 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,
 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
 the server failed to create a parser to decode the contents of the named
 configuration element, or the creation succeeded but the parsing actions
 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
 This is a debug message issued when the Dhcp-Ddns application encounters an
 unrecoverable error from within the event loop.
 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
 % DHCP_DDNS_NO_MATCH No DNS servers match FQDN: %1
 This is warning message issued when there are no domains in the configuration
 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.
 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
 % DHCP_DDNS_PROCESS_INIT application init invoked
 This is a debug message issued when the Dhcp-Ddns application enters
 This is a debug message issued when the Dhcp-Ddns application enters
 its init method.
 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_update_message.cc ../d2_update_message.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.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_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 += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_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 += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += ncr_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
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

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

@@ -608,11 +608,12 @@ public:
         try  {
         try  {
             config_set_ = isc::data::Element::fromJSON(json_text);
             config_set_ = isc::data::Element::fromJSON(json_text);
         } catch (const isc::Exception &ex) {
         } 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;
         isc::data::ConstElementPtr comment;
         comment = isc::config::parseAnswer(rcode, answer_);
         comment = isc::config::parseAnswer(rcode, answer_);
         if (rcode == should_be) {
         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.
     /// @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