/* * psocksxx - A C++ wrapper for POSIX sockets * Copyright (C) 2013 Uditha Atukorala * * This software library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This software library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this software library. If not, see . * */ #include "sockstreambuf.h" #include #include // Mac OSX does not define MSG_NOSIGNAL #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif namespace psocksxx { sockstreambuf::sockstreambuf() throw() : _socket( -1 ), _bufsize( SOCKSTREAMBUF_SIZE ), _putbacksize( SOCKSTREAMBUF_PUTBACK_SIZE ) { // initialise defaults init_defaults(); // initialise internal buffers init_buffers(); } sockstreambuf::sockstreambuf( socket_t socket ) throw() : _bufsize( SOCKSTREAMBUF_SIZE ), _putbacksize( SOCKSTREAMBUF_PUTBACK_SIZE ) { // update local copy of the socket data _socket = socket; // initialise defaults init_defaults(); // initialise internal buffers init_buffers(); } sockstreambuf::~sockstreambuf() { // close any open sockets close(); // cleanup buffers cleanup_buffers(); // cleanup timeout if ( _timeout ) { delete _timeout; } } void sockstreambuf::init_defaults() throw() { // timeout structure reference _timeout = NULL; // timed-out status _timed_out = false; } void sockstreambuf::open( socket_domain_t domain, socket_type_t type, socket_protocol_t proto ) throw( sockexception ) { // create a communication endpoint _socket = ::socket( domain, type, proto ); // sanity check if ( _socket == -1 ) { throw sockexception(); } #ifdef SO_NOSIGPIPE // suppress SIGPIPE (Mac OSX) int optval = 1; if ( setsockopt( _socket, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof( optval ) ) != 0 ) { throw sockexception(); } #endif } void sockstreambuf::close() throw() { // sanity check if ( _socket > -1 ) { // sync sync(); // close the socket ::close( _socket ); // update socket data _socket = -1; } } void sockstreambuf::connect( const sockaddr * dest_addr, unsigned int timeout ) throw( sockexception, socktimeoutexception ) { timeval t_val; timeval * t_ptr; // check timeout value if ( timeout > 0 ) { // setup timeval structure t_val.tv_sec = timeout; t_val.tv_usec = 0; // update pointer t_ptr = &t_val; } else { // fall-back to class value t_ptr = _timeout; } // call overloaded connect() connect( dest_addr, t_ptr ); } void sockstreambuf::connect( const sockaddr * dest_addr, timeval * timeout ) throw( sockexception, socktimeoutexception ) { // copy current flags int s_flags = fcntl( _socket, F_GETFL ); // sanity check - affectively we ignore the fcntl() error if ( s_flags == -1 ) { s_flags = 0; } // setup timeout if needed if ( timeout ) { // make the socket non-blocking if ( fcntl( _socket, F_SETFL, ( s_flags | O_NONBLOCK ) ) == -1 ) { throw sockexception(); } } // connect if ( ::connect( _socket, dest_addr->psockaddr(), dest_addr->size() ) != 0 ) { throw sockexception(); } // check for timeout if set if ( timeout ) { if (! ready( timeout ) ) { // shutdown ::shutdown( _socket, 2 ); // throw a timeout exception if ( _timed_out ) { throw socktimeoutexception( timeout, "sockstreambuf::connect()" ); } } // reset flags back to what they were fcntl( _socket, F_SETFL, s_flags ); } } void sockstreambuf::bind( const sockaddr * bind_addr, bool reuse_addr ) throw( sockexception ) { // set socket options - SO_REUSEADDR if ( reuse_addr ) { int optval = 1; if ( setsockopt( _socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof( optval ) ) != 0 ) { throw sockexception(); } } // bind if ( ::bind( _socket, bind_addr->psockaddr(), bind_addr->size() ) != 0 ) { throw sockexception(); } } void sockstreambuf::listen( int backlog ) throw( sockexception ) { if ( ::listen( _socket, backlog ) != 0 ) { throw sockexception(); } } sockstreambuf::socket_t sockstreambuf::accept() throw( sockexception ) { socket_t peer_sock; if ( ( peer_sock = ::accept( _socket, 0, 0 ) ) < 0 ) { throw sockexception(); } return peer_sock; } const sockstreambuf::socket_t & sockstreambuf::socket() const throw() { return _socket; } const timeval * sockstreambuf::timeout( time_t sec, suseconds_t usec ) throw() { _timeout = new timeval; _timeout->tv_sec = sec; _timeout->tv_usec = usec; return _timeout; } void * sockstreambuf::clear_timeout() throw() { // sanity check if ( _timeout ) { // delete structure delete _timeout; // set a null pointer _timeout = NULL; } return _timeout; } bool sockstreambuf::timedout() const throw() { return _timed_out; } void sockstreambuf::init_buffers() throw() { // allocate output buffer space char * pbuf = new char[_bufsize]; // allocate input buffer space char * gbuf = new char[_bufsize]; // setup output buffer setp( pbuf, pbuf + ( _bufsize - 1 ) ); // setup input buffer setg( gbuf, gbuf, gbuf ); } void sockstreambuf::cleanup_buffers() throw() { // cleanup output buffer delete [] pbase(); // cleanup input buffer delete [] eback(); } int sockstreambuf::flush() throw( socktimeoutexception ) { int flush_size = pptr() - pbase(); bool b_ready = false; // sanity check if ( flush_size > 0 ) { try { b_ready = ready( _timeout, false, true ); } catch ( sockexception &e ) { // couldn't select the socket return eof; } if ( b_ready ) { if ( ::send( _socket, pbase(), flush_size, MSG_NOSIGNAL ) == flush_size ) { pbump( -flush_size ); return flush_size; } } else { // timed out - throw a timeout exception if ( _timed_out ) { throw socktimeoutexception( _timeout, "sockstreambuf::flush()" ); } } } return eof; } int sockstreambuf::sync() throw() { try { // flush buffer if ( flush() != eof ) { return 0; } } catch ( socktimeoutexception &e ) { // communication timeout - suppress the exception } // sync failed return -1; } int sockstreambuf::overflow( int c ) throw( socktimeoutexception ) { // sanity check if ( c != eof ) { // insert the overflowed char into the buffer *pptr() = c; pbump( 1 ); } // flush the buffer - could throw a timeout exception if ( flush() == eof ) { return eof; } return c; } int sockstreambuf::underflow() throw( socktimeoutexception ) { // sanity check - read position before end-of-buffer? if ( gptr() < egptr() ) { return traits_type::to_int_type( *gptr() ); } char * read_buffer; size_t putback_size = gptr() - eback(); size_t readable_size = 0; bool b_ready = false; ssize_t read_size = 0; // sanitise putback size if ( putback_size > _putbacksize ) { putback_size = _putbacksize; } // update read buffer position read_buffer = eback() + putback_size; // calculate read buffer size readable_size = _bufsize - putback_size; // check for availability try { b_ready = ready( _timeout, true, false ); } catch ( sockexception &e ) { // couldn't select the socket return eof; } // read from socket if ( b_ready ) { read_size = ::read( _socket, read_buffer, readable_size ); } else { // timed out - throw a timeout exception if ( _timed_out ) { throw socktimeoutexception( _timeout, "sockstreambuf::overflow()" ); } } // sanity check if ( read_size <= 0 ) { return eof; } // update pointers setg( eback(), read_buffer, read_buffer + read_size ); // return next character return traits_type::to_int_type( *gptr() ); } bool sockstreambuf::ready( timeval * timeout, bool chk_read, bool chk_write ) throw( sockexception ) { // sanity check if ( _socket < 0 ) { throw sockexception( "sockstreambuf::ready(): invalid socket" ); } fd_set fds; fd_set * read_fds = 0; fd_set * write_fds = 0; // timespec structure timespec * t_spec = 0; // set the fd_set so we only check our socket memset( &fds, 0, sizeof( fds ) ); FD_SET( _socket, &fds ); // set the actions we want to check if ( chk_read ) { read_fds = &fds; } if ( chk_write ) { write_fds = &fds; } // reset timed-out status _timed_out = false; // create timespec structure from timeval structure if ( timeout ) { t_spec = new timespec; t_spec->tv_sec = timeout->tv_sec; t_spec->tv_nsec = ( timeout->tv_usec * 1000 ); } // select the socket int s_status = ::pselect( ( _socket + 1 ), read_fds, write_fds, 0, t_spec, 0 ); // cleanup if ( t_spec != 0 ) { delete t_spec; } // check status switch ( s_status ) { case 0: // timed-out _timed_out = true; break; case -1: throw sockexception(); break; default: break; } // sanity check if ( FD_ISSET( _socket, &fds ) ) { return true; } return false; } } /* end of namespace psocksxx */