summaryrefslogtreecommitdiff
path: root/lib/psocksxx/sockstreambuf.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/psocksxx/sockstreambuf.cpp')
-rw-r--r--lib/psocksxx/sockstreambuf.cpp523
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 */
+