Logo Search packages:      
Sourcecode: kmess version File versions

directconnectionbase.cpp

/***************************************************************************
                          directconnectionbase.cpp -  description
                             -------------------
    begin                : Tue 12 27 2005
    copyright            : (C) 2005 by Diederik van der Boor
    email                : vdboor --at-- codingdomain.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "directconnectionbase.h"

#include "../../kmessdebug.h"

#include <qcstring.h>

#include <kextsock.h>
#include <ksockaddr.h>

#ifdef KMESSDEBUG_DIRECTCONNECTION
  #define KMESSDEBUG_DIRECTCONNECTION_GENERAL
#endif


int DirectConnectionBase::nextServerPort(6891);


// The constructor
DirectConnectionBase::DirectConnectionBase( QObject *parent, const char *name)
  : QObject(parent, name)
  , authorized_(false)
  , isServer_(false)
  , server_(0)
  , serverPort_(-1)
  , socket_(0)
  , timeout_(false)
{
  connect( &connectionTimer_, SIGNAL(               timeout() ),
           this,              SLOT  ( slotConnectionTimeout() ));
}



// The destructor
DirectConnectionBase::~DirectConnectionBase()
{
  // Close and clean up
  if(socket_ != 0 || server_ != 0)
  {
    closeConnection();
  }

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kdDebug() << "DESTROYED DirectConnectionBase" << endl;
#endif
}



// Close the connection
void DirectConnectionBase::closeConnection()
{
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kdDebug() << "DirectConnectionBase: closing sockets" << endl;
#endif

  // Reset globals
  isServer_   = false;
  authorized_ = false;
  connectionTimer_.stop();

  // Check whether we should emit a signal:
  bool closed = false;

  // Close and delete the socket
  if( socket_ != 0 )
  {
    socket_->flush();
    socket_->closeNow();  // also calls cancelAsyncConnect()
    delete socket_;
    socket_ = 0;
    closed = true;
  }

  // Delete the server.
  if( server_ != 0 )
  {
    server_->closeNow();
    delete server_;
    server_ = 0;
    closed = true;
  }

#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_CLOSE(this);
#endif

  // Signal (only once) that the connection was closed
  if(closed)
  {
    emit connectionClosed();
  }
}



// Close and delete the server socket
void DirectConnectionBase::closeServerSocket()
{
  connectionTimer_.stop();
  if(server_ != 0)
  {
    server_->closeNow();
    delete server_;
    server_ = 0;
  }
}



// Register a slot that will be called each time the write buffer is empty (for autonomous/asynchonous writing).
void DirectConnectionBase::connectWriteHandler(QObject *receiver, const char *slot)
{
  if(KMESS_NULL(socket_)) return;

  // Enable the readyWrite signal.
  // This is invoked each time the write-buffer is empty,
  // meaning the derived class can send data fast in a autonomous way.

  // When you don't need to write commands very often,
  // this will generate a lot of of CPU overhead.
  // Since our socket is buffered, writeBlock() can be called directly too.

  socket_->enableWrite(true);
  connect(socket_, SIGNAL(readyWrite()), receiver, slot);
}



// Return the number of bytes which are already received in the local buffer
int DirectConnectionBase::getBytesAvailable() const
{
  if(KMESS_NULL(socket_)) return -2;  // same as KExtendedSocket API, means "invalid state"
  return socket_->bytesAvailable();
}



// Get the server port that will be used
int DirectConnectionBase::getLocalServerPort()
{
  // Port not chosen yet
  if( serverPort_ < 0 )
  {
    // It's possible to use a random port nowadays,
    // old clients with the "only port 6891" are no longer in use.
    //
    // KMess continues to use ports between 6891-6900 so it's easier
    // to firewall these.
    //
    // TODO: verify whether we can use the port by calling openServerPort() earlier.

    if(nextServerPort > 6900)
    {
      nextServerPort = 6891;
    }

    serverPort_ = nextServerPort;

    nextServerPort++;

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kdDebug() << "DirectConnectionBase::getLocalServerPort: Reserved port " << serverPort_ << " for server socket." << endl;
#endif
  }

  return serverPort_;
}



// Get the remote ip the socket is connected with.
QString DirectConnectionBase::getRemoteIp() const
{
  if(socket_ == 0)
  {
    return QString::null;
  }
  else if( socket_->peerAddress() == 0 )
  {
    return QString::null;
  }
  else
  {
    return socket_->peerAddress()->nodeName();
  }
}



// Get the remote port the socket is connected with.
QString DirectConnectionBase::getRemotePort() const
{
  if(socket_ == 0)
  {
    return QString::null;
  }
  else if( socket_->peerAddress() == 0 )
  {
    return QString::null;
  }
  else
  {
    return socket_->peerAddress()->serviceName();
  }
}



// Return the error description
QString DirectConnectionBase::getSocketError() const
{
  if( timeout_ )
  {
    return "connection timeout event";
  }
  else if(socket_ == 0)
  {
    return QString::null;
  }
  else
  {
    // The return value of systemError() is the "errno" value from errno.h at the
    // time the socket system-call failed. This function hides the low-level stuff
    // like "errno" (see man:errno(3)) and perror() (see man:perror(3))
    return KExtendedSocket::strError( socket_->status(), socket_->systemError() );
  }
}



// Initialize the connection, return true when this was succesful
bool DirectConnectionBase::initialize()
{
  // The base class doesn't need any initialisation, MsnDirectConnection does.
  return true;
}



// Return whether the connection login was successful.
bool DirectConnectionBase::isAuthorized() const
{
  return authorized_;
}



// Return true if a connection is active
bool DirectConnectionBase::isConnected() const
{
  return (socket_ != 0 && socket_->socketStatus() == KExtendedSocket::connected);
}



// Return true if this class acts as server, false if it acts as client.
bool DirectConnectionBase::isServer() const
{
  return isServer_;
}



// Connect to a host
bool DirectConnectionBase::openConnection(const QString &ipAddress, const int port, bool async)
{
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kdDebug() << "DirectConnectionBase::openConnection: Connecting to " << ipAddress << " port " << port << "." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( socket_ == 0 );
#endif
  connectingTo_ = ipAddress + ":" + QString::number(port);

  // Configure the socket for asynchronous operation
  // Note that a buffered socket only emits the readyRead() signal when data is really received.
  // The socket is made non-blocking, so the connectionSuccess() signal tells us when the connection is made.
  // Otherwise KMess would freeze until a connection is made or fails.
  socket_ = new KExtendedSocket(ipAddress, port, KExtendedSocket::bufferedSocket );
  socket_->enableRead(true);
  socket_->enableWrite(false);
  socket_->setBlockingMode(false);  // avoid blocking until connect() returns
  socket_->setTimeout(10);
  connect( socket_, SIGNAL( readyRead()           ), this, SLOT( slotDataReceived()    ));
  connect( socket_, SIGNAL( connectionFailed(int) ), this, SLOT( slotSocketFailed()    ));
  connect( socket_, SIGNAL( connectionSuccess()   ), this, SLOT( slotSocketConnected() ));

  // Indicate we'll become a direct-connection client.
  isServer_ = false;
  timeout_  = false;

  // Connect the socket
  int connectResult = (async ? socket_->startAsyncConnect() : socket_->connect());

  // Connect the socket
  if(connectResult != 0)
  {
    kdWarning() << "DirectConnectionBase::openConnection: Couldn't connect new socket" << endl;
    delete socket_;
    socket_ = 0;
    return false;
  }

  // Start timer, as the asyncConnect doesn't seam to honor the timeout value.
  connectionTimer_.start(10000, true);

  // The code continues with slotSocketConnected() or slotSocketFailed()
  return true;
}



// Wait for an incoming connection
bool DirectConnectionBase::openServerPort()
{
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kdDebug() << "DirectConnectionBase::openServerPort - Creating server socket." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( server_ == 0 );
#endif

  int  port;
  bool portSuccess;
  int  listenResult;

  // Create the server socket to the given port.
  server_ = new KExtendedSocket();
  server_->setSocketFlags( KExtendedSocket::noResolve | KExtendedSocket::passiveSocket );

  // Set the port
  port        = getLocalServerPort();
  portSuccess = server_->setPort(port);
  if(! portSuccess )
  {
    kdWarning() << "DirectConnectionBase: Could not use port " << port << "." << endl;
    serverPort_ = -1;  // reset, so getLocalServerPort() picks the next port with another openServerPort() call.
    delete server_;
    server_ = 0;
    return false;
  }


  // Connect the server to signal when it is ready to accept a connection
  connect( server_, SIGNAL( readyAccept() ), this, SLOT( slotAcceptConnection() ));


  // Indicate we'll become a direct-connection server.
  isServer_ = true;
  timeout_  = false;

  // Put the socket in listen mode.
  listenResult = server_->listen(1);  // Refuse parallel connections
  if(listenResult != 0)
  {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kdDebug() << "DirectConnectionBase: Could not listen at port " << port << ", freeing for next one." << endl;
#endif
    serverPort_ = -1;  // reset, so getLocalServerPort() picks the next port with another openServerPort() call.
    delete server_;
    server_ = 0;
    return false;
  }


  // Start timer, but allow a longer timeout.
  connectionTimer_.start(30000, true);

  // The code continues with slotAcceptConnection()
  return true;
}



// Read data from the socket
int DirectConnectionBase::readBlock( char *buffer, int size )
{
  // Avoid crashes
  if(KMESS_NULL(socket_)) return -1;
  if(KMESS_NULL(buffer))  return -1;

  // Fill the buffer
  return socket_->readBlock( buffer, size );
}



// Read data from the socket (uses the QByteArray size() as block size)
int DirectConnectionBase::readBlock( QByteArray &buffer, const int maxSize, const int bufferOffset )
{
  // Avoid crashes
  if(KMESS_NULL(socket_)) return -1;

  // Determine the maximum number of bytes we can write to the buffer
  int bufferSpace = buffer.size() - bufferOffset;

  // API usage check.
  // Warn if certain sizes get a unusual value
  if(buffer.size() == 0)
  {
    kdWarning() << "DirectConnectionBase::readBlock: buffer is not initialized!" << endl;
    return -1;
  }
  else if(bufferSpace < 0)
  {
    kdWarning() << "DirectConnectionBase::readBlock: offset of " << bufferOffset << " bytes"
                << " exceeds the buffer size of " << buffer.size() << " bytes!" << endl;
  }
  else if(bufferSpace < maxSize)
  {
    kdWarning() << "DirectConnectionBase::readBlock: maximum allowed read size"
                << " exceeds the buffer size of " << buffer.size() << " bytes!" << endl;
  }

  // Fill the buffer from the give offset,
  // size argument is limited to what the buffer can/may sustain.
  char *bufferStart = buffer.data() + bufferOffset;
  return socket_->readBlock( bufferStart, QMIN(bufferSpace, maxSize) );
}



// Mark the remote host as authorized (usually after the handshake was successful)
void DirectConnectionBase::setAuthorized(bool authorized)
{
#ifdef KMESSTEST
  ASSERT( isConnected() );
#endif

  // notify when it wasn't authorized before, but will be authorized now.
  bool notify = (! authorized_ && authorized);

  authorized_ = authorized;

  if( notify )
  {
    emit connectionAuthorized();
  }
}



// Accept incoming connections on the socket.
void DirectConnectionBase::slotAcceptConnection()
{
#ifdef KMESSTEST
  ASSERT( socket_ == 0 );
#endif
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kdDebug() << "DirectConnectionBase: Accept a socket" << endl;
#endif

  if(socket_ != 0)
  {
    // Already accepted a connection, close server
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kdDebug() << "DirectConnectionBase: Already got a connection, rejecting this request" << endl;
#endif

    closeServerSocket();
    return;
  }

  // Wait until a connection was established
  server_->setBlockingMode(true);

  if(server_->accept(socket_) != 0)
  {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kdWarning() << "DirectConnectionBase: Failed to accept incoming connection." << endl;
#endif
    // TODO: signal failure?
    return;
  }

  // Accept success
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kdDebug() << "DirectConnectionBase: Accept success" << endl;
#endif

  // Close the server, we don't need to anymore (no connectionClosed events()).
  server_->closeNow();
  delete server_;
  server_ = 0;
  connectionTimer_.stop();

  // Configure the socket for asynchronous operation
  socket_->enableRead(true);
  socket_->enableWrite(false);
  socket_->setBlockingMode(true);
  socket_->setBufferSize(-1, -1);
  connect( socket_, SIGNAL( readyRead()           ), this, SLOT( slotDataReceived()      ));
  connect( socket_, SIGNAL( connectionFailed(int) ), this, SLOT( slotConnectionFailed()  ));

  // Inform there is a connection
  emit connectionEstablished();
  slotConnectionEstablished(); // call manually, no need to connect
}



// This is called when the connection is established
void DirectConnectionBase::slotConnectionEstablished()
{
  // This method is added for derived classes to overload.
  // It pretends we're also listening to our own signals.

  // This is both called when a outgoing connection was made or
  // an incoming connection was accepted.
  // By default, it's empty,
}



// This is called when there's an error in the socket.
void DirectConnectionBase::slotConnectionFailed()
{
  // This method is added for derived classes to overload.
  // It pretends we're also listening to our own signals.

  QString sysError = getSocketError();  // avoids poking sys_errlist[] manually

  if( ! sysError.isNull() )
  {
    kdWarning() << "DirectConnectionBase: Failed to connect to " << connectingTo_ << ", system error: " << sysError << "." << endl;
  }
  else
  {
    kdWarning() << "DirectConnectionBase: Failed to connect to " << connectingTo_ << "!" << endl;
  }
}



// A timeout occured to connect a socket
void DirectConnectionBase::slotConnectionTimeout()
{
  if( isConnected() )
  {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kdDebug() << "DirectConnectionBase::slotConnectionTimeout: Timer fired but connection already established, ignore." << endl;
#endif
    return;
  }


#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kdDebug() << "DirectConnectionBase::slotConnectionTimeout: Timer fired before connection was established, closing sockets and reporting failure." << endl;
#endif

  // Delete sockets, no connectionClosed events.
  if( server_ != 0 )
  {
    server_->closeNow();
    delete server_;
    server_ = 0;
  }
  if( socket_ != 0 )
  {
    socket_->closeNow();
    delete socket_;
    socket_ = 0;
  }

  // Set state for getSocketError();
  timeout_ = true;

  // Inform derived class and other listeners
  // This pretends we're connected to our signals but without generating the overhead.
  connectionTimer_.stop();
  emit connectionFailed();
  slotConnectionFailed();
}



// Signal that the connection was established
void DirectConnectionBase::slotSocketConnected()
{
  // This is called when the socket in openConnection() was connected.
#ifdef KMESSTEST
  ASSERT( ! isServer_ );
#endif
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kdDebug() << "DirectConnectionBase::slotSocketConnected: Connected with host" << endl;
#endif

  // Inform derived class and other listeners
  // This pretends we're connected to our signals but without generating the overhead.
  connectionTimer_.stop();
  emit connectionEstablished();
  slotConnectionEstablished();
}



// Signal that the connection could not be made.
void DirectConnectionBase::slotSocketFailed()
{
  // This is called when the socket in openConnection() failed.
#ifdef KMESSTEST
  ASSERT( ! isServer_ );
#endif
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kdDebug() << "DirectConnectionBase::slotSocketFailed: Failed to connect to host" << endl;
#endif

  // Inform derived class and other listeners
  // This pretends we're connected to our signals but without generating the overhead.
  connectionTimer_.stop();
  emit connectionFailed();
  slotConnectionFailed();
}



// Write data to the socket
bool DirectConnectionBase::writeBlock( const char *block, const int size )
{
  if(KMESS_NULL(socket_)) return false;

  // Make final connected check
  if( ! isConnected() )
  {
    kdWarning() << "DirectConnectionBase: Attempting to write data to a disconnected socket." << endl;
    return false;
  }

  // Write the data
  int noBytesWritten = socket_->writeBlock( block, size );

  // Log in network window.
#ifdef KMESS_NETWORK_WINDOW
  QByteArray wrapper;
  wrapper.setRawData( block, size );
  KMESS_NET_SENT(this, wrapper);
  wrapper.resetRawData( block, size );
#endif

  // Return true if the data was indeed written.
  return ( noBytesWritten == size );
}



// Write data to the socket (convenience function)
bool DirectConnectionBase::writeBlock( const QByteArray &block )
{
  return writeBlock( block.data(), block.size() );
}


#include "directconnectionbase.moc"

Generated by  Doxygen 1.6.0   Back to index