LCOV - code coverage report
Current view: top level - asiolink - tcp_socket.h (source / functions) Hit Total Coverage
Test: report.info Lines: 66 74 89.2 %
Date: 2012-05-15 Functions: 21 37 56.8 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 30 68 44.1 %

           Branch data     Line data    Source code
       1                 :            : // Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
       2                 :            : //
       3                 :            : // Permission to use, copy, modify, and/or distribute this software for any
       4                 :            : // purpose with or without fee is hereby granted, provided that the above
       5                 :            : // copyright notice and this permission notice appear in all copies.
       6                 :            : //
       7                 :            : // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
       8                 :            : // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
       9                 :            : // AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
      10                 :            : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
      11                 :            : // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
      12                 :            : // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
      13                 :            : // PERFORMANCE OF THIS SOFTWARE.
      14                 :            : 
      15                 :            : #ifndef __TCP_SOCKET_H
      16                 :            : #define __TCP_SOCKET_H 1
      17                 :            : 
      18                 :            : #ifndef ASIO_HPP
      19                 :            : #error "asio.hpp must be included before including this, see asiolink.h as to why"
      20                 :            : #endif
      21                 :            : 
      22                 :            : #include <log/dummylog.h>
      23                 :            : #include <netinet/in.h>
      24                 :            : #include <sys/socket.h>
      25                 :            : #include <unistd.h>             // for some IPC/network system calls
      26                 :            : 
      27                 :            : #include <algorithm>
      28                 :            : #include <cassert>
      29                 :            : #include <cstddef>
      30                 :            : 
      31                 :            : #include <boost/bind.hpp>
      32                 :            : #include <boost/numeric/conversion/cast.hpp>
      33                 :            : 
      34                 :            : #include <config.h>
      35                 :            : 
      36                 :            : #include <util/buffer.h>
      37                 :            : #include <util/io_utilities.h>
      38                 :            : 
      39                 :            : #include <asiolink/io_asio_socket.h>
      40                 :            : #include <asiolink/io_endpoint.h>
      41                 :            : #include <asiolink/io_service.h>
      42                 :            : #include <asiolink/tcp_endpoint.h>
      43                 :            : 
      44                 :            : namespace isc {
      45                 :            : namespace asiolink {
      46                 :            : 
      47                 :            : /// \brief Buffer Too Large
      48                 :            : ///
      49                 :            : /// Thrown on an attempt to send a buffer > 64k
      50                 :          0 : class BufferTooLarge : public IOError {
      51                 :            : public:
      52                 :            :     BufferTooLarge(const char* file, size_t line, const char* what) :
      53         [ #  # ]:          0 :         IOError(file, line, what) {}
      54                 :            : };
      55                 :            : 
      56                 :            : /// \brief The \c TCPSocket class is a concrete derived class of \c IOAsioSocket
      57                 :            : /// that represents a TCP socket.
      58                 :            : ///
      59                 :            : /// \param C Callback type
      60                 :            : template <typename C>
      61                 :            : class TCPSocket : public IOAsioSocket<C> {
      62                 :            : private:
      63                 :            :     /// \brief Class is non-copyable
      64                 :            :     TCPSocket(const TCPSocket&);
      65                 :            :     TCPSocket& operator=(const TCPSocket&);
      66                 :            : 
      67                 :            : public:
      68                 :            : 
      69                 :            :     /// \brief Constructor from an ASIO TCP socket.
      70                 :            :     ///
      71                 :            :     /// \param socket The ASIO representation of the TCP socket.  It is assumed
      72                 :            :     ///        that the caller will open and close the socket, so these
      73                 :            :     ///        operations are a no-op for that socket.
      74                 :            :     TCPSocket(asio::ip::tcp::socket& socket);
      75                 :            : 
      76                 :            :     /// \brief Constructor
      77                 :            :     ///
      78                 :            :     /// Used when the TCPSocket is being asked to manage its own internal
      79                 :            :     /// socket.  In this case, the open() and close() methods are used.
      80                 :            :     ///
      81                 :            :     /// \param service I/O Service object used to manage the socket.
      82                 :            :     TCPSocket(IOService& service);
      83                 :            : 
      84                 :            :     /// \brief Destructor
      85                 :            :     virtual ~TCPSocket();
      86                 :            : 
      87                 :            :     /// \brief Return file descriptor of underlying socket
      88                 :          6 :     virtual int getNative() const {
      89                 :          6 :         return (socket_.native());
      90                 :            :     }
      91                 :            : 
      92                 :            :     /// \brief Return protocol of socket
      93                 :          6 :     virtual int getProtocol() const {
      94                 :          6 :         return (IPPROTO_TCP);
      95                 :            :     }
      96                 :            : 
      97                 :            :     /// \brief Is "open()" synchronous?
      98                 :            :     ///
      99                 :            :     /// Indicates that the opening of a TCP socket is asynchronous.
     100                 :         20 :     virtual bool isOpenSynchronous() const {
     101                 :         20 :         return (false);
     102                 :            :     }
     103                 :            : 
     104                 :            :     /// \brief Open Socket
     105                 :            :     ///
     106                 :            :     /// Opens the TCP socket.  This is an asynchronous operation, completion of
     107                 :            :     /// which will be signalled via a call to the callback function.
     108                 :            :     ///
     109                 :            :     /// \param endpoint Endpoint to which the socket will connect.
     110                 :            :     /// \param callback Callback object.
     111                 :            :     virtual void open(const IOEndpoint* endpoint, C& callback);
     112                 :            : 
     113                 :            :     /// \brief Send Asynchronously
     114                 :            :     ///
     115                 :            :     /// Calls the underlying socket's async_send() method to send a packet of
     116                 :            :     /// data asynchronously to the remote endpoint.  The callback will be called
     117                 :            :     /// on completion.
     118                 :            :     ///
     119                 :            :     /// \param data Data to send
     120                 :            :     /// \param length Length of data to send
     121                 :            :     /// \param endpoint Target of the send. (Unused for a TCP socket because
     122                 :            :     ///        that was determined when the connection was opened.)
     123                 :            :     /// \param callback Callback object.
     124                 :            :     virtual void asyncSend(const void* data, size_t length,
     125                 :            :                            const IOEndpoint* endpoint, C& callback);
     126                 :            : 
     127                 :            :     /// \brief Receive Asynchronously
     128                 :            :     ///
     129                 :            :     /// Calls the underlying socket's async_receive() method to read a packet
     130                 :            :     /// of data from a remote endpoint.  Arrival of the data is signalled via a
     131                 :            :     /// call to the callback function.
     132                 :            :     ///
     133                 :            :     /// \param data Buffer to receive incoming message
     134                 :            :     /// \param length Length of the data buffer
     135                 :            :     /// \param offset Offset into buffer where data is to be put
     136                 :            :     /// \param endpoint Source of the communication
     137                 :            :     /// \param callback Callback object
     138                 :            :     virtual void asyncReceive(void* data, size_t length, size_t offset,
     139                 :            :                               IOEndpoint* endpoint, C& callback);
     140                 :            : 
     141                 :            :     /// \brief Process received data packet
     142                 :            :     ///
     143                 :            :     /// See the description of IOAsioSocket::receiveComplete for a complete
     144                 :            :     /// description of this method.
     145                 :            :     ///
     146                 :            :     /// \param staging Pointer to the start of the staging buffer.
     147                 :            :     /// \param length Amount of data in the staging buffer.
     148                 :            :     /// \param cumulative Amount of data received before the staging buffer is
     149                 :            :     ///        processed.
     150                 :            :     /// \param offset Unused.
     151                 :            :     /// \param expected unused.
     152                 :            :     /// \param outbuff Output buffer.  Data in the staging buffer is be copied
     153                 :            :     ///        to this output buffer in the call.
     154                 :            :     ///
     155                 :            :     /// \return Always true
     156                 :            :     virtual bool processReceivedData(const void* staging, size_t length,
     157                 :            :                                      size_t& cumulative, size_t& offset,
     158                 :            :                                      size_t& expected,
     159                 :            :                                      isc::util::OutputBufferPtr& outbuff);
     160                 :            : 
     161                 :            :     /// \brief Cancel I/O On Socket
     162                 :            :     virtual void cancel();
     163                 :            : 
     164                 :            :     /// \brief Close socket
     165                 :            :     virtual void close();
     166                 :            : 
     167                 :            : 
     168                 :            : private:
     169                 :            :     // Two variables to hold the socket - a socket and a pointer to it.  This
     170                 :            :     // handles the case where a socket is passed to the TCPSocket on
     171                 :            :     // construction, or where it is asked to manage its own socket.
     172                 :            :     asio::ip::tcp::socket*      socket_ptr_;    ///< Pointer to own socket
     173                 :            :     asio::ip::tcp::socket&      socket_;        ///< Socket
     174                 :            :     bool                        isopen_;        ///< true when socket is open
     175                 :            : 
     176                 :            :     // TODO: Remove temporary buffer
     177                 :            :     // The current implementation copies the buffer passed to asyncSend() into
     178                 :            :     // a temporary buffer and precedes it with a two-byte count field.  As
     179                 :            :     // ASIO should really be just about sending and receiving data, the TCP
     180                 :            :     // code should not do this.  If the protocol using this requires a two-byte
     181                 :            :     // count, it should add it before calling this code.  (This may be best
     182                 :            :     // achieved by altering isc::dns::buffer to have pairs of methods:
     183                 :            :     // getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
     184                 :            :     // methods taking into account a two-byte count field.)
     185                 :            :     //
     186                 :            :     // The option of sending the data in two operations, the count followed by
     187                 :            :     // the data was discounted as that would lead to two callbacks which would
     188                 :            :     // cause problems with the stackless coroutine code.
     189                 :            :     isc::util::OutputBufferPtr   send_buffer_;   ///< Send buffer
     190                 :            : };
     191                 :            : 
     192                 :            : // Constructor - caller manages socket
     193                 :            : 
     194                 :            : template <typename C>
     195                 :            : TCPSocket<C>::TCPSocket(asio::ip::tcp::socket& socket) :
     196                 :         32 :     socket_ptr_(NULL), socket_(socket), isopen_(true), send_buffer_()
     197                 :            : {
     198                 :            : }
     199                 :            : 
     200                 :            : // Constructor - create socket on the fly
     201                 :            : 
     202                 :            : template <typename C>
     203                 :         30 : TCPSocket<C>::TCPSocket(IOService& service) :
     204                 :            :     socket_ptr_(new asio::ip::tcp::socket(service.get_io_service())),
     205 [ +  - ][ +  - ]:         30 :     socket_(*socket_ptr_), isopen_(false)
                 [ +  - ]
     206                 :            : {
     207                 :         30 : }
     208                 :            : 
     209                 :            : // Destructor.  Only delete the socket if we are managing it.
     210                 :            : 
     211                 :            : template <typename C>
     212                 :         90 : TCPSocket<C>::~TCPSocket()
     213                 :            : {
     214 [ +  + ][ +  - ]:         74 :     delete socket_ptr_;
     215                 :         90 : }
     216                 :            : 
     217                 :            : // Open the socket.
     218                 :            : 
     219                 :            : template <typename C> void
     220                 :         21 : TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
     221                 :            : 
     222                 :            :     // Ignore opens on already-open socket.  Don't throw a failure because
     223                 :            :     // of uncertainties as to what precedes whan when using asynchronous I/O.
     224                 :            :     // At also allows us a treat a passed-in socket as a self-managed socket.
     225         [ +  - ]:         21 :     if (!isopen_) {
     226         [ +  - ]:         21 :         if (endpoint->getFamily() == AF_INET) {
     227                 :         21 :             socket_.open(asio::ip::tcp::v4());
     228                 :            :         }
     229                 :            :         else {
     230                 :          0 :             socket_.open(asio::ip::tcp::v6());
     231                 :            :         }
     232                 :         21 :         isopen_ = true;
     233                 :            : 
     234                 :            :         // Set options on the socket:
     235                 :            : 
     236                 :            :         // Reuse address - allow the socket to bind to a port even if the port
     237                 :            :         // is in the TIMED_WAIT state.
     238                 :         21 :         socket_.set_option(asio::socket_base::reuse_address(true));
     239                 :            :     }
     240                 :            : 
     241                 :            :     // Upconvert to a TCPEndpoint.  We need to do this because although
     242                 :            :     // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
     243                 :            :     // contain a method for getting at the underlying endpoint type - that is in
     244                 :            :     /// the derived class and the two classes differ on return type.
     245         [ -  + ]:         21 :     assert(endpoint->getProtocol() == IPPROTO_TCP);
     246                 :            :     const TCPEndpoint* tcp_endpoint =
     247                 :         21 :         static_cast<const TCPEndpoint*>(endpoint);
     248                 :            : 
     249                 :            :     // Connect to the remote endpoint.  On success, the handler will be
     250                 :            :     // called (with one argument - the length argument will default to
     251                 :            :     // zero).
     252         [ +  - ]:         21 :     socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
     253                 :         21 : }
     254                 :            : 
     255                 :            : // Send a message.  Should never do this if the socket is not open, so throw
     256                 :            : // an exception if this is the case.
     257                 :            : 
     258                 :            : template <typename C> void
     259                 :         19 : TCPSocket<C>::asyncSend(const void* data, size_t length,
     260                 :            :     const IOEndpoint*, C& callback)
     261                 :            : {
     262         [ +  - ]:         19 :     if (isopen_) {
     263                 :            : 
     264                 :            :         // Need to copy the data into a temporary buffer and precede it with
     265                 :            :         // a two-byte count field.
     266                 :            :         // TODO: arrange for the buffer passed to be preceded by the count
     267                 :            :         try {
     268                 :            :             // Ensure it fits into 16 bits
     269                 :         19 :             uint16_t count = boost::numeric_cast<uint16_t>(length);
     270                 :            : 
     271                 :            :             // Copy data into a buffer preceded by the count field.
     272 [ +  - ][ +  - ]:         19 :             send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
                 [ #  # ]
     273         [ #  # ]:         19 :             send_buffer_->writeUint16(count);
     274         [ +  - ]:         19 :             send_buffer_->writeData(data, length);
     275                 :            : 
     276                 :            :             // ... and send it
     277 [ +  - ][ +  - ]:         19 :             socket_.async_send(asio::buffer(send_buffer_->getData(),
     278                 :            :                                send_buffer_->getLength()), callback);
     279         [ #  # ]:          0 :         } catch (boost::numeric::bad_numeric_cast&) {
     280 [ #  # ][ #  # ]:          0 :             isc_throw(BufferTooLarge,
     281                 :            :                       "attempt to send buffer larger than 64kB");
     282                 :            :         }
     283                 :            : 
     284                 :            :     } else {
     285         [ #  # ]:          0 :         isc_throw(SocketNotOpen,
     286                 :            :             "attempt to send on a TCP socket that is not open");
     287                 :            :     }
     288                 :         19 : }
     289                 :            : 
     290                 :            : // Receive a message. Note that the "offset" argument is used as an index
     291                 :            : // into the buffer in order to decide where to put the data.  It is up to the
     292                 :            : // caller to initialize the data to zero
     293                 :            : template <typename C> void
     294                 :         58 : TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
     295                 :            :     IOEndpoint* endpoint, C& callback)
     296                 :            : {
     297         [ +  - ]:         58 :     if (isopen_) {
     298                 :            :         // Upconvert to a TCPEndpoint.  We need to do this because although
     299                 :            :         // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
     300                 :            :         // does not contain a method for getting at the underlying endpoint
     301                 :            :         // type - that is in the derived class and the two classes differ on
     302                 :            :         // return type.
     303         [ -  + ]:         58 :         assert(endpoint->getProtocol() == IPPROTO_TCP);
     304                 :         58 :         TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
     305                 :            : 
     306                 :            :         // Write the endpoint details from the communications link.  Ideally
     307                 :            :         // we should make IOEndpoint assignable, but this runs in to all sorts
     308                 :            :         // of problems concerning the management of the underlying Boost
     309                 :            :         // endpoint (e.g. if it is not self-managed, is the copied one
     310                 :            :         // self-managed?) The most pragmatic solution is to let Boost take care
     311                 :            :         // of everything and copy details of the underlying endpoint.
     312                 :        116 :         tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
     313                 :            : 
     314                 :            :         // Ensure we can write into the buffer and if so, set the pointer to
     315                 :            :         // where the data will be written.
     316         [ -  + ]:         58 :         if (offset >= length) {
     317         [ #  # ]:          0 :             isc_throw(BufferOverflow, "attempt to read into area beyond end of "
     318                 :            :                                       "TCP receive buffer");
     319                 :            :         }
     320                 :         58 :         void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
     321                 :            : 
     322                 :            :         // ... and kick off the read.
     323         [ +  - ]:         58 :         socket_.async_receive(asio::buffer(buffer_start, length - offset), callback);
     324                 :            : 
     325                 :            :     } else {
     326         [ #  # ]:          0 :         isc_throw(SocketNotOpen,
     327                 :            :             "attempt to receive from a TCP socket that is not open");
     328                 :            :     }
     329                 :         58 : }
     330                 :            : 
     331                 :            : // Is the receive complete?
     332                 :            : 
     333                 :            : template <typename C> bool
     334                 :         58 : TCPSocket<C>::processReceivedData(const void* staging, size_t length,
     335                 :            :                                   size_t& cumulative, size_t& offset,
     336                 :            :                                   size_t& expected,
     337                 :            :                                   isc::util::OutputBufferPtr& outbuff)
     338                 :            : {
     339                 :            :     // Point to the data in the staging buffer and note how much there is.
     340                 :         58 :     const uint8_t* data = static_cast<const uint8_t*>(staging);
     341                 :         58 :     size_t data_length = length;
     342                 :            : 
     343                 :            :     // Is the number is "expected" valid?  It won't be unless we have received
     344                 :            :     // at least two bytes of data in total for this set of receives.
     345         [ +  + ]:         58 :     if (cumulative < 2) {
     346                 :            : 
     347                 :            :         // "expected" is not valid.  Did this read give us enough data to
     348                 :            :         // work it out?
     349                 :         20 :         cumulative += length;
     350         [ +  + ]:         20 :         if (cumulative < 2) {
     351                 :            : 
     352                 :            :             // Nope, still not valid.  This must have been the first packet and
     353                 :            :             // was only one byte long.  Tell the fetch code to read the next
     354                 :            :             // packet into the staging buffer beyond the data that is already
     355                 :            :             // there so that the next time we are called we have a complete
     356                 :            :             // TCP count.
     357                 :          1 :             offset = cumulative;
     358                 :          1 :             return (false);
     359                 :            :         }
     360                 :            : 
     361                 :            :         // Have enough data to interpret the packet count, so do so now.
     362                 :         19 :         expected = isc::util::readUint16(data);
     363                 :            : 
     364                 :            :         // We have two bytes less of data to process.  Point to the start of the
     365                 :            :         // data and adjust the packet size.  Note that at this point,
     366                 :            :         // "cumulative" is the true amount of data in the staging buffer, not
     367                 :            :         // "length".
     368                 :         19 :         data += 2;
     369                 :         19 :         data_length = cumulative - 2;
     370                 :            :     } else {
     371                 :            : 
     372                 :            :         // Update total amount of data received.
     373                 :         38 :         cumulative += length;
     374                 :            :     }
     375                 :            : 
     376                 :            :     // Regardless of anything else, the next read goes into the start of the
     377                 :            :     // staging buffer.
     378                 :         57 :     offset = 0;
     379                 :            : 
     380                 :            :     // Work out how much data we still have to put in the output buffer. (This
     381                 :            :     // could be zero if we have just interpreted the TCP count and that was
     382                 :            :     // set to zero.)
     383         [ +  - ]:         57 :     if (expected >= outbuff->getLength()) {
     384                 :            : 
     385                 :            :         // Still need data in the output packet.  Copy what we can from the
     386                 :            :         // staging buffer to the output buffer.
     387                 :         57 :         size_t copy_amount = std::min(expected - outbuff->getLength(), data_length);
     388                 :         57 :         outbuff->writeData(data, copy_amount);
     389                 :            :     }
     390                 :            : 
     391                 :            :     // We can now say if we have all the data.
     392                 :         58 :     return (expected == outbuff->getLength());
     393                 :            : }
     394                 :            : 
     395                 :            : // Cancel I/O on the socket.  No-op if the socket is not open.
     396                 :            : 
     397                 :            : template <typename C> void
     398                 :         21 : TCPSocket<C>::cancel() {
     399         [ +  + ]:         21 :     if (isopen_) {
     400                 :          6 :         socket_.cancel();
     401                 :            :     }
     402                 :         21 : }
     403                 :            : 
     404                 :            : // Close the socket down.  Can only do this if the socket is open and we are
     405                 :            : // managing it ourself.
     406                 :            : 
     407                 :            : template <typename C> void
     408                 :         36 : TCPSocket<C>::close() {
     409 [ +  + ][ +  - ]:         36 :     if (isopen_ && socket_ptr_) {
     410                 :         21 :         socket_.close();
     411                 :         21 :         isopen_ = false;
     412                 :            :     }
     413                 :         36 : }
     414                 :            : 
     415                 :            : } // namespace asiolink
     416                 :            : } // namespace isc
     417                 :            : 
     418                 :            : #endif // __TCP_SOCKET_H

Generated by: LCOV version 1.9