1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
// Copyright (C) 2013-2024 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_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 boost::system::error_code error_code,
///                     const size_t bytes_transferred);
/// @endcode
///
/// Upon completion of the service, the callback instance's operator() is
/// invoked by the asio layer.  It is given both a outcome result and the
/// number of bytes either read or written, to or from the buffer supplied
/// to the service.
///
/// Typically, an asiolink based implementation would simply implement the
/// callback operator directly.  However, the nature of the asiolink library
/// is such that the callback object may be copied several times during course
/// of a service invocation.  This implies that any class being used as a
/// callback class must be copyable.  This is not always desirable.  In order
/// to separate the callback class from the NameChangeRequest, the construct
/// defines the UDPCallback class for use as a copyable, callback object.
///
/// The UDPCallback class provides the asiolink layer callback operator(),
/// which is invoked by the asiolink layer upon service completion.  It
/// contains:
///    * a pointer to the transfer buffer
///    * the capacity of the transfer buffer
///    * a IO layer outcome result
///    * the number of bytes transferred
///    * a method pointer to a NameChangeRequest layer completion handler
///
/// This last item, is critical. It points to an instance method that
/// will be invoked by the UDPCallback operator.  This provides access to
/// the outcome of the service call to the NameChangeRequest layer without
/// that layer being used as the actual callback object.
///
/// The completion handler method signature is codified in the typedef,
/// UDPCompletionHandler, and must be as follows:
///
/// @code
///     void(const bool, const UDPCallback*)
/// @endcode
///
/// Note that is accepts two parameters.  The first is a boolean indicator
/// which indicates if the service call completed successfully or not.  The
/// second is a pointer to the callback object invoked by the IOService upon
/// completion of the service.  The callback instance will contain all of the
/// pertinent information about the invocation and outcome of the service.
///
/// Using the contents of the callback, it is the responsibility of the
/// UDPCompletionHandler to interpret the results of the service invocation and
/// pass the interpretation to the application layer via either
/// NameChangeListener::invokeRecvHandler in the case of the UDP listener, or
/// NameChangeSender::invokeSendHandler in the case of UDP sender.
///

#include <asiolink/asio_wrapper.h>
#include <asiolink/io_address.h>
#include <asiolink/io_service.h>
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
#include <dhcp_ddns/ncr_io.h>
#include <util/buffer.h>
#include <util/watch_socket.h>

#include <boost/shared_array.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/enable_shared_from_this.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

/// 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 dhcp_ddns {

/// @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 std::function<void(const bool, const UDPCallback*)> UDPCompletionHandler;

/// @brief Defines a dynamically allocated shared array.
typedef boost::shared_array<uint8_t> RawBufferPtr;

typedef boost::shared_ptr<asiolink::UDPEndpoint> UDPEndpointPtr;

/// @brief Implements the callback class passed into UDPSocket calls.
///
/// It serves as the link between the asiolink::UDPSocket asynchronous services
/// and the NameChangeRequest layer. The class provides the asiolink layer
/// callback operator(), which is invoked by the asiolink layer upon service
/// completion.  It contains all of the data pertinent to both the invocation
/// and completion of a service, as well as a pointer to NameChangeRequest
/// layer completion handler to invoke.
///
class UDPCallback {

public:
    /// @brief Container class which stores service invocation related data.
    ///
    /// Because the callback object may be copied numerous times during the
    /// course of service invocation, it does not directly contain data values.
    /// Rather it will retain a shared pointer to an instance of this structure
    /// thus ensuring that all copies of the callback object, ultimately refer
    /// to the same data values.
    struct Data {

        /// @brief Constructor
        ///
        /// @param buffer is a pointer to the data transfer buffer. This is
        /// the buffer data will be written to on a read, or read from on a
        /// send.
        /// @param buf_size is the capacity of the buffer
        /// @param data_source storage for UDP endpoint which supplied the data
        Data(RawBufferPtr& buffer, const size_t buf_size, UDPEndpointPtr& data_source)
            : buffer_(buffer), buf_size_(buf_size), data_source_(data_source),
              put_len_(0), error_code_(), bytes_transferred_(0) {
        };

