/src/libtomahawk/network/Connection.cpp
http://github.com/tomahawk-player/tomahawk · C++ · 701 lines · 505 code · 145 blank · 51 comment · 53 complexity · 92ec521b9ae8e6e60131a4d552a6e896 MD5 · raw file
- /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
- *
- * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
- * Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
- * Copyright 2013, Uwe L. Korn <uwelk@xhochy.com>
- *
- * Tomahawk 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 3 of the License, or
- * (at your option) any later version.
- *
- * Tomahawk 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
- */
- #include "Connection_p.h"
- #include "network/acl/AclRegistry.h"
- #include "network/acl/AclRequest.h"
- #include "network/Servent.h"
- #include "network/Msg.h"
- #include "utils/Logger.h"
- #include "utils/Json.h"
- #include "utils/TomahawkUtils.h"
- #include "QTcpSocketExtra.h"
- #include "Source.h"
- #include <QTime>
- #include <QThread>
- #define PROTOVER "4" // must match remote peer, or we can't talk.
- Connection::Connection( Servent* parent )
- : QObject()
- , d_ptr( new ConnectionPrivate( this, parent ) )
- {
- moveToThread( parent->thread() );
- tDebug( LOGVERBOSE ) << "CTOR Connection (super)" << thread();
- connect( &d_func()->msgprocessor_out, SIGNAL( ready( msg_ptr ) ),
- SLOT( sendMsg_now( msg_ptr ) ), Qt::QueuedConnection );
- connect( &d_func()->msgprocessor_in, SIGNAL( ready( msg_ptr ) ),
- SLOT( handleMsg( msg_ptr ) ), Qt::QueuedConnection );
- connect( &d_func()->msgprocessor_in, SIGNAL( empty() ),
- SLOT( handleIncomingQueueEmpty() ), Qt::QueuedConnection );
- }
- Connection::~Connection()
- {
- Q_D( Connection );
- tDebug( LOGVERBOSE ) << "DTOR connection (super)" << id() << thread() << d->sock.isNull();
- if ( !d->sock.isNull() )
- {
- d->sock->deleteLater();
- }
- delete d->statstimer;
- delete d_ptr;
- }
- void
- Connection::handleIncomingQueueEmpty()
- {
- Q_D( Connection );
- //qDebug() << Q_FUNC_INFO << "bavail" << m_sock->bytesAvailable()
- // << "isopen" << m_sock->isOpen()
- // << "m_peer_disconnected" << m_peer_disconnected
- // << "bytes rx" << bytesReceived();
- if ( !d->sock.isNull() && d->sock->bytesAvailable() == 0 && d->peer_disconnected )
- {
- tDebug( LOGVERBOSE ) << "No more data to read, peer disconnected. shutting down connection."
- << "bytesavail" << d->sock->bytesAvailable()
- << "bytesrx" << d->rx_bytes;
- shutdown();
- }
- }
- // convenience:
- void
- Connection::setFirstMessage( const QVariant& m )
- {
- const QByteArray ba = TomahawkUtils::toJson( m );
- //qDebug() << "first msg json len:" << ba.length();
- setFirstMessage( Msg::factory( ba, Msg::JSON ) );
- }
- void
- Connection::setFirstMessage( msg_ptr m )
- {
- Q_D( Connection );
- d->firstmsg = m;
- //qDebug() << id() << " first msg set to " << QString::fromAscii(m_firstmsg->payload())
- // << "msg len:" << m_firstmsg->length() ;
- }
- msg_ptr
- Connection::firstMessage() const
- {
- Q_D( const Connection );
- return d->firstmsg;
- }
- const QPointer<QTcpSocket>&
- Connection::socket() const
- {
- Q_D( const Connection );
- return d->sock;
- }
- void
- Connection::setOutbound( bool o )
- {
- Q_D( Connection );
- d->outbound = o;
- }
- bool
- Connection::outbound() const
- {
- Q_D( const Connection );
- return d->outbound;
- }
- Servent*
- Connection::servent() const
- {
- Q_D( const Connection );
- return d->servent;
- }
- int
- Connection::peerPort() const
- {
- Q_D( const Connection );
- return d->peerport;
- }
- void
- Connection::setPeerPort(int p)
- {
- Q_D( Connection );
- d->peerport = p;
- }
- void
- Connection::shutdown( bool waitUntilSentAll )
- {
- tDebug( LOGVERBOSE ) << Q_FUNC_INFO << waitUntilSentAll << id();
- if ( d_func()->do_shutdown )
- {
- //qDebug() << id() << " already shutting down";
- return;
- }
- d_func()->do_shutdown = true;
- if ( !waitUntilSentAll )
- {
- // qDebug() << "Shutting down immediately " << id();
- actualShutdown();
- }
- else
- {
- tDebug( LOGVERBOSE ) << "Shutting down after transfer complete " << id()
- << "Actual/Desired" << d_func()->tx_bytes << d_func()->tx_bytes_requested;
- bytesWritten( 0 ); // trigger shutdown if we've already sent everything
- // otherwise the bytesWritten slot will call actualShutdown()
- // once all enqueued data has been properly written to the socket
- }
- }
- void
- Connection::actualShutdown()
- {
- Q_D( Connection );
- tDebug( LOGVERBOSE ) << Q_FUNC_INFO << d->actually_shutting_down << id();
- if ( d->actually_shutting_down )
- {
- return;
- }
- d->actually_shutting_down = true;
- if ( !d->sock.isNull() && d->sock->isOpen() )
- {
- d->sock->disconnectFromHost();
- }
- // qDebug() << "EMITTING finished()";
- emit finished();
- }
- void
- Connection::markAsFailed()
- {
- tDebug( LOGVERBOSE ) << "Connection" << id() << "FAILED ***************" << thread();
- emit failed();
- shutdown();
- }
- void
- Connection::setName( const QString& n )
- {
- Q_D( Connection );
- d->name = n;
- }
- QString
- Connection::name() const
- {
- Q_D( const Connection );
- return d->name;
- }
- void
- Connection::setOnceOnly( bool b )
- {
- Q_D( Connection );
- d->onceonly = b;
- }
- bool
- Connection::onceOnly() const
- {
- Q_D( const Connection );
- return d->onceonly;
- }
- bool
- Connection::isReady() const
- {
- Q_D( const Connection );
- return d->ready;
- }
- bool
- Connection::isRunning() const
- {
- Q_D( const Connection );
- return d->sock != 0;
- }
- qint64
- Connection::bytesSent() const
- {
- return d_func()->tx_bytes;
- }
- qint64
- Connection::bytesReceived() const
- {
- return d_func()->rx_bytes;
- }
- void
- Connection::setMsgProcessorModeOut(quint32 m)
- {
- d_func()->msgprocessor_out.setMode( m );
- }
- void
- Connection::setMsgProcessorModeIn(quint32 m)
- {
- d_func()->msgprocessor_in.setMode( m );
- }
- const QHostAddress
- Connection::peerIpAddress() const
- {
- return d_func()->peerIpAddress;
- }
- void
- Connection::start( QTcpSocket* sock )
- {
- Q_D( Connection );
- Q_ASSERT( d->sock.isNull() );
- Q_ASSERT( sock );
- Q_ASSERT( sock->isValid() );
- d->sock = sock;
- if ( d->name.isEmpty() )
- {
- d->name = QString( "peer[%1]" ).arg( d->sock->peerAddress().toString() );
- }
- QTimer::singleShot( 0, this, SLOT( checkACL() ) );
- }
- void
- Connection::checkACL()
- {
- Q_D( Connection );
- QReadLocker nodeidLocker( &d->nodeidLock );
- if ( d->nodeid.isEmpty() )
- {
- tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Not checking ACL, nodeid is empty";
- QTimer::singleShot( 0, this, SLOT( doSetup() ) );
- emit authSuccessful();
- return;
- }
- if ( Servent::isIPWhitelisted( d_func()->peerIpAddress ) )
- {
- QTimer::singleShot( 0, this, SLOT( doSetup() ) );
- emit authSuccessful();
- return;
- }
- tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Checking ACL for" << name();
- d->aclRequest = Tomahawk::Network::ACL::aclrequest_ptr(
- new Tomahawk::Network::ACL::AclRequest( d->nodeid, bareName(), Tomahawk::ACLStatus::NotFound ),
- &QObject::deleteLater );
- connect( d->aclRequest.data(), SIGNAL( decision( Tomahawk::ACLStatus::Type ) ), SLOT( aclDecision( Tomahawk::ACLStatus::Type ) ), Qt::QueuedConnection );
- ACLRegistry::instance()->isAuthorizedRequest( d->aclRequest );
- }
- QString
- Connection::bareName() const
- {
- return name().contains( '/' ) ? name().left( name().indexOf( "/" ) ) : name();
- }
- void
- Connection::aclDecision( Tomahawk::ACLStatus::Type status )
- {
- Q_D( Connection );
- tLog( LOGVERBOSE ) << Q_FUNC_INFO << "ACL decision for" << name() << ":" << status;
- // We have a decision, free memory.
- d->aclRequest.clear();
- if ( status == Tomahawk::ACLStatus::Stream )
- {
- QTimer::singleShot( 0, this, SLOT( doSetup() ) );
- emit authSuccessful();
- return;
- }
- emit authFailed();
- shutdown();
- }
- void
- Connection::authCheckTimeout()
- {
- Q_D( Connection );
- if ( d->ready )
- return;
- emit authTimeout();
- tDebug( LOGVERBOSE ) << "Closing connection, not authed in time.";
- shutdown();
- }
- void
- Connection::doSetup()
- {
- Q_D( Connection );
- tDebug( LOGVERBOSE ) << Q_FUNC_INFO << thread() << d->id;
- /*
- New connections can be created from other thread contexts, such as
- when AudioEngine calls getIODevice.. - we need to ensure that connections
- and their associated sockets are running in the same thread as the servent.
- HINT: export QT_FATAL_WARNINGS=1 helps to catch these kind of errors.
- */
- if ( QThread::currentThread() != d->servent->thread() )
- {
- // Connections should always be in the same thread as the servent.
- moveToThread( d->servent->thread() );
- }
- if ( !d->setup )
- {
- // We only want to setup this connection once
- d->setup = true;
- //stats timer calculates BW used by this connection
- d->statstimer = new QTimer;
- d->statstimer->moveToThread( this->thread() );
- d->statstimer->setInterval( 1000 );
- connect( d->statstimer, SIGNAL( timeout() ), SLOT( calcStats() ) );
- d->statstimer->start();
- d->statstimer_mark.start();
- d->sock->moveToThread( thread() );
- connect( d->sock.data(), SIGNAL( bytesWritten( qint64 ) ),
- SLOT( bytesWritten( qint64 ) ), Qt::QueuedConnection );
- connect( d->sock.data(), SIGNAL( disconnected() ),
- SLOT( socketDisconnected() ), Qt::QueuedConnection );
- connect( d->sock.data(), SIGNAL( error( QAbstractSocket::SocketError ) ),
- SLOT( socketDisconnectedError( QAbstractSocket::SocketError ) ), Qt::QueuedConnection );
- connect( d->sock.data(), SIGNAL( readyRead() ),
- SLOT( readyRead() ), Qt::QueuedConnection );
- // if connection not authed/setup fast enough, kill it:
- QTimer::singleShot( AUTH_TIMEOUT, this, SLOT( authCheckTimeout() ) );
- if ( outbound() )
- {
- Q_ASSERT( !d->firstmsg.isNull() );
- sendMsg( d->firstmsg );
- }
- else
- {
- sendMsg( Msg::factory( PROTOVER, Msg::SETUP ) );
- }
- }
- else
- {
- tLog() << Q_FUNC_INFO << QThread::currentThread() << d->id << "Duplicate doSetup call";
- }
- // call readyRead incase we missed the signal in between the servent disconnecting and us
- // connecting to the signal - won't do anything if there are no bytesAvailable anyway.
- readyRead();
- }
- void
- Connection::socketDisconnected()
- {
- Q_D( Connection );
- qint64 bytesAvailable = 0;
- if ( !d->sock.isNull() )
- {
- bytesAvailable = d->sock->bytesAvailable();
- }
- tDebug( LOGVERBOSE ) << "SOCKET DISCONNECTED" << this->name() << id()
- << "shutdown will happen after incoming queue empties."
- << "bytesavail:" << bytesAvailable
- << "bytesRecvd" << bytesReceived();
- d->peer_disconnected = true;
- emit socketClosed();
- if ( d->msgprocessor_in.length() == 0 && bytesAvailable == 0 )
- {
- handleIncomingQueueEmpty();
- actualShutdown();
- }
- }
- void
- Connection::socketDisconnectedError( QAbstractSocket::SocketError e )
- {
- tDebug() << "SOCKET ERROR CODE" << e << this->name() << "CALLING Connection::shutdown(false)";
- if ( e == QAbstractSocket::RemoteHostClosedError )
- return;
- d_func()->peer_disconnected = true;
- emit socketErrored(e);
- emit socketClosed();
- shutdown( false );
- }
- QString
- Connection::id() const
- {
- return d_func()->id;
- }
- void
- Connection::setId( const QString& id )
- {
- d_func()->id = id;
- }
- QString
- Connection::nodeId() const
- {
- Q_D( const Connection );
- QReadLocker locker( &d->nodeidLock );
- return d->nodeid;
- }
- void
- Connection::setNodeId( const QString& nodeid )
- {
- Q_D( Connection );
- QWriteLocker locker( &d->nodeidLock );
- d->nodeid = nodeid;
- }
- void
- Connection::readyRead()
- {
- // qDebug() << "readyRead, bytesavail:" << m_sock->bytesAvailable();
- Q_D( Connection );
- if ( d->msg.isNull() )
- {
- if ( d->sock->bytesAvailable() < Msg::headerSize() )
- return;
- char msgheader[ Msg::headerSize() ];
- if ( d->sock->read( (char*) &msgheader, Msg::headerSize() ) != Msg::headerSize() )
- {
- tDebug() << "Failed reading msg header";
- this->markAsFailed();
- return;
- }
- d->msg = Msg::begin( (char*) &msgheader );
- d->rx_bytes += Msg::headerSize();
- }
- if ( d->sock->bytesAvailable() < d->msg->length() )
- return;
- QByteArray ba = d->sock->read( d->msg->length() );
- if ( ba.length() != (qint32)d->msg->length() )
- {
- tDebug() << "Failed to read full msg payload";
- this->markAsFailed();
- return;
- }
- d->msg->fill( ba );
- d->rx_bytes += ba.length();
- handleReadMsg(); // process m_msg and clear() it
- // since there is no explicit threading, use the event loop to schedule this:
- if ( d->sock->bytesAvailable() )
- {
- QTimer::singleShot( 0, this, SLOT( readyRead() ) );
- }
- }
- void
- Connection::handleReadMsg()
- {
- Q_D( Connection );
- if ( outbound() == false &&
- d->msg->is( Msg::SETUP ) &&
- d->msg->payload() == "ok" )
- {
- d->ready = true;
- tDebug( LOGVERBOSE ) << "Connection" << id() << "READY";
- setup();
- emit ready();
- }
- else if ( !d->ready &&
- outbound() &&
- d->msg->is( Msg::SETUP ) )
- {
- if ( d->msg->payload() == PROTOVER )
- {
- sendMsg( Msg::factory( "ok", Msg::SETUP ) );
- d->ready = true;
- tDebug( LOGVERBOSE ) << "Connection" << id() << "READY";
- setup();
- emit ready();
- }
- else
- {
- sendMsg( Msg::factory( "{\"method\":\"protovercheckfail\"}", Msg::JSON | Msg::SETUP ) );
- shutdown( true );
- }
- }
- else
- {
- d->msgprocessor_in.append( d->msg );
- }
- d->msg.clear();
- }
- void
- Connection::sendMsg( QVariant j )
- {
- Q_D( Connection );
- if ( d->do_shutdown )
- return;
- const QByteArray payload = TomahawkUtils::toJson( j );
- tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Sending to" << id() << ":" << payload;
- sendMsg( Msg::factory( payload, Msg::JSON ) );
- }
- void
- Connection::sendMsg( msg_ptr msg )
- {
- if ( d_func()->do_shutdown )
- {
- tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "SHUTTING DOWN, NOT SENDING msg flags:"
- << (int)msg->flags() << "length:" << msg->length() << id();
- return;
- }
- d_func()->tx_bytes_requested += msg->length() + Msg::headerSize();
- d_func()->msgprocessor_out.append( msg );
- }
- void
- Connection::sendMsg_now( msg_ptr msg )
- {
- Q_D( Connection );
- Q_ASSERT( QThread::currentThread() == thread() );
- // Q_ASSERT( this->isRunning() );
- if ( d->sock.isNull() || !d->sock->isOpen() || !d->sock->isWritable() )
- {
- tDebug() << "***** Socket problem, whilst in sendMsg(). Cleaning up. *****";
- shutdown( false );
- return;
- }
- if ( !msg->write( d->sock.data() ) )
- {
- //qDebug() << "Error writing to socket in sendMsg() *************";
- shutdown( false );
- return;
- }
- }
- void
- Connection::bytesWritten( qint64 i )
- {
- d_func()->tx_bytes += i;
- // if we are waiting to shutdown, and have sent all queued data, do actual shutdown:
- if ( d_func()->do_shutdown && d_func()->tx_bytes == d_func()->tx_bytes_requested )
- actualShutdown();
- }
- void
- Connection::calcStats()
- {
- Q_D( Connection );
- int elapsed = d->statstimer_mark.restart(); // ms since last calc
- d->stats_tx_bytes_per_sec = (float)1000 * ( (d->tx_bytes - d->tx_bytes_last) / (float)elapsed );
- d->stats_rx_bytes_per_sec = (float)1000 * ( (d->rx_bytes - d->rx_bytes_last) / (float)elapsed );
- d->rx_bytes_last = d->rx_bytes;
- d->tx_bytes_last = d->tx_bytes;
- emit statsTick( d->stats_tx_bytes_per_sec, d->stats_rx_bytes_per_sec );
- }