diff options
Diffstat (limited to 'src/lib/dhcp_ddns/ncr_io.h')
-rw-r--r-- | src/lib/dhcp_ddns/ncr_io.h | 853 |
1 files changed, 853 insertions, 0 deletions
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h new file mode 100644 index 0000000..664057d --- /dev/null +++ b/src/lib/dhcp_ddns/ncr_io.h @@ -0,0 +1,853 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef NCR_IO_H +#define NCR_IO_H + +/// @file ncr_io.h +/// @brief This file defines abstract classes for exchanging NameChangeRequests. +/// +/// These classes are used for sending and receiving requests to update DNS +/// information for FQDNs embodied as NameChangeRequests (aka NCRs). Ultimately, +/// NCRs must move through the following three layers in order to implement +/// DHCP-DDNS: +/// +/// * Application layer - the business layer which needs to +/// transport NameChangeRequests, and is unaware of the means by which +/// they are transported. +/// +/// * NameChangeRequest layer - This is the layer which acts as the +/// intermediary between the Application layer and the IO layer. It must +/// be able to move NameChangeRequests to the IO layer as raw data and move +/// raw data from the IO layer in the Application layer as +/// NameChangeRequests. +/// +/// * IO layer - the low-level layer that is directly responsible for +/// sending and receiving data asynchronously and is supplied through +/// other libraries. This layer is largely unaware of the nature of the +/// data being transmitted. In other words, it doesn't know beans about +/// NCRs. +/// +/// The abstract classes defined here implement the latter, middle layer, +/// the NameChangeRequest layer. There are two types of participants in this +/// middle ground: +/// +/// * listeners - Receive NCRs from one or more sources. The DHCP-DDNS +/// application, (aka D2), is a listener. Listeners are embodied by the +/// class, NameChangeListener. +/// +/// * senders - sends NCRs to a given target. DHCP servers are senders. +/// Senders are embodied by the class, NameChangeSender. +/// +/// 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 <dhcp_ddns/ncr_msg.h> +#include <exceptions/exceptions.h> + +#include <boost/scoped_ptr.hpp> + +#include <deque> +#include <mutex> + +namespace isc { +namespace dhcp_ddns { + +/// @brief Defines the list of socket protocols supported. +/// Currently only UDP is implemented. +/// @todo TCP is intended to be implemented prior 1.0 release. +/// @todo Give some thought to an ANY protocol which might try +/// first as UDP then as TCP, etc. +enum NameChangeProtocol { + NCR_UDP, + NCR_TCP +}; + +/// @brief Function which converts text labels to @ref NameChangeProtocol enums. +/// +/// @param protocol_str text to convert to an enum. +/// Valid string values: "UDP", "TCP" +/// +/// @return NameChangeProtocol value which maps to the given string. +/// +/// @throw isc::BadValue if given a string value which does not map to an +/// enum value. +extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str); + +/// @brief Function which converts @ref NameChangeProtocol enums to text labels. +/// +/// @param protocol enum value to convert to label +/// +/// @return std:string containing the text label if the value is valid, or +/// "UNKNOWN" if not. +extern std::string ncrProtocolToString(NameChangeProtocol protocol); + +/// @brief Exception thrown if an NcrListenerError encounters a general error. +class NcrListenerError : public isc::Exception { +public: + NcrListenerError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown if an error occurs during IO source open. +class NcrListenerOpenError : public isc::Exception { +public: + NcrListenerOpenError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown if an error occurs initiating an IO receive. +class NcrListenerReceiveError : public isc::Exception { +public: + NcrListenerReceiveError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + + +/// @brief Abstract interface for receiving NameChangeRequests. +/// +/// NameChangeListener provides the means to: +/// - Supply a callback to invoke upon receipt of an NCR or a listening +/// error +/// - Start listening using a given IOService instance to process events +/// - Stop listening +/// +/// It implements the high level logic flow to listen until a request arrives, +/// invoke the implementation's handler and return to listening for the next +/// request. +/// +/// It provides virtual methods that allow derivations supply implementations +/// to open the appropriate IO source, perform a listen, and close the IO +/// source. +/// +/// The overall design is based on a callback chain. The listener's caller (the +/// application) supplies an "application" layer callback through which it will +/// receive inbound NameChangeRequests. The listener derivation will supply +/// its own callback to the IO layer to process receive events from its IO +/// source. This is referred to as the NameChangeRequest completion handler. +/// It is through this handler that the NameChangeRequest layer gains access +/// to the low level IO read service results. It is expected to assemble +/// NameChangeRequests from the inbound data and forward them to the +/// application layer by invoking the application layer callback registered +/// with the listener. +/// +/// The application layer callback is structured around a nested class, +/// RequestReceiveHandler. It consists of single, abstract operator() which +/// accepts a result code and a pointer to a NameChangeRequest as parameters. +/// In order to receive inbound NCRs, a caller implements a derivation of the +/// RequestReceiveHandler and supplies an instance of this derivation to the +/// NameChangeListener constructor. This "registers" the handler with the +/// listener. +/// +/// To begin listening, the caller invokes the listener's startListener() +/// method, passing in an IOService instance. This in turn will pass the +/// IOService into the virtual method, open(). The open method is where the +/// listener derivation performs the steps necessary to prepare its IO source +/// for reception (e.g. opening a socket, connecting to a database). +/// +/// Assuming the open is successful, startListener will call receiveNext, to +/// initiate an asynchronous receive. This method calls the virtual method, +/// doReceive(). The listener derivation uses doReceive to instigate an IO +/// layer asynchronous receive passing in its IO layer callback to +/// handle receive events from the IO source. +/// +/// As stated earlier, the derivation's NameChangeRequest completion handler +/// MUST invoke the application layer handler registered with the listener. +/// This is done by passing in either a success status and a populated +/// NameChangeRequest or an error status and an empty request into the +/// listener's invokeRecvHandler method. This is the mechanism by which the +/// listener's caller is handed inbound NCRs. +class NameChangeListener { +public: + + /// @brief Defines the outcome of an asynchronous NCR receive + enum Result { + SUCCESS, + TIME_OUT, + STOPPED, + ERROR + }; + + /// @brief Abstract class for defining application layer receive callbacks. + /// + /// Applications which will receive NameChangeRequests must provide a + /// derivation of this class to the listener constructor in order to + /// receive NameChangeRequests. + class RequestReceiveHandler { + public: + + /// @brief Function operator implementing a NCR receive callback. + /// + /// This method allows the application to receive the inbound + /// NameChangeRequests. It is intended to function as a hand off of + /// information and should probably not be time-consuming. + /// + /// @param result contains that receive outcome status. + /// @param ncr is a pointer to the newly received NameChangeRequest if + /// result is NameChangeListener::SUCCESS. It is indeterminate other + /// wise. + /// + /// @throw This method MUST NOT throw. + virtual void operator ()(const Result result, + NameChangeRequestPtr& ncr) = 0; + + virtual ~RequestReceiveHandler() { + } + }; + + /// @brief Constructor + /// + /// @param recv_handler is a pointer the application layer handler to be + /// invoked each time a NCR is received or a receive error occurs. + NameChangeListener(RequestReceiveHandler& recv_handler); + + /// @brief Destructor + virtual ~NameChangeListener() { + }; + + /// @brief Prepares the IO for reception and initiates the first receive. + /// + /// Calls the derivation's open implementation to initialize the IO layer + /// source for receiving inbound requests. If successful, it starts the + /// first asynchronous read by receiveNext. + /// + /// @param io_service is the IOService that will handle IO event processing. + /// + /// @throw NcrListenError if the listener is already "listening" or + /// in the event the open or doReceive methods fail. + void startListening(isc::asiolink::IOService& io_service); + + /// @brief Closes the IO source and stops listen logic. + /// + /// Calls the derivation's implementation of close and marks the state + /// as not listening. + void stopListening(); + +protected: + /// @brief Initiates an asynchronous receive + /// + /// Sets context information to indicate that IO is in progress and invokes + /// the derivation's asynchronous receive method, doReceive. Note doReceive + /// should not be called outside this method to ensure context information + /// integrity. + /// + /// @throw Derivation's doReceive method may throw isc::Exception upon + /// error. + void receiveNext(); + + /// @brief Calls the NCR receive handler registered with the listener. + /// + /// This is the hook by which the listener's caller's NCR receive handler + /// is called. This method MUST be invoked by the derivation's + /// implementation of doReceive. + /// + /// NOTE: + /// The handler invoked by this method MUST NOT THROW. The handler is + /// at application level and should trap and handle any errors at + /// that level, rather than throw exceptions. If an error has occurred + /// prior to invoking the handler, it will be expressed in terms a failed + /// result being passed to the handler, not a throw. Therefore any + /// exceptions at the handler level are application issues and should be + /// dealt with at that level. + /// + /// This method does wrap the handler invocation within a try-catch + /// block as a fail-safe. The exception will be logged but the + /// receive logic will continue. What this implies is that continued + /// operation may or may not succeed as the application has violated + /// the interface contract. + /// + /// @param result contains that receive outcome status. + /// @param ncr is a pointer to the newly received NameChangeRequest if + /// result is NameChangeListener::SUCCESS. It is indeterminate other + /// wise. + void invokeRecvHandler(const Result result, NameChangeRequestPtr& ncr); + + /// @brief Abstract method which opens the IO source for reception. + /// + /// The derivation uses this method to perform the steps needed to + /// prepare the IO source to receive requests. + /// + /// @param io_service is the IOService that process IO events. + /// + /// @throw If the implementation encounters an error it MUST + /// throw it as an isc::Exception or derivative. + virtual void open(isc::asiolink::IOService& io_service) = 0; + + /// @brief Abstract method which closes the IO source. + /// + /// The derivation uses this method to perform the steps needed to + /// "close" the IO source. + /// + /// @throw If the implementation encounters an error it MUST + /// throw it as an isc::Exception or derivative. + virtual void close() = 0; + + /// @brief Initiates an IO layer asynchronous read. + /// + /// The derivation uses this method to perform the steps needed to + /// initiate an asynchronous read of the IO source with the + /// derivation's IO layer handler as the IO completion callback. + /// + /// @throw If the implementation encounters an error it MUST + /// throw it as an isc::Exception or derivative. + virtual void doReceive() = 0; + +public: + + /// @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. This implies that closing the + /// IO source will interrupt that operation, resulting in a callback + /// invocation. + /// + /// @return The listening mode. + bool amListening() const { + return (listening_); + } + + /// @brief Returns true if the listener has an IO call in progress. + /// + /// A true value indicates that the listener has an asynchronous IO in + /// progress which will complete at some point in the future. Completion + /// of the call will invoke the registered callback. It is important to + /// understand that the listener and its related objects should not be + /// deleted while there is an IO call pending. This can result in the + /// IO service attempting to invoke methods on objects that are no longer + /// valid. + /// + /// @return The IO pending flag. + bool isIoPending() const { + return (io_pending_); + } + +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 in listening mode. + bool listening_; + + /// @brief Indicates that listener has an async IO pending completion. + bool io_pending_; + + /// @brief Application level NCR receive completion handler. + RequestReceiveHandler& recv_handler_; +}; + +/// @brief Defines a smart pointer to an instance of a listener. +typedef boost::shared_ptr<NameChangeListener> NameChangeListenerPtr; + + +/// @brief Thrown when a NameChangeSender encounters an error. +class NcrSenderError : public isc::Exception { +public: + NcrSenderError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown if an error occurs during IO source open. +class NcrSenderOpenError : public isc::Exception { +public: + NcrSenderOpenError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown if an error occurs initiating an IO send. +class NcrSenderQueueFull : public isc::Exception { +public: + NcrSenderQueueFull(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown if an error occurs initiating an IO send. +class NcrSenderSendError : public isc::Exception { +public: + NcrSenderSendError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + + +/// @brief Abstract interface for sending NameChangeRequests. +/// +/// NameChangeSender provides the means to: +/// - Supply a callback to invoke upon completing the delivery of an NCR or a +/// send error +/// - Start sending using a given IOService instance to process events +/// - Queue NCRs for delivery +/// - Stop sending +/// +/// It implements the high level logic flow to queue requests for delivery, +/// and ship them one at a time, waiting for the send to complete prior to +/// sending the next request in the queue. If a send fails, the request +/// will remain at the front of queue and the send will be retried +/// endlessly unless the caller dequeues the request. Note, it is presumed that +/// a send failure is some form of IO error such as loss of connectivity and +/// not a message content error. It should not be possible to queue an invalid +/// message. +/// +/// It should be noted that once a request is placed onto the send queue it +/// will remain there until one of three things occur: +/// * It is successfully delivered +/// * @c NameChangeSender::skipNext() is called +/// * @c NameChangeSender::clearSendQueue() is called +/// +/// The queue contents are preserved across start and stop listening +/// transitions. This is to provide for error recovery without losing +/// undelivered requests. + +/// It provides virtual methods so derivations may supply implementations to +/// open the appropriate IO sink, perform a send, and close the IO sink. +/// +/// The overall design is based on a callback chain. The sender's caller (the +/// application) supplies an "application" layer callback through which it will +/// be given send completion notifications. The sender derivation will employ +/// its own callback at the IO layer to process send events from its IO sink. +/// This callback is expected to forward the outcome of each asynchronous send +/// to the application layer by invoking the application layer callback +/// registered with the sender. +/// +/// The application layer callback is structured around a nested class, +/// RequestSendHandler. It consists of single, abstract operator() which +/// accepts a result code and a pointer to a NameChangeRequest as parameters. +/// In order to receive send completion notifications, a caller implements a +/// derivation of the RequestSendHandler and supplies an instance of this +/// derivation to the NameChangeSender constructor. This "registers" the +/// handler with the sender. +/// +/// To begin sending, the caller invokes the listener's startSending() +/// method, passing in an IOService instance. This in turn will pass the +/// IOService into the virtual method, open(). The open method is where the +/// sender derivation performs the steps necessary to prepare its IO sink for +/// output (e.g. opening a socket, connecting to a database). At this point, +/// the sender is ready to send messages. +/// +/// In order to send a request, the application layer invokes the sender +/// method, sendRequest(), passing in the NameChangeRequest to send. This +/// method places the request onto the back of the send queue, and then invokes +/// the sender method, sendNext(). +/// +/// If there is already a send in progress when sendNext() is called, the method +/// will return immediately rather than initiate the next send. This is to +/// ensure that sends are processed sequentially. +/// +/// If there is not a send in progress and the send queue is not empty, +/// the sendNext method will pass the NCR at the front of the send queue into +/// the virtual doSend() method. +/// +/// The sender derivation uses this doSend() method to instigate an IO layer +/// asynchronous send with its IO layer callback to handle send events from its +/// IO sink. +/// +/// As stated earlier, the derivation's IO layer callback MUST invoke the +/// application layer handler registered with the sender. This is done by +/// passing in a status indicating the outcome of the send into the sender's +/// invokeSendHandler method. This is the mechanism by which the sender's +/// caller is handed outbound notifications. + +/// After invoking the application layer handler, the invokeSendHandler method +/// will call the sendNext() method to initiate the next send. This ensures +/// that requests continue to dequeue and ship. +/// +class NameChangeSender { +public: + + /// @brief Defines the type used for the request send queue. + typedef std::deque<NameChangeRequestPtr> SendQueue; + + /// @brief Defines a default maximum number of entries in the send queue. + static const size_t MAX_QUEUE_DEFAULT = 1024; + + /// @brief Defines the outcome of an asynchronous NCR send. + enum Result { + SUCCESS, + TIME_OUT, + STOPPED, + ERROR + }; + + /// @brief Abstract class for defining application layer send callbacks. + /// + /// Applications which will send NameChangeRequests must provide a + /// derivation of this class to the sender constructor in order to + /// receive send outcome notifications. + class RequestSendHandler { + public: + + /// @brief Function operator implementing a NCR send callback. + /// + /// This method allows the application to receive the outcome of + /// each send. It is intended to function as a hand off of information + /// and should probably not be time-consuming. + /// + /// @param result contains that send outcome status. + /// @param ncr is a pointer to the NameChangeRequest that was + /// delivered (or attempted). + /// + /// @throw This method MUST NOT throw. + virtual void operator ()(const Result result, + NameChangeRequestPtr& ncr) = 0; + + virtual ~RequestSendHandler() { + } + }; + + /// @brief Constructor + /// + /// @param send_handler is a pointer the application layer handler to be + /// invoked each time a NCR send attempt completes. + /// @param send_queue_max is the maximum number of entries allowed in the + /// send queue. Once the maximum number is reached, all calls to + /// sendRequest will fail with an exception. + NameChangeSender(RequestSendHandler& send_handler, + size_t send_queue_max = MAX_QUEUE_DEFAULT); + + /// @brief Destructor + virtual ~NameChangeSender() { + } + + /// @brief Prepares the IO for transmission. + /// + /// Calls the derivation's open implementation to initialize the IO layer + /// sink for sending outbound requests. + /// + /// @param io_service is the IOService that will handle IO event processing. + /// + /// @throw NcrSenderError if the sender is already "sending" or + /// NcrSenderOpenError if the open fails. + void startSending(isc::asiolink::IOService & io_service); + + /// @brief Closes the IO sink and stops send logic. + /// + /// Calls the derivation's implementation of close and marks the state + /// as not sending. + void stopSending(); + + /// @brief Queues the given request to be sent. + /// + /// The given request is placed at the back of the send queue and then + /// sendNext is invoked. + /// + /// @param ncr is the NameChangeRequest to send. + /// + /// @throw NcrSenderError if the sender is not in sending state or + /// the request is empty; NcrSenderQueueFull if the send queue has reached + /// capacity. + void sendRequest(NameChangeRequestPtr& ncr); + + /// @brief Move all queued requests from a given sender into the send queue + /// + /// Moves all of the entries in the given sender's queue and places them + /// into send queue. This provides a mechanism of reassigning queued + /// messages from one sender to another. This is useful for dealing with + /// dynamic configuration changes. + /// + /// @param source_sender from whom the queued messages will be taken + /// + /// @throw NcrSenderError if either sender is in send mode, if the number of + /// messages in the source sender's queue is larger than this sender's + /// maximum queue size, or if this sender's queue is not empty. + void assumeQueue(NameChangeSender& source_sender); + + /// @brief Returns a file descriptor suitable for use with select + /// + /// The value returned is an open file descriptor which can be used with + /// select() system call to monitor the sender for IO events. This allows + /// NameChangeSenders to be used in applications which use select, rather + /// than IOService to wait for IO events to occur. + /// + /// @warning Attempting other use of this value may lead to unpredictable + /// behavior in the sender. + /// + /// @return Returns an "open" file descriptor + /// + /// @throw NcrSenderError if the sender is not in send mode, + virtual int getSelectFd() = 0; + + /// @brief Returns whether or not the sender has IO ready to process. + /// + /// @return true if the sender has at IO ready, false otherwise. + virtual bool ioReady() = 0; + +private: + + /// @brief Prepares the IO for transmission in a thread safe context. + /// + /// @param io_service is the IOService that will handle IO event processing. + void startSendingInternal(isc::asiolink::IOService & io_service); + + /// @brief Queues the given request to be sent in a thread safe context. + /// + /// @param ncr is the NameChangeRequest to send. + /// + /// @throw NcrSenderQueueFull if the send queue has reached capacity. + void sendRequestInternal(NameChangeRequestPtr& ncr); + + /// @brief Move all queued requests from a given sender into the send queue + /// in a thread safe context. + /// + /// @param source_sender from whom the queued messages will be taken + /// + /// @throw NcrSenderError if this sender's queue is not empty. + void assumeQueueInternal(NameChangeSender& source_sender); + + /// @brief Calls the NCR send completion handler registered with the + /// sender in a thread safe context. + /// + /// @param result contains that send outcome status. + void invokeSendHandlerInternal(const NameChangeSender::Result result); + + /// @brief Removes the request at the front of the send queue in a thread + /// safe context. + void skipNextInternal(); + + /// @brief Returns the number of entries currently in the send queue in a + /// thread safe context. + /// + /// @return the queue size. + size_t getQueueSizeInternal() const; + + /// @brief Returns the entry at a given position in the queue in a thread + /// safe context. + /// + /// @return Pointer reference to the queue entry. + /// + /// @throw NcrSenderError if the given index is beyond the + /// end of the queue. + const NameChangeRequestPtr& peekAtInternal(const size_t index) const; + +protected: + + /// @brief Dequeues and sends the next request on the send queue in a thread + /// safe context. + /// + /// If there is already a send in progress just return. If there is not + /// a send in progress and the send queue is not empty the grab the next + /// message on the front of the queue and call doSend(). + void sendNext(); + + /// @brief Calls the NCR send completion handler registered with the + /// sender. + /// + /// This is the hook by which the sender's caller's NCR send completion + /// handler is called. This method MUST be invoked by the derivation's + /// implementation of doSend. Note that if the send was a success, the + /// entry at the front of the queue is removed from the queue. + /// If not we leave it there so we can retry it. After we invoke the + /// handler we clear the pending ncr value and queue up the next send. + /// + /// NOTE: + /// The handler invoked by this method MUST NOT THROW. The handler is + /// application level logic and should trap and handle any errors at + /// that level, rather than throw exceptions. If IO errors have occurred + /// prior to invoking the handler, they are expressed in terms a failed + /// result being passed to the handler. Therefore any exceptions at the + /// handler level are application issues and should be dealt with at that + /// level. + /// + /// This method does wrap the handler invocation within a try-catch + /// block as a fail-safe. The exception will be logged but the + /// send logic will continue. What this implies is that continued + /// operation may or may not succeed as the application has violated + /// the interface contract. + /// + /// @param result contains that send outcome status. + void invokeSendHandler(const NameChangeSender::Result result); + + /// @brief 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; + +public: + + /// @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 the message. + void skipNext(); + + /// @brief Flushes all entries in the send queue + /// + /// This method can be used to discard all of the NCRs currently in the + /// the send queue. Note it may not be called while the sender is in + /// the sending state. + /// + /// @throw NcrSenderError if called and sender is in sending state. + void clearSendQueue(); + + /// @brief 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. + /// + /// @return The send mode. + 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. + /// + /// @return The send in progress flag. + bool isSendInProgress() const; + + /// @brief Returns the maximum number of entries allowed in the send queue. + /// + /// @return The queue maximum size. + size_t getQueueMaxSize() const { + return (send_queue_max_); + } + + /// @brief Sets the maximum queue size to the given value. + /// + /// Sets the maximum number of entries allowed in the queue to the + /// the given value. + /// + /// @param new_max the new value to use as the maximum + /// + /// @throw NcrSenderError if the value is less than one. + void setQueueMaxSize(const size_t new_max); + + /// @brief Returns the number of entries currently in the send queue. + /// + /// @return The queue size. + size_t getQueueSize() const; + + /// @brief Returns the entry at a given position in the queue. + /// + /// Note that the entry is not removed from the queue. + /// + /// @param index the index of the entry in the queue to fetch. + /// Valid values are 0 (front of the queue) to (queue size - 1). + /// + /// @return Pointer reference to the queue entry. + /// + /// @throw NcrSenderError if the given index is beyond the + /// end of the queue. + const NameChangeRequestPtr& peekAt(const size_t index) const; + + /// @brief Processes sender IO events + /// + /// Executes at most one ready handler on the sender's IO service. If + /// no handlers are ready it returns immediately. + /// + /// @warning - Running all ready handlers, in theory, could process all + /// messages currently queued. + /// + /// NameChangeSender daisy chains requests together in its completion + /// by one message completion's handler initiating the next message's send. + /// When using UDP, a send immediately marks its event handler as ready + /// to run. If this occurs inside a call to ioservice::poll() or run(), + /// that event will also be run. If that handler calls UDP send then + /// that send's handler will be marked ready and executed and so on. If + /// there were 1000 messages in the queue then all them would be sent from + /// within the context of one call to runReadyIO(). + /// By running only one handler at time, we ensure that NCR IO activity + /// doesn't starve other processing. It is unclear how much of a real + /// threat this poses but for now it is best to err on the side of caution. + virtual void runReadyIO(); + +protected: + + /// @brief Returns a reference to the send queue. + /// + /// @return The send queue. + SendQueue& getSendQueue() { + return (send_queue_); + } + +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 registered send completion handler. + RequestSendHandler& send_handler_; + + /// @brief Maximum number of entries permitted in the send queue. + size_t send_queue_max_; + + /// @brief Queue of the requests waiting to be sent. + SendQueue send_queue_; + + /// @brief Pointer to the request which is in the process of being sent. + NameChangeRequestPtr ncr_to_send_; + + /// @brief Pointer to the IOService currently being used by the sender. + /// @note We need to remember the io_service but we receive it by + /// reference. Use a raw pointer to store it. This value should never be + /// exposed and is only valid while in send mode. + asiolink::IOService* io_service_; + + /// @brief The mutex used to protect internal state. + const boost::scoped_ptr<std::mutex> mutex_; +}; + +/// @brief Defines a smart pointer to an instance of a sender. +typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr; + +} // namespace dhcp_ddns +} // namespace isc + +#endif |