        /// @brief A pointer to the data transfer buffer.
        RawBufferPtr buffer_;

        /// @brief Storage capacity of the buffer.
        size_t buf_size_;

        /// @brief The UDP endpoint that is the origin of the data transferred.
        UDPEndpointPtr data_source_;

        /// @brief Stores this size of the data within the buffer when written
        /// there manually. (See UDPCallback::putData()) .
        size_t put_len_;

        /// @brief Stores the IO layer result code of the completed IO service.
        boost::system::error_code error_code_;

        /// @brief Stores the number of bytes transferred by completed IO
        /// service.
        /// For a read it is the number of bytes written into the
        /// buffer.  For a write it is the number of bytes read from the
        /// buffer.
        size_t bytes_transferred_;

    };

    /// @brief Used as the callback object for UDPSocket services.
    ///
    /// @param buffer is a pointer to the data transfer buffer. This is
    /// the buffer data will be written to on a read, or read from on a
    /// send.
    /// @param buf_size is the capacity of the buffer
    /// @param data_source storage for UDP endpoint which supplied the data
    /// @param handler is a method pointer to the completion handler that
    /// is to be called by the operator() implementation.
    ///
    /// @throw NcrUDPError if either the handler or buffer pointers
    /// are invalid.
    UDPCallback(RawBufferPtr& buffer, const size_t buf_size,
                UDPEndpointPtr& data_source,
                const UDPCompletionHandler& handler);

    /// @brief Operator that will be invoked by the asiolink layer.
    ///
    /// @param error_code is the IO layer result code of the
    /// completed IO service.
    /// @param bytes_transferred is the number of bytes transferred by
    /// completed IO.
    /// For a read it is the number of bytes written into the
    /// buffer.  For a write it is the number of bytes read from the
    /// buffer.
    void operator ()(const boost::system::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.
    boost::system::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 boost::system::error_code value) {
        data_->error_code_  = value;
    }

    /// @brief Returns the data transfer buffer.
    RawBufferPtr getBuffer() const {
        return (data_->buffer_);
    }

    /// @brief Returns the data transfer buffer capacity.
    size_t getBufferSize() const {
        return (data_->buf_size_);
    }

    /// @brief Returns a pointer the data transfer buffer content.
    const uint8_t* getData() const {
        return (data_->buffer_.get());
    }

    /// @brief Copies data into the data transfer buffer.
    ///
    /// Copies the given number of bytes from the given source buffer
    /// into the data transfer buffer, and updates the value of put length.
    /// This method may be used when performing sends to make a copy of
    /// the "raw data" that was shipped (or attempted) accessible to the
    /// upstream callback.
    ///
    /// @param src is a pointer to the data source from which to copy
    /// @param len is the number of bytes to copy
    ///
    /// @throw NcrUDPError if the number of bytes to copy exceeds
    /// the buffer capacity or if the source pointer is invalid.
    void putData(const uint8_t* src, size_t len);

    /// @brief Returns the number of bytes manually written into the
    /// transfer buffer.
    size_t getPutLen() const {
        return (data_->put_len_);
    }

    /// @brief Sets the data source to the given endpoint.
    ///
    /// @param endpoint is the new value to assign to data source.
    void setDataSource(UDPEndpointPtr& endpoint) {<--- Parameter 'endpoint' can be declared as reference to const
        data_->data_source_ = endpoint;
    }

    /// @brief Returns the UDP endpoint that provided the transferred data.
    const UDPEndpointPtr& getDataSource() {
        return (data_->data_source_);
    }

  private:
    /// @brief NameChangeRequest layer completion handler to invoke.
    UDPCompletionHandler handler_;

    /// @brief Shared pointer to the service data container.
    boost::shared_ptr<Data> data_;
};

/// @brief Convenience type for UDP socket based listener
typedef isc::asiolink::UDPSocket<UDPCallback> NameChangeUDPSocket;

/// @brief Provides the ability to receive NameChangeRequests via  UDP socket
///
/// This class is a derivation of the NameChangeListener which is capable of
/// receiving NameChangeRequests through a UDP socket.  The caller need only
/// supply network addressing and a RequestReceiveHandler instance to receive
/// NameChangeRequests asynchronously.
class NameChangeUDPListener : public NameChangeListener {
public:
    /// @brief Defines the maximum size packet that can be received.
    static const size_t RECV_BUF_MAX = isc::asiolink::UDPSocket<UDPCallback>::MIN_SIZE;

