diff options
Diffstat (limited to 'lib/psocksxx/sockstreambuf.cpp')
-rw-r--r-- | lib/psocksxx/sockstreambuf.cpp | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/lib/psocksxx/sockstreambuf.cpp b/lib/psocksxx/sockstreambuf.cpp new file mode 100644 index 0000000..c0a0c81 --- /dev/null +++ b/lib/psocksxx/sockstreambuf.cpp @@ -0,0 +1,523 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +#include "sockstreambuf.h" + +#include <fcntl.h> +#include <cstring> + +// 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 != 0 ) { + delete _timeout; + } + + } + + + void sockstreambuf::init_defaults() throw() { + + // timeout structure reference + _timeout = 0; + + // 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 > 0 ) { + + // 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 > 0 ) { + + 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 != 0 ) { + + // delete structure + delete _timeout; + + // set a null pointer + _timeout = 0; + + } + + 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 != 0 ) { + 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 */ + |