1 // Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #ifndef NCR_IO_H
8 #define NCR_IO_H
9 
10 /// @file ncr_io.h
11 /// @brief This file defines abstract classes for exchanging NameChangeRequests.
12 ///
13 /// These classes are used for sending and receiving requests to update DNS
14 /// information for FQDNs embodied as NameChangeRequests (aka NCRs). Ultimately,
15 /// NCRs must move through the following three layers in order to implement
16 /// DHCP-DDNS:
17 ///
18 ///    * Application layer - the business layer which needs to
19 ///    transport NameChangeRequests, and is unaware of the means by which
20 ///    they are transported.
21 ///
22 ///    * NameChangeRequest layer - This is the layer which acts as the
23 ///    intermediary between the Application layer and the IO layer.  It must
24 ///    be able to move NameChangeRequests to the IO layer as raw data and move
25 ///    raw data from the IO layer in the Application layer as
26 ///    NameChangeRequests.
27 ///
28 ///    * IO layer - the low-level layer that is directly responsible for
29 ///    sending and receiving data asynchronously and is supplied through
30 ///    other libraries.  This layer is largely unaware of the nature of the
31 ///    data being transmitted.  In other words, it doesn't know beans about
32 ///    NCRs.
33 ///
34 /// The abstract classes defined here implement the latter, middle layer,
35 /// the NameChangeRequest layer.  There are two types of participants in this
36 /// middle ground:
37 ///
38 ///    * listeners - Receive NCRs from one or more sources. The DHCP-DDNS
39 ///   application, (aka D2), is a listener. Listeners are embodied by the
40 ///   class, NameChangeListener.
41 ///
42 ///    * senders - sends NCRs to a given target.  DHCP servers are senders.
43 ///   Senders are embodied by the class, NameChangeSender.
44 ///
45 /// These two classes present a public interface for asynchronous
46 /// communications that is independent of the IO layer mechanisms.  While the
47 /// type and details of the IO mechanism are not relevant to either class, it
48 /// is presumed to use isc::asiolink library for asynchronous event processing.
49 
50 #include <asiolink/io_address.h>
51 #include <asiolink/io_service.h>
52 #include <dhcp_ddns/ncr_msg.h>
53 #include <exceptions/exceptions.h>
54 
55 #include <boost/scoped_ptr.hpp>
56 
57 #include <deque>
58 #include <mutex>
59 
60 namespace isc {
61 namespace dhcp_ddns {
62 
63 /// @brief Defines the list of socket protocols supported.
64 /// Currently only UDP is implemented.
65 /// @todo TCP is intended to be implemented prior 1.0 release.
66 /// @todo Give some thought to an ANY protocol which might try
67 /// first as UDP then as TCP, etc.
68 enum NameChangeProtocol {
69   NCR_UDP,
70   NCR_TCP
71 };
72 
73 /// @brief Function which converts text labels to @ref NameChangeProtocol enums.
74 ///
75 /// @param protocol_str text to convert to an enum.
76 /// Valid string values: "UDP", "TCP"
77 ///
78 /// @return NameChangeProtocol value which maps to the given string.
79 ///
80 /// @throw isc::BadValue if given a string value which does not map to an
81 /// enum value.
82 extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str);
83 
84 /// @brief Function which converts @ref NameChangeProtocol enums to text labels.
85 ///
86 /// @param protocol enum value to convert to label
87 ///
88 /// @return std:string containing the text label if the value is valid, or
89 /// "UNKNOWN" if not.
90 extern std::string ncrProtocolToString(NameChangeProtocol protocol);
91 
92 /// @brief Exception thrown if an NcrListenerError encounters a general error.
93 class NcrListenerError : public isc::Exception {
94 public:
NcrListenerError(const char * file,size_t line,const char * what)95     NcrListenerError(const char* file, size_t line, const char* what) :
96         isc::Exception(file, line, what) { };
97 };
98 
99 /// @brief Exception thrown if an error occurs during IO source open.
100 class NcrListenerOpenError : public isc::Exception {
101 public:
NcrListenerOpenError(const char * file,size_t line,const char * what)102     NcrListenerOpenError(const char* file, size_t line, const char* what) :
103         isc::Exception(file, line, what) { };
104 };
105 
106 /// @brief Exception thrown if an error occurs initiating an IO receive.
107 class NcrListenerReceiveError : public isc::Exception {
108 public:
NcrListenerReceiveError(const char * file,size_t line,const char * what)109     NcrListenerReceiveError(const char* file, size_t line, const char* what) :
110         isc::Exception(file, line, what) { };
111 };
112 
113 
114 /// @brief Abstract interface for receiving NameChangeRequests.
115 ///
116 /// NameChangeListener provides the means to:
117 /// -  Supply a callback to invoke upon receipt of an NCR or a listening
118 /// error
119 /// -  Start listening using a given IOService instance to process events
120 /// -  Stop listening
121 ///
122 /// It implements the high level logic flow to listen until a request arrives,
123 /// invoke the implementation's handler and return to listening for the next
124 /// request.
125 ///
126 /// It provides virtual methods that allow derivations supply implementations
127 /// to open the appropriate IO source, perform a listen, and close the IO
128 /// source.
129 ///
130 /// The overall design is based on a callback chain. The listener's caller (the
131 /// application) supplies an "application" layer callback through which it will
132 /// receive inbound NameChangeRequests.  The listener derivation will supply
133 /// its own callback to the IO layer to process receive events from its IO
134 /// source.  This is referred to as the NameChangeRequest completion handler.
135 /// It is through this handler that the NameChangeRequest layer gains access
136 /// to the low level IO read service results.  It is expected to assemble
137 /// NameChangeRequests from the inbound data and forward them to the
138 /// application layer by invoking the application layer callback registered
139 /// with the listener.
140 ///
141 /// The application layer callback is structured around a nested class,
142 /// RequestReceiveHandler.  It consists of single, abstract operator() which
143 /// accepts a result code and a pointer to a NameChangeRequest as parameters.
144 /// In order to receive inbound NCRs, a caller implements a derivation of the
145 /// RequestReceiveHandler and supplies an instance of this derivation to the
146 /// NameChangeListener constructor. This "registers" the handler with the
147 /// listener.
148 ///
149 /// To begin listening, the caller invokes the listener's startListener()
150 /// method, passing in an IOService instance.  This in turn will pass the
151 /// IOService into the virtual method, open().  The open method is where the
152 /// listener derivation performs the steps necessary to prepare its IO source
153 /// for reception (e.g. opening a socket, connecting to a database).
154 ///
155 /// Assuming the open is successful, startListener will call receiveNext, to
156 /// initiate an asynchronous receive.  This method calls the virtual method,
157 /// doReceive().  The listener derivation uses doReceive to instigate an IO
158 /// layer asynchronous receive passing in its IO layer callback to
159 /// handle receive events from the IO source.
160 ///
161 /// As stated earlier, the derivation's NameChangeRequest completion handler
162 /// MUST invoke the application layer handler registered with the listener.
163 /// This is done by passing in either a success status and a populated
164 /// NameChangeRequest or an error status and an empty request into the
165 /// listener's invokeRecvHandler method. This is the mechanism by which the
166 /// listener's caller is handed inbound NCRs.
167 class NameChangeListener {
168 public:
169 
170     /// @brief Defines the outcome of an asynchronous NCR receive
171     enum Result {
172         SUCCESS,
173         TIME_OUT,
174         STOPPED,
175         ERROR
176     };
177 
178     /// @brief Abstract class for defining application layer receive callbacks.
179     ///
180     /// Applications which will receive NameChangeRequests must provide a
181     /// derivation of this class to the listener constructor in order to
182     /// receive NameChangeRequests.
183     class RequestReceiveHandler {
184     public:
185 
186         /// @brief Function operator implementing a NCR receive callback.
187         ///
188         /// This method allows the application to receive the inbound
189         /// NameChangeRequests. It is intended to function as a hand off of
190         /// information and should probably not be time-consuming.
191         ///
192         /// @param result contains that receive outcome status.
193         /// @param ncr is a pointer to the newly received NameChangeRequest if
194         /// result is NameChangeListener::SUCCESS.  It is indeterminate other
195         /// wise.
196         ///
197         /// @throw This method MUST NOT throw.
198         virtual void operator ()(const Result result,
199                                  NameChangeRequestPtr& ncr) = 0;
200 
~RequestReceiveHandler()201         virtual ~RequestReceiveHandler() {
202         }
203     };
204 
205     /// @brief Constructor
206     ///
207     /// @param recv_handler is a pointer the application layer handler to be
208     /// invoked each time a NCR is received or a receive error occurs.
209     NameChangeListener(RequestReceiveHandler& recv_handler);
210 
211     /// @brief Destructor
~NameChangeListener()212     virtual ~NameChangeListener() {
213     };
214 
215     /// @brief Prepares the IO for reception and initiates the first receive.
216     ///
217     /// Calls the derivation's open implementation to initialize the IO layer
218     /// source for receiving inbound requests.  If successful, it starts the
219     /// first asynchronous read by receiveNext.
220     ///
221     /// @param io_service is the IOService that will handle IO event processing.
222     ///
223     /// @throw NcrListenError if the listener is already "listening" or
224     /// in the event the open or doReceive methods fail.
225     void startListening(isc::asiolink::IOService& io_service);
226 
227     /// @brief Closes the IO source and stops listen logic.
228     ///
229     /// Calls the derivation's implementation of close and marks the state
230     /// as not listening.
231     void stopListening();
232 
233 protected:
234     /// @brief Initiates an asynchronous receive
235     ///
236     /// Sets context information to indicate that IO is in progress and invokes
237     /// the derivation's asynchronous receive method, doReceive.  Note doReceive
238     /// should not be called outside this method to ensure context information
239     /// integrity.
240     ///
241     /// @throw Derivation's doReceive method may throw isc::Exception upon
242     /// error.
243     void receiveNext();
244 
245     /// @brief Calls the NCR receive handler registered with the listener.
246     ///
247     /// This is the hook by which the listener's caller's NCR receive handler
248     /// is called.  This method MUST be invoked by the derivation's
249     /// implementation of doReceive.
250     ///
251     /// NOTE:
252     /// The handler invoked by this method MUST NOT THROW. The handler is
253     /// at application level and should trap and handle any errors at
254     /// that level, rather than throw exceptions.  If an error has occurred
255     /// prior to invoking the handler, it will be expressed in terms a failed
256     /// result being passed to the handler, not a throw.  Therefore any
257     /// exceptions at the handler level are application issues and should be
258     /// dealt with at that level.
259     ///
260     /// This method does wrap the handler invocation within a try-catch
261     /// block as a fail-safe.  The exception will be logged but the
262     /// receive logic will continue.  What this implies is that continued
263     /// operation may or may not succeed as the application has violated
264     /// the interface contract.
265     ///
266     /// @param result contains that receive outcome status.
267     /// @param ncr is a pointer to the newly received NameChangeRequest if
268     /// result is NameChangeListener::SUCCESS.  It is indeterminate other
269     /// wise.
270     void invokeRecvHandler(const Result result, NameChangeRequestPtr& ncr);
271 
272     /// @brief Abstract method which opens the IO source for reception.
273     ///
274     /// The derivation uses this method to perform the steps needed to
275     /// prepare the IO source to receive requests.
276     ///
277     /// @param io_service is the IOService that process IO events.
278     ///
279     /// @throw If the implementation encounters an error it MUST
280     /// throw it as an isc::Exception or derivative.
281     virtual void open(isc::asiolink::IOService& io_service) = 0;
282 
283     /// @brief Abstract method which closes the IO source.
284     ///
285     /// The derivation uses this method to perform the steps needed to
286     /// "close" the IO source.
287     ///
288     /// @throw If the implementation encounters an error it  MUST
289     /// throw it as an isc::Exception or derivative.
290     virtual void close() = 0;
291 
292     /// @brief Initiates an IO layer asynchronous read.
293     ///
294     /// The derivation uses this method to perform the steps needed to
295     /// initiate an asynchronous read of the IO source with the
296     /// derivation's IO layer handler as the IO completion callback.
297     ///
298     /// @throw If the implementation encounters an error it  MUST
299     /// throw it as an isc::Exception or derivative.
300     virtual void doReceive() = 0;
301 
302 public:
303 
304     /// @brief Returns true if the listener is listening, false otherwise.
305     ///
306     /// A true value indicates that the IO source has been opened successfully,
307     /// and that receive loop logic is active.  This implies that closing the
308     /// IO source will interrupt that operation, resulting in a callback
309     /// invocation.
310     ///
311     /// @return The listening mode.
amListening()312     bool amListening() const {
313         return (listening_);
314     }
315 
316     /// @brief Returns true if the listener has an IO call in progress.
317     ///
318     /// A true value indicates that the listener has an asynchronous IO in
319     /// progress which will complete at some point in the future. Completion
320     /// of the call will invoke the registered callback.  It is important to
321     /// understand that the listener and its related objects should not be
322     /// deleted while there is an IO call pending.  This can result in the
323     /// IO service attempting to invoke methods on objects that are no longer
324     /// valid.
325     ///
326     /// @return The IO pending flag.
isIoPending()327     bool isIoPending() const {
328         return (io_pending_);
329     }
330 
331 private:
332     /// @brief Sets the listening indicator to the given value.
333     ///
334     /// Note, this method is private as it is used the base class is solely
335     /// responsible for managing the state.
336     ///
337     /// @param value is the new value to assign to the indicator.
setListening(bool value)338     void setListening(bool value) {
339         listening_ = value;
340     }
341 
342     /// @brief Indicates if the listener is in listening mode.
343     bool listening_;
344 
345     /// @brief Indicates that listener has an async IO pending completion.
346     bool io_pending_;
347 
348     /// @brief Application level NCR receive completion handler.
349     RequestReceiveHandler& recv_handler_;
350 };
351 
352 /// @brief Defines a smart pointer to an instance of a listener.
353 typedef boost::shared_ptr<NameChangeListener> NameChangeListenerPtr;
354 
355 
356 /// @brief Thrown when a NameChangeSender encounters an error.
357 class NcrSenderError : public isc::Exception {
358 public:
NcrSenderError(const char * file,size_t line,const char * what)359     NcrSenderError(const char* file, size_t line, const char* what) :
360         isc::Exception(file, line, what) { };
361 };
362 
363 /// @brief Exception thrown if an error occurs during IO source open.
364 class NcrSenderOpenError : public isc::Exception {
365 public:
NcrSenderOpenError(const char * file,size_t line,const char * what)366     NcrSenderOpenError(const char* file, size_t line, const char* what) :
367         isc::Exception(file, line, what) { };
368 };
369 
370 /// @brief Exception thrown if an error occurs initiating an IO send.
371 class NcrSenderQueueFull : public isc::Exception {
372 public:
NcrSenderQueueFull(const char * file,size_t line,const char * what)373     NcrSenderQueueFull(const char* file, size_t line, const char* what) :
374         isc::Exception(file, line, what) { };
375 };
376 
377 /// @brief Exception thrown if an error occurs initiating an IO send.
378 class NcrSenderSendError : public isc::Exception {
379 public:
NcrSenderSendError(const char * file,size_t line,const char * what)380     NcrSenderSendError(const char* file, size_t line, const char* what) :
381         isc::Exception(file, line, what) { };
382 };
383 
384 
385 /// @brief Abstract interface for sending NameChangeRequests.
386 ///
387 /// NameChangeSender provides the means to:
388 /// - Supply a callback to invoke upon completing the delivery of an NCR or a
389 /// send error
390 /// - Start sending using a given IOService instance to process events
391 /// - Queue NCRs for delivery
392 /// - Stop sending
393 ///
394 /// It implements the high level logic flow to queue requests for delivery,
395 /// and ship them one at a time, waiting for the send to complete prior to
396 /// sending the next request in the queue.  If a send fails, the request
397 /// will remain at the front of queue and the send will be retried
398 /// endlessly unless the caller dequeues the request.  Note, it is presumed that
399 /// a send failure is some form of IO error such as loss of connectivity and
400 /// not a message content error.  It should not be possible to queue an invalid
401 /// message.
402 ///
403 /// It should be noted that once a request is placed onto the send queue it
404 /// will remain there until one of three things occur:
405 ///     * It is successfully delivered
406 ///     * @c NameChangeSender::skipNext() is called
407 ///     * @c NameChangeSender::clearSendQueue() is called
408 ///
409 /// The queue contents are preserved across start and stop listening
410 /// transitions. This is to provide for error recovery without losing
411 /// undelivered requests.
412 
413 /// It provides virtual methods so derivations may supply implementations to
414 /// open the appropriate IO sink, perform a send, and close the IO sink.
415 ///
416 /// The overall design is based on a callback chain.  The sender's caller (the
417 /// application) supplies an "application" layer callback through which it will
418 /// be given send completion notifications. The sender derivation will employ
419 /// its own callback at the IO layer to process send events from its IO sink.
420 /// This callback is expected to forward the outcome of each asynchronous send
421 /// to the application layer by invoking the application layer callback
422 /// registered with the sender.
423 ///
424 /// The application layer callback is structured around a nested class,
425 /// RequestSendHandler.  It consists of single, abstract operator() which
426 /// accepts a result code and a pointer to a NameChangeRequest as parameters.
427 /// In order to receive send completion notifications, a caller implements a
428 /// derivation of the RequestSendHandler and supplies an instance of this
429 /// derivation to the NameChangeSender constructor. This "registers" the
430 /// handler with the sender.
431 ///
432 /// To begin sending, the caller invokes the listener's startSending()
433 /// method, passing in an IOService instance.  This in turn will pass the
434 /// IOService into the virtual method, open().  The open method is where the
435 /// sender derivation performs the steps necessary to prepare its IO sink for
436 /// output (e.g. opening a socket, connecting to a database).  At this point,
437 /// the sender is ready to send messages.
438 ///
439 /// In order to send a request, the application layer invokes the sender
440 /// method, sendRequest(), passing in the NameChangeRequest to send.  This
441 /// method places the request onto the back of the send queue, and then invokes
442 /// the sender method, sendNext().
443 ///
444 /// If there is already a send in progress when sendNext() is called, the method
445 /// will return immediately rather than initiate the next send.  This is to
446 /// ensure that sends are processed sequentially.
447 ///
448 /// If there is not a send in progress and the send queue is not empty,
449 /// the sendNext method will pass the NCR at the front of the send queue into
450 /// the virtual doSend() method.
451 ///
452 /// The sender derivation uses this doSend() method to instigate an IO layer
453 /// asynchronous send with its IO layer callback to handle send events from its
454 /// IO sink.
455 ///
456 /// As stated earlier, the derivation's IO layer callback MUST invoke the
457 /// application layer handler registered with the sender.  This is done by
458 /// passing in  a status indicating the outcome of the send into the sender's
459 /// invokeSendHandler method. This is the mechanism by which the sender's
460 /// caller is handed outbound notifications.
461 
462 /// After invoking the application layer handler, the invokeSendHandler method
463 /// will call the sendNext() method to initiate the next send.  This ensures
464 /// that requests continue to dequeue and ship.
465 ///
466 class NameChangeSender {
467 public:
468 
469     /// @brief Defines the type used for the request send queue.
470     typedef std::deque<NameChangeRequestPtr> SendQueue;
471 
472     /// @brief Defines a default maximum number of entries in the send queue.
473     static const size_t MAX_QUEUE_DEFAULT = 1024;
474 
475     /// @brief Defines the outcome of an asynchronous NCR send.
476     enum Result {
477         SUCCESS,
478         TIME_OUT,
479         STOPPED,
480         ERROR
481     };
482 
483     /// @brief Abstract class for defining application layer send callbacks.
484     ///
485     /// Applications which will send NameChangeRequests must provide a
486     /// derivation of this class to the sender constructor in order to
487     /// receive send outcome notifications.
488     class RequestSendHandler {
489     public:
490 
491         /// @brief Function operator implementing a NCR send callback.
492         ///
493         /// This method allows the application to receive the outcome of
494         /// each send.  It is intended to function as a hand off of information
495         /// and should probably not be time-consuming.
496         ///
497         /// @param result contains that send outcome status.
498         /// @param ncr is a pointer to the NameChangeRequest that was
499         /// delivered (or attempted).
500         ///
501         /// @throw This method MUST NOT throw.
502         virtual void operator ()(const Result result,
503                                  NameChangeRequestPtr& ncr) = 0;
504 
~RequestSendHandler()505         virtual ~RequestSendHandler() {
506         }
507     };
508 
509     /// @brief Constructor
510     ///
511     /// @param send_handler is a pointer the application layer handler to be
512     /// invoked each time a NCR send attempt completes.
513     /// @param send_queue_max is the maximum number of entries allowed in the
514     /// send queue.  Once the maximum number is reached, all calls to
515     /// sendRequest will fail with an exception.
516     NameChangeSender(RequestSendHandler& send_handler,
517                      size_t send_queue_max = MAX_QUEUE_DEFAULT);
518 
519     /// @brief Destructor
~NameChangeSender()520     virtual ~NameChangeSender() {
521     }
522 
523     /// @brief Prepares the IO for transmission.
524     ///
525     /// Calls the derivation's open implementation to initialize the IO layer
526     /// sink for sending outbound requests.
527     ///
528     /// @param io_service is the IOService that will handle IO event processing.
529     ///
530     /// @throw NcrSenderError if the sender is already "sending" or
531     /// NcrSenderOpenError if the open fails.
532     void startSending(isc::asiolink::IOService & io_service);
533 
534     /// @brief Closes the IO sink and stops send logic.
535     ///
536     /// Calls the derivation's implementation of close and marks the state
537     /// as not sending.
538     void stopSending();
539 
540     /// @brief Queues the given request to be sent.
541     ///
542     /// The given request is placed at the back of the send queue and then
543     /// sendNext is invoked.
544     ///
545     /// @param ncr is the NameChangeRequest to send.
546     ///
547     /// @throw NcrSenderError if the sender is not in sending state or
548     /// the request is empty; NcrSenderQueueFull if the send queue has reached
549     /// capacity.
550     void sendRequest(NameChangeRequestPtr& ncr);
551 
552     /// @brief Move all queued requests from a given sender into the send queue
553     ///
554     /// Moves all of the entries in the given sender's queue and places them
555     /// into send queue.  This provides a mechanism of reassigning queued
556     /// messages from one sender to another. This is useful for dealing with
557     /// dynamic configuration changes.
558     ///
559     /// @param source_sender from whom the queued messages will be taken
560     ///
561     /// @throw NcrSenderError if either sender is in send mode, if the number of
562     /// messages in the source sender's queue is larger than this sender's
563     /// maximum queue size, or if this sender's queue is not empty.
564     void assumeQueue(NameChangeSender& source_sender);
565 
566     /// @brief Returns a file descriptor suitable for use with select
567     ///
568     /// The value returned is an open file descriptor which can be used with
569     /// select() system call to monitor the sender for IO events.  This allows
570     /// NameChangeSenders to be used in applications which use select, rather
571     /// than IOService to wait for IO events to occur.
572     ///
573     /// @warning Attempting other use of this value may lead to unpredictable
574     /// behavior in the sender.
575     ///
576     /// @return Returns an "open" file descriptor
577     ///
578     /// @throw NcrSenderError if the sender is not in send mode,
579     virtual int getSelectFd() = 0;
580 
581     /// @brief Returns whether or not the sender has IO ready to process.
582     ///
583     /// @return true if the sender has at IO ready, false otherwise.
584     virtual bool ioReady() = 0;
585 
586 private:
587 
588     /// @brief Prepares the IO for transmission in a thread safe context.
589     ///
590     /// @param io_service is the IOService that will handle IO event processing.
591     void startSendingInternal(isc::asiolink::IOService & io_service);
592 
593     /// @brief Queues the given request to be sent in a thread safe context.
594     ///
595     /// @param ncr is the NameChangeRequest to send.
596     ///
597     /// @throw NcrSenderQueueFull if the send queue has reached capacity.
598     void sendRequestInternal(NameChangeRequestPtr& ncr);
599 
600     /// @brief Move all queued requests from a given sender into the send queue
601     /// in a thread safe context.
602     ///
603     /// @param source_sender from whom the queued messages will be taken
604     ///
605     /// @throw NcrSenderError if this sender's queue is not empty.
606     void assumeQueueInternal(NameChangeSender& source_sender);
607 
608     /// @brief Calls the NCR send completion handler registered with the
609     /// sender in a thread safe context.
610     ///
611     /// @param result contains that send outcome status.
612     void invokeSendHandlerInternal(const NameChangeSender::Result result);
613 
614     /// @brief Removes the request at the front of the send queue in a thread
615     /// safe context.
616     void skipNextInternal();
617 
618     /// @brief Returns the number of entries currently in the send queue in a
619     /// thread safe context.
620     ///
621     /// @return the queue size.
622     size_t getQueueSizeInternal() const;
623 
624     /// @brief Returns the entry at a given position in the queue in a thread
625     /// safe context.
626     ///
627     /// @return Pointer reference to the queue entry.
628     ///
629     /// @throw NcrSenderError if the given index is beyond the
630     /// end of the queue.
631     const NameChangeRequestPtr& peekAtInternal(const size_t index) const;
632 
633 protected:
634 
635     /// @brief Dequeues and sends the next request on the send queue in a thread
636     /// safe context.
637     ///
638     /// If there is already a send in progress just return. If there is not
639     /// a send in progress and the send queue is not empty the grab the next
640     /// message on the front of the queue and call doSend().
641     void sendNext();
642 
643     /// @brief Calls the NCR send completion handler registered with the
644     /// sender.
645     ///
646     /// This is the hook by which the sender's caller's NCR send completion
647     /// handler is called.  This method MUST be invoked by the derivation's
648     /// implementation of doSend.  Note that if the send was a success, the
649     /// entry at the front of the queue is removed from the queue.
650     /// If not we leave it there so we can retry it.  After we invoke the
651     /// handler we clear the pending ncr value and queue up the next send.
652     ///
653     /// NOTE:
654     /// The handler invoked by this method MUST NOT THROW. The handler is
655     /// application level logic and should trap and handle any errors at
656     /// that level, rather than throw exceptions.  If IO errors have occurred
657     /// prior to invoking the handler, they are expressed in terms a failed
658     /// result being passed to the handler.  Therefore any exceptions at the
659     /// handler level are application issues and should be dealt with at that
660     /// level.
661     ///
662     /// This method does wrap the handler invocation within a try-catch
663     /// block as a fail-safe.  The exception will be logged but the
664     /// send logic will continue.  What this implies is that continued
665     /// operation may or may not succeed as the application has violated
666     /// the interface contract.
667     ///
668     /// @param result contains that send outcome status.
669     void invokeSendHandler(const NameChangeSender::Result result);
670 
671     /// @brief Abstract method which opens the IO sink for transmission.
672     ///
673     /// The derivation uses this method to perform the steps needed to
674     /// prepare the IO sink to send requests.
675     ///
676     /// @param io_service is the IOService that process IO events.
677     ///
678     /// @throw If the implementation encounters an error it MUST
679     /// throw it as an isc::Exception or derivative.
680     virtual void open(isc::asiolink::IOService& io_service) = 0;
681 
682     /// @brief Abstract method which closes the IO sink.
683     ///
684     /// The derivation uses this method to perform the steps needed to
685     /// "close" the IO sink.
686     ///
687     /// @throw If the implementation encounters an error it MUST
688     /// throw it as an isc::Exception or derivative.
689     virtual void close() = 0;
690 
691     /// @brief Initiates an IO layer asynchronous send
692     ///
693     /// The derivation uses this method to perform the steps needed to
694     /// initiate an asynchronous send through the IO sink of the given NCR.
695     ///
696     /// @param ncr is a pointer to the NameChangeRequest to send.
697     /// derivation's IO layer handler as the IO completion callback.
698     ///
699     /// @throw If the implementation encounters an error it MUST
700     /// throw it as an isc::Exception or derivative.
701     virtual void doSend(NameChangeRequestPtr& ncr) = 0;
702 
703 public:
704 
705     /// @brief Removes the request at the front of the send queue
706     ///
707     /// This method can be used to avoid further retries of a failed
708     /// send. It is provided primarily as a just-in-case measure. Since
709     /// a failed send results in the same request being retried continuously
710     /// this method makes it possible to remove that entry, causing the
711     /// subsequent entry in the queue to be attempted on the next send.
712     /// It is presumed that sends will only fail due to some sort of
713     /// communications issue. In the unlikely event that a request is
714     /// somehow tainted and causes an send failure based on its content,
715     /// this method provides a means to remove the message.
716     void skipNext();
717 
718     /// @brief Flushes all entries in the send queue
719     ///
720     /// This method can be used to discard all of the NCRs currently in the
721     /// the send queue.  Note it may not be called while the sender is in
722     /// the sending state.
723     ///
724     /// @throw NcrSenderError if called and sender is in sending state.
725     void clearSendQueue();
726 
727     /// @brief Returns true if the sender is in send mode, false otherwise.
728     ///
729     /// A true value indicates that the IO sink has been opened successfully,
730     /// and that send loop logic is active.
731     ///
732     /// @return The send mode.
amSending()733     bool amSending() const {
734         return (sending_);
735     }
736 
737     /// @brief Returns true when a send is in progress.
738     ///
739     /// A true value indicates that a request is actively in the process of
740     /// being delivered.
741     ///
742     /// @return The send in progress flag.
743     bool isSendInProgress() const;
744 
745     /// @brief Returns the maximum number of entries allowed in the send queue.
746     ///
747     /// @return The queue maximum size.
getQueueMaxSize()748     size_t getQueueMaxSize() const  {
749         return (send_queue_max_);
750     }
751 
752     /// @brief Sets the maximum queue size to the given value.
753     ///
754     /// Sets the maximum number of entries allowed in the queue to the
755     /// the given value.
756     ///
757     /// @param new_max the new value to use as the maximum
758     ///
759     /// @throw NcrSenderError if the value is less than one.
760     void setQueueMaxSize(const size_t new_max);
761 
762     /// @brief Returns the number of entries currently in the send queue.
763     ///
764     /// @return The queue size.
765     size_t getQueueSize() const;
766 
767     /// @brief Returns the entry at a given position in the queue.
768     ///
769     /// Note that the entry is not removed from the queue.
770     ///
771     /// @param index the index of the entry in the queue to fetch.
772     /// Valid values are 0 (front of the queue) to (queue size - 1).
773     ///
774     /// @return Pointer reference to the queue entry.
775     ///
776     /// @throw NcrSenderError if the given index is beyond the
777     /// end of the queue.
778     const NameChangeRequestPtr& peekAt(const size_t index) const;
779 
780     /// @brief Processes sender IO events
781     ///
782     /// Executes at most one ready handler on the sender's IO service. If
783     /// no handlers are ready it returns immediately.
784     ///
785     /// @warning - Running all ready handlers, in theory, could process all
786     /// messages currently queued.
787     ///
788     /// NameChangeSender daisy chains requests together in its completion
789     /// by one message completion's handler initiating the next message's send.
790     /// When using UDP, a send immediately marks its event handler as ready
791     /// to run.  If this occurs inside a call to ioservice::poll() or run(),
792     /// that event will also be run.  If that handler calls UDP send then
793     /// that send's handler will be marked ready and executed and so on.  If
794     /// there were 1000 messages in the queue then all them would be sent from
795     /// within the context of one call to runReadyIO().
796     /// By running only one handler at time, we ensure that NCR IO activity
797     /// doesn't starve other processing.  It is unclear how much of a real
798     /// threat this poses but for now it is best to err on the side of caution.
799     virtual void runReadyIO();
800 
801 protected:
802 
803     /// @brief Returns a reference to the send queue.
804     ///
805     /// @return The send queue.
getSendQueue()806     SendQueue& getSendQueue() {
807         return (send_queue_);
808     }
809 
810 private:
811 
812     /// @brief Sets the sending indicator to the given value.
813     ///
814     /// Note, this method is private as it is used the base class is solely
815     /// responsible for managing the state.
816     ///
817     /// @param value is the new value to assign to the indicator.
setSending(bool value)818     void setSending(bool value) {
819         sending_ = value;
820     }
821 
822     /// @brief Boolean indicator which tracks sending status.
823     bool sending_;
824 
825     /// @brief A pointer to registered send completion handler.
826     RequestSendHandler& send_handler_;
827 
828     /// @brief Maximum number of entries permitted in the send queue.
829     size_t send_queue_max_;
830 
831     /// @brief Queue of the requests waiting to be sent.
832     SendQueue send_queue_;
833 
834     /// @brief Pointer to the request which is in the process of being sent.
835     NameChangeRequestPtr ncr_to_send_;
836 
837     /// @brief Pointer to the IOService currently being used by the sender.
838     /// @note We need to remember the io_service but we receive it by
839     /// reference.  Use a raw pointer to store it.  This value should never be
840     /// exposed and is only valid while in send mode.
841     asiolink::IOService* io_service_;
842 
843     /// @brief The mutex used to protect internal state.
844     const boost::scoped_ptr<std::mutex> mutex_;
845 };
846 
847 /// @brief Defines a smart pointer to an instance of a sender.
848 typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr;
849 
850 }  // namespace dhcp_ddns
851 }  // namespace isc
852 
853 #endif
854