    /// @brief Constructor
    ///
    /// @param ip_address is the network address on which to listen
    /// @param port is the UDP port on which to listen
    /// @param format is the wire format of the inbound requests. Currently
    /// only JSON is supported
    /// @param ncr_recv_handler the receive handler object to notify when
    /// a receive completes.
    /// @param reuse_address enables IP address sharing when true
    /// It defaults to false.
    ///
    /// @throw base class throws NcrListenerError if handler is invalid.
    NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
                          const uint32_t port,
                          const NameChangeFormat format,
                          RequestReceiveHandlerPtr ncr_recv_handler,
                          const bool reuse_address = false);

    /// @brief Destructor.
    virtual ~NameChangeUDPListener();

    /// @brief Opens a UDP socket using the given IOService.
    ///
    /// Creates a NameChangeUDPSocket bound to the listener's ip address
    /// and port, that is monitored by the given IOService instance.
    ///
    /// @param io_service the IOService which will monitor the socket.
    ///
    /// @throw NcrUDPError if the open fails.
    virtual void open(const isc::asiolink::IOServicePtr& io_service);<--- Function in derived class

    /// @brief Closes the UDPSocket.
    ///
    /// It first invokes the socket's cancel method which should stop any
    /// pending read and remove the socket callback from the IOService. It
    /// then calls the socket's close method to actually close the socket.
    ///
    /// @throw NcrUDPError if the open fails.
    virtual void close();<--- Function in derived class

    /// @brief Initiates an asynchronous read on the socket.
    ///
    /// Invokes the asyncReceive() method on the socket passing in the
    /// recv_callback_ member's transfer buffer as the receive buffer, and
    /// recv_callback_ itself as the callback object.
    ///
    /// @throw NcrUDPError if the open fails.
    void doReceive();<--- Function in derived class

    /// @brief Implements the NameChangeRequest level receive completion
    /// handler.
    ///
    /// This method is invoked by the UPDCallback operator() implementation,
    /// passing in the boolean success indicator and pointer to itself.
    ///
    /// If the indicator denotes success, then the method will attempt to
    /// to construct a NameChangeRequest from the received data.  If the
    /// construction was successful, it will send the new NCR to the
    /// application layer by calling invokeRecvHandler() with a success
    /// status and a pointer to the new NCR.
    ///
    /// If the buffer contains invalid data such that construction fails,
    /// the method will log the failure and then call doReceive() to start a
    /// initiate the next receive.
    ///
    /// If the indicator denotes failure the method will log the failure and
    /// notify the application layer by calling invokeRecvHandler() with
    /// an error status and an empty pointer.
    ///
    /// @param successful boolean indicator that should be true if the
    /// socket receive completed without error, false otherwise.
    /// @param recv_callback pointer to the callback instance which handled
    /// the socket receive completion.
    void receiveCompletionHandler(const bool successful,
                                  const UDPCallback* recv_callback);

private:

    /// @brief The IO service used to handle events.
    isc::asiolink::IOServicePtr io_service_;

    /// @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<boost::asio::ip::udp::socket> asio_socket_;

    /// @brief NameChangeUDPSocket listening socket
    boost::shared_ptr<NameChangeUDPSocket> socket_;

    /// @brief Pointer to the receive callback
    boost::shared_ptr<UDPCallback> recv_callback_;

    /// @brief Flag which enables the reuse address socket option if true.
    bool reuse_address_;

    ///
    /// @name Copy and constructor assignment operator
    ///
    /// The copy constructor and assignment operator are private to avoid
    /// potential issues with multiple listeners attempting to share sockets
    /// and callbacks.
private:
    NameChangeUDPListener(const NameChangeUDPListener& source);
    NameChangeUDPListener& operator=(const NameChangeUDPListener& source);
    //@}
};

/// @brief Provides the ability to send NameChangeRequests via  UDP socket
///
/// This class is a derivation of the NameChangeSender which is capable of
/// sending NameChangeRequests through a UDP socket.  The caller need only
/// supply network addressing and a RequestSendHandler instance to send
/// NameChangeRequests asynchronously.
class NameChangeUDPSender : public NameChangeSender {
public:

    /// @brief Defines the maximum size packet that can be sent.
    static const size_t SEND_BUF_MAX =  NameChangeUDPListener::RECV_BUF_MAX;

    /// @brief Constructor
    ///
    /// @param ip_address the IP address from which to send
    /// @param port the port from which to send
    /// @param server_address the IP address of the target listener
    /// @param server_port is the IP port  of the target listener
    /// @param format is the wire format of the outbound requests.
    /// @param ncr_send_handler the send handler object to notify when
    /// when a send completes.
    /// @param send_que_max sets the maximum number of entries allowed in
    /// the send queue.
    /// It defaults to NameChangeSender::MAX_QUEUE_DEFAULT
    /// @param reuse_address enables IP address sharing when true
    /// It defaults to false.
    ///
    NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
        const uint32_t port, const isc::asiolink::IOAddress& server_address,
        const uint32_t server_port, const NameChangeFormat format,
        RequestSendHandlerPtr ncr_send_handler,
        const size_t send_que_max = NameChangeSender::MAX_QUEUE_DEFAULT,
        const bool reuse_address = false);

    /// @brief Destructor
    virtual ~NameChangeUDPSender();

    /// @brief Opens a UDP socket using the given IOService.
    ///
    /// Creates a NameChangeUDPSocket bound to the sender's IP address
    /// and port, that is monitored by the given IOService instance.
    ///
    /// @param io_service the IOService which will monitor the socket.
    ///
    /// @throw NcrUDPError if the open fails.
    virtual void open(const isc::asiolink::IOServicePtr& io_service);<--- Function in derived class

    /// @brief Closes the UDPSocket.
    ///
    /// It first invokes the socket's cancel method which should stop any
    /// pending send and remove the socket callback from the IOService. It
    /// then calls the socket's close method to actually close the socket.
    ///
    /// @throw NcrUDPError if the open fails.
    virtual void close();<--- Function in derived class

    /// @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.
    /// @param ncr NameChangeRequest to send.
    virtual void doSend(NameChangeRequestPtr& ncr);<--- Function in derived class

    /// @brief Implements the NameChangeRequest level send completion handler.
    ///
    /// This method is invoked by the UDPCallback operator() implementation,
    /// passing in the boolean success indicator and pointer to itself.
    ///
    /// If the indicator denotes success, then the method will notify the
    /// application layer by calling invokeSendHandler() with a success
    /// status.
    ///
    /// If the indicator denotes failure the method will log the failure and
    /// notify the application layer by calling invokeRecvHandler() with
    /// an error status.
    ///
    /// @param successful boolean indicator that should be true if the
    /// socket send completed without error, false otherwise.
    /// @param send_callback pointer to the callback instance which handled
    /// the socket receive completion.
    void sendCompletionHandler(const bool successful,
                               const UDPCallback* send_callback);

    /// @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
    /// NameChangeUDPSenders 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();<--- Function in derived class

    /// @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();<--- Function in derived class

private:

    /// @brief Closes watch socket if the socket is open.
    ///
    /// This method closes watch socket if its instance exists and if the
    /// socket is open. An error message is logged when this operation fails.
    void closeWatchSocket();

    /// @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<boost::asio::ip::udp::socket> asio_socket_;

    /// @brief NameChangeUDPSocket sending socket.
    boost::shared_ptr<NameChangeUDPSocket> socket_;

    /// @brief Endpoint of the target listener.
    boost::shared_ptr<isc::asiolink::UDPEndpoint> server_endpoint_;

    /// @brief Pointer to the send callback
    boost::shared_ptr<UDPCallback> send_callback_;

    /// @brief Flag which enables the reuse address socket option if true.
    bool reuse_address_;

    /// @brief Pointer to WatchSocket instance supplying the "select-fd".
    util::WatchSocketPtr watch_socket_;
};

} // namespace isc::dhcp_ddns
} // namespace isc

#endif