PageRenderTime 243ms CodeModel.GetById 40ms app.highlight 131ms RepoModel.GetById 50ms app.codeStats 1ms

/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
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4 *   Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
  5 *   Copyright 2013, Uwe L. Korn <uwelk@xhochy.com>
  6 *
  7 *   Tomahawk is free software: you can redistribute it and/or modify
  8 *   it under the terms of the GNU General Public License as published by
  9 *   the Free Software Foundation, either version 3 of the License, or
 10 *   (at your option) any later version.
 11 *
 12 *   Tomahawk is distributed in the hope that it will be useful,
 13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15 *   GNU General Public License for more details.
 16 *
 17 *   You should have received a copy of the GNU General Public License
 18 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 19 */
 20
 21#include "Connection_p.h"
 22
 23#include "network/acl/AclRegistry.h"
 24#include "network/acl/AclRequest.h"
 25#include "network/Servent.h"
 26#include "network/Msg.h"
 27#include "utils/Logger.h"
 28#include "utils/Json.h"
 29#include "utils/TomahawkUtils.h"
 30
 31#include "QTcpSocketExtra.h"
 32#include "Source.h"
 33
 34#include <QTime>
 35#include <QThread>
 36
 37#define PROTOVER "4" // must match remote peer, or we can't talk.
 38
 39
 40Connection::Connection( Servent* parent )
 41    : QObject()
 42    , d_ptr( new ConnectionPrivate( this, parent ) )
 43{
 44    moveToThread( parent->thread() );
 45    tDebug( LOGVERBOSE ) << "CTOR Connection (super)" << thread();
 46
 47    connect( &d_func()->msgprocessor_out, SIGNAL( ready( msg_ptr ) ),
 48             SLOT( sendMsg_now( msg_ptr ) ), Qt::QueuedConnection );
 49
 50    connect( &d_func()->msgprocessor_in,  SIGNAL( ready( msg_ptr ) ),
 51             SLOT( handleMsg( msg_ptr ) ), Qt::QueuedConnection );
 52
 53    connect( &d_func()->msgprocessor_in, SIGNAL( empty() ),
 54             SLOT( handleIncomingQueueEmpty() ), Qt::QueuedConnection );
 55}
 56
 57
 58Connection::~Connection()
 59{
 60    Q_D( Connection );
 61    tDebug( LOGVERBOSE ) << "DTOR connection (super)" << id() << thread() << d->sock.isNull();
 62    if ( !d->sock.isNull() )
 63    {
 64        d->sock->deleteLater();
 65    }
 66
 67    delete d->statstimer;
 68    delete d_ptr;
 69}
 70
 71
 72void
 73Connection::handleIncomingQueueEmpty()
 74{
 75    Q_D( Connection );
 76    //qDebug() << Q_FUNC_INFO << "bavail" << m_sock->bytesAvailable()
 77    //         << "isopen" << m_sock->isOpen()
 78    //         << "m_peer_disconnected" << m_peer_disconnected
 79    //         << "bytes rx" << bytesReceived();
 80
 81    if ( !d->sock.isNull() && d->sock->bytesAvailable() == 0 && d->peer_disconnected )
 82    {
 83        tDebug( LOGVERBOSE ) << "No more data to read, peer disconnected. shutting down connection."
 84                             << "bytesavail" << d->sock->bytesAvailable()
 85                             << "bytesrx" << d->rx_bytes;
 86        shutdown();
 87    }
 88}
 89
 90
 91// convenience:
 92void
 93Connection::setFirstMessage( const QVariant& m )
 94{
 95    const QByteArray ba = TomahawkUtils::toJson( m );
 96    //qDebug() << "first msg json len:" << ba.length();
 97    setFirstMessage( Msg::factory( ba, Msg::JSON ) );
 98}
 99
100
101void
102Connection::setFirstMessage( msg_ptr m )
103{
104    Q_D( Connection );
105
106    d->firstmsg = m;
107    //qDebug() << id() << " first msg set to " << QString::fromAscii(m_firstmsg->payload())
108    //        << "msg len:" << m_firstmsg->length() ;
109}
110
111msg_ptr
112Connection::firstMessage() const
113{
114    Q_D( const Connection );
115
116    return d->firstmsg;
117}
118
119const QPointer<QTcpSocket>&
120Connection::socket() const
121{
122    Q_D( const Connection );
123
124    return d->sock;
125}
126
127void
128Connection::setOutbound( bool o )
129{
130    Q_D( Connection );
131
132    d->outbound = o;
133}
134
135bool
136Connection::outbound() const
137{
138    Q_D( const Connection );
139
140    return d->outbound;
141}
142
143Servent*
144Connection::servent() const
145{
146    Q_D( const Connection );
147
148    return d->servent;
149}
150
151int
152Connection::peerPort() const
153{
154    Q_D( const Connection );
155
156    return d->peerport;
157}
158
159void
160Connection::setPeerPort(int p)
161{
162    Q_D( Connection );
163
164    d->peerport = p;
165}
166
167
168void
169Connection::shutdown( bool waitUntilSentAll )
170{
171    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << waitUntilSentAll << id();
172    if ( d_func()->do_shutdown )
173    {
174        //qDebug() << id() << " already shutting down";
175        return;
176    }
177
178    d_func()->do_shutdown = true;
179    if ( !waitUntilSentAll )
180    {
181//        qDebug() << "Shutting down immediately " << id();
182        actualShutdown();
183    }
184    else
185    {
186        tDebug( LOGVERBOSE ) << "Shutting down after transfer complete " << id()
187                             << "Actual/Desired" << d_func()->tx_bytes << d_func()->tx_bytes_requested;
188
189        bytesWritten( 0 ); // trigger shutdown if we've already sent everything
190        // otherwise the bytesWritten slot will call actualShutdown()
191        // once all enqueued data has been properly written to the socket
192    }
193}
194
195
196void
197Connection::actualShutdown()
198{
199    Q_D( Connection );
200    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << d->actually_shutting_down << id();
201    if ( d->actually_shutting_down )
202    {
203        return;
204    }
205    d->actually_shutting_down = true;
206
207    if ( !d->sock.isNull() && d->sock->isOpen() )
208    {
209        d->sock->disconnectFromHost();
210    }
211
212//    qDebug() << "EMITTING finished()";
213    emit finished();
214}
215
216
217void
218Connection::markAsFailed()
219{
220    tDebug( LOGVERBOSE ) << "Connection" << id() << "FAILED ***************" << thread();
221    emit failed();
222    shutdown();
223}
224
225void
226Connection::setName( const QString& n )
227{
228    Q_D( Connection );
229
230    d->name = n;
231}
232
233QString
234Connection::name() const
235{
236    Q_D( const Connection );
237
238    return d->name;
239}
240
241void
242Connection::setOnceOnly( bool b )
243{
244    Q_D( Connection );
245
246    d->onceonly = b;
247}
248
249bool
250Connection::onceOnly() const
251{
252    Q_D( const Connection );
253
254    return d->onceonly;
255}
256
257bool
258Connection::isReady() const
259{
260    Q_D( const Connection );
261
262    return d->ready;
263}
264
265bool
266Connection::isRunning() const
267{
268    Q_D( const Connection );
269
270    return d->sock != 0;
271}
272
273qint64
274Connection::bytesSent() const
275{
276    return d_func()->tx_bytes;
277}
278
279qint64
280Connection::bytesReceived() const
281{
282    return d_func()->rx_bytes;
283}
284
285void
286Connection::setMsgProcessorModeOut(quint32 m)
287{
288    d_func()->msgprocessor_out.setMode( m );
289}
290
291void
292Connection::setMsgProcessorModeIn(quint32 m)
293{
294    d_func()->msgprocessor_in.setMode( m );
295}
296
297const QHostAddress
298Connection::peerIpAddress() const
299{
300    return d_func()->peerIpAddress;
301}
302
303
304void
305Connection::start( QTcpSocket* sock )
306{
307    Q_D( Connection );
308    Q_ASSERT( d->sock.isNull() );
309    Q_ASSERT( sock );
310    Q_ASSERT( sock->isValid() );
311
312    d->sock = sock;
313
314    if ( d->name.isEmpty() )
315    {
316        d->name = QString( "peer[%1]" ).arg( d->sock->peerAddress().toString() );
317    }
318
319    QTimer::singleShot( 0, this, SLOT( checkACL() ) );
320}
321
322
323void
324Connection::checkACL()
325{
326    Q_D( Connection );
327    QReadLocker nodeidLocker( &d->nodeidLock );
328
329    if ( d->nodeid.isEmpty() )
330    {
331        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Not checking ACL, nodeid is empty";
332        QTimer::singleShot( 0, this, SLOT( doSetup() ) );
333        emit authSuccessful();
334        return;
335    }
336
337    if ( Servent::isIPWhitelisted( d_func()->peerIpAddress ) )
338    {
339        QTimer::singleShot( 0, this, SLOT( doSetup() ) );
340        emit authSuccessful();
341        return;
342    }
343
344    tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Checking ACL for" << name();
345    d->aclRequest = Tomahawk::Network::ACL::aclrequest_ptr(
346                new Tomahawk::Network::ACL::AclRequest( d->nodeid, bareName(), Tomahawk::ACLStatus::NotFound ),
347                &QObject::deleteLater );
348    connect( d->aclRequest.data(), SIGNAL( decision( Tomahawk::ACLStatus::Type ) ), SLOT( aclDecision( Tomahawk::ACLStatus::Type ) ), Qt::QueuedConnection );
349    ACLRegistry::instance()->isAuthorizedRequest( d->aclRequest );
350}
351
352
353QString
354Connection::bareName() const
355{
356    return name().contains( '/' ) ? name().left( name().indexOf( "/" ) ) : name();
357}
358
359void
360Connection::aclDecision( Tomahawk::ACLStatus::Type status )
361{
362    Q_D( Connection );
363    tLog( LOGVERBOSE ) << Q_FUNC_INFO << "ACL decision for" << name() << ":" << status;
364
365    // We have a decision, free memory.
366    d->aclRequest.clear();
367
368    if ( status == Tomahawk::ACLStatus::Stream )
369    {
370        QTimer::singleShot( 0, this, SLOT( doSetup() ) );
371        emit authSuccessful();
372        return;
373    }
374
375    emit authFailed();
376
377    shutdown();
378}
379
380
381void
382Connection::authCheckTimeout()
383{
384    Q_D( Connection );
385
386    if ( d->ready )
387        return;
388
389    emit authTimeout();
390
391    tDebug( LOGVERBOSE ) << "Closing connection, not authed in time.";
392    shutdown();
393}
394
395
396void
397Connection::doSetup()
398{
399    Q_D( Connection );
400
401    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << thread() << d->id;
402    /*
403        New connections can be created from other thread contexts, such as
404        when AudioEngine calls getIODevice.. - we need to ensure that connections
405        and their associated sockets are running in the same thread as the servent.
406
407        HINT: export QT_FATAL_WARNINGS=1 helps to catch these kind of errors.
408     */
409    if ( QThread::currentThread() != d->servent->thread() )
410    {
411        // Connections should always be in the same thread as the servent.
412        moveToThread( d->servent->thread() );
413    }
414
415    if ( !d->setup )
416    {
417        // We only want to setup this connection once
418        d->setup = true;
419
420        //stats timer calculates BW used by this connection
421        d->statstimer = new QTimer;
422        d->statstimer->moveToThread( this->thread() );
423        d->statstimer->setInterval( 1000 );
424        connect( d->statstimer, SIGNAL( timeout() ), SLOT( calcStats() ) );
425        d->statstimer->start();
426        d->statstimer_mark.start();
427
428        d->sock->moveToThread( thread() );
429
430        connect( d->sock.data(), SIGNAL( bytesWritten( qint64 ) ),
431                                  SLOT( bytesWritten( qint64 ) ), Qt::QueuedConnection );
432
433        connect( d->sock.data(), SIGNAL( disconnected() ),
434                                  SLOT( socketDisconnected() ), Qt::QueuedConnection );
435
436        connect( d->sock.data(), SIGNAL( error( QAbstractSocket::SocketError ) ),
437                                  SLOT( socketDisconnectedError( QAbstractSocket::SocketError ) ), Qt::QueuedConnection );
438
439        connect( d->sock.data(), SIGNAL( readyRead() ),
440                                  SLOT( readyRead() ), Qt::QueuedConnection );
441
442        // if connection not authed/setup fast enough, kill it:
443        QTimer::singleShot( AUTH_TIMEOUT, this, SLOT( authCheckTimeout() ) );
444
445        if ( outbound() )
446        {
447            Q_ASSERT( !d->firstmsg.isNull() );
448            sendMsg( d->firstmsg );
449        }
450        else
451        {
452            sendMsg( Msg::factory( PROTOVER, Msg::SETUP ) );
453        }
454    }
455    else
456    {
457        tLog() << Q_FUNC_INFO << QThread::currentThread() << d->id << "Duplicate doSetup call";
458    }
459
460    // call readyRead incase we missed the signal in between the servent disconnecting and us
461    // connecting to the signal - won't do anything if there are no bytesAvailable anyway.
462    readyRead();
463}
464
465
466void
467Connection::socketDisconnected()
468{
469    Q_D( Connection );
470
471    qint64 bytesAvailable = 0;
472    if ( !d->sock.isNull() )
473    {
474        bytesAvailable = d->sock->bytesAvailable();
475    }
476    tDebug( LOGVERBOSE ) << "SOCKET DISCONNECTED" << this->name() << id()
477                         << "shutdown will happen after incoming queue empties."
478                         << "bytesavail:" << bytesAvailable
479                         << "bytesRecvd" << bytesReceived();
480
481    d->peer_disconnected = true;
482    emit socketClosed();
483
484    if ( d->msgprocessor_in.length() == 0 && bytesAvailable == 0 )
485    {
486        handleIncomingQueueEmpty();
487        actualShutdown();
488    }
489}
490
491
492void
493Connection::socketDisconnectedError( QAbstractSocket::SocketError e )
494{
495    tDebug() << "SOCKET ERROR CODE" << e << this->name() << "CALLING Connection::shutdown(false)";
496
497    if ( e == QAbstractSocket::RemoteHostClosedError )
498        return;
499
500    d_func()->peer_disconnected = true;
501
502    emit socketErrored(e);
503    emit socketClosed();
504
505    shutdown( false );
506}
507
508
509QString
510Connection::id() const
511{
512    return d_func()->id;
513}
514
515
516void
517Connection::setId( const QString& id )
518{
519    d_func()->id = id;
520}
521
522QString
523Connection::nodeId() const
524{
525    Q_D( const Connection );
526    QReadLocker locker( &d->nodeidLock );
527    return d->nodeid;
528}
529
530void
531Connection::setNodeId( const QString& nodeid )
532{
533    Q_D( Connection );
534    QWriteLocker locker( &d->nodeidLock );
535    d->nodeid = nodeid;
536}
537
538
539void
540Connection::readyRead()
541{
542//    qDebug() << "readyRead, bytesavail:" << m_sock->bytesAvailable();
543    Q_D( Connection );
544
545    if ( d->msg.isNull() )
546    {
547        if ( d->sock->bytesAvailable() < Msg::headerSize() )
548            return;
549
550        char msgheader[ Msg::headerSize() ];
551        if ( d->sock->read( (char*) &msgheader, Msg::headerSize() ) != Msg::headerSize() )
552        {
553            tDebug() << "Failed reading msg header";
554            this->markAsFailed();
555            return;
556        }
557
558        d->msg = Msg::begin( (char*) &msgheader );
559        d->rx_bytes += Msg::headerSize();
560    }
561
562    if ( d->sock->bytesAvailable() < d->msg->length() )
563        return;
564
565    QByteArray ba = d->sock->read( d->msg->length() );
566    if ( ba.length() != (qint32)d->msg->length() )
567    {
568        tDebug() << "Failed to read full msg payload";
569        this->markAsFailed();
570        return;
571    }
572    d->msg->fill( ba );
573    d->rx_bytes += ba.length();
574
575    handleReadMsg(); // process m_msg and clear() it
576
577    // since there is no explicit threading, use the event loop to schedule this:
578    if ( d->sock->bytesAvailable() )
579    {
580        QTimer::singleShot( 0, this, SLOT( readyRead() ) );
581    }
582}
583
584
585void
586Connection::handleReadMsg()
587{
588    Q_D( Connection );
589
590    if ( outbound() == false &&
591        d->msg->is( Msg::SETUP ) &&
592        d->msg->payload() == "ok" )
593    {
594        d->ready = true;
595        tDebug( LOGVERBOSE ) << "Connection" << id() << "READY";
596        setup();
597        emit ready();
598    }
599    else if ( !d->ready &&
600             outbound() &&
601             d->msg->is( Msg::SETUP ) )
602    {
603        if ( d->msg->payload() == PROTOVER )
604        {
605            sendMsg( Msg::factory( "ok", Msg::SETUP ) );
606            d->ready = true;
607            tDebug( LOGVERBOSE ) << "Connection" << id() << "READY";
608            setup();
609            emit ready();
610        }
611        else
612        {
613            sendMsg( Msg::factory( "{\"method\":\"protovercheckfail\"}", Msg::JSON | Msg::SETUP ) );
614            shutdown( true );
615        }
616    }
617    else
618    {
619        d->msgprocessor_in.append( d->msg );
620    }
621
622    d->msg.clear();
623}
624
625
626void
627Connection::sendMsg( QVariant j )
628{
629    Q_D( Connection );
630
631    if ( d->do_shutdown )
632        return;
633
634    const QByteArray payload = TomahawkUtils::toJson( j );
635    tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Sending to" << id() << ":" << payload;
636    sendMsg( Msg::factory( payload, Msg::JSON ) );
637}
638
639
640void
641Connection::sendMsg( msg_ptr msg )
642{
643    if ( d_func()->do_shutdown )
644    {
645        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "SHUTTING DOWN, NOT SENDING msg flags:"
646                             << (int)msg->flags() << "length:" << msg->length() << id();
647        return;
648    }
649
650    d_func()->tx_bytes_requested += msg->length() + Msg::headerSize();
651    d_func()->msgprocessor_out.append( msg );
652}
653
654
655void
656Connection::sendMsg_now( msg_ptr msg )
657{
658    Q_D( Connection );
659    Q_ASSERT( QThread::currentThread() == thread() );
660//    Q_ASSERT( this->isRunning() );
661
662    if ( d->sock.isNull() || !d->sock->isOpen() || !d->sock->isWritable() )
663    {
664        tDebug() << "***** Socket problem, whilst in sendMsg(). Cleaning up. *****";
665        shutdown( false );
666        return;
667    }
668
669    if ( !msg->write( d->sock.data() ) )
670    {
671        //qDebug() << "Error writing to socket in sendMsg() *************";
672        shutdown( false );
673        return;
674    }
675}
676
677
678void
679Connection::bytesWritten( qint64 i )
680{
681    d_func()->tx_bytes += i;
682    // if we are waiting to shutdown, and have sent all queued data, do actual shutdown:
683    if ( d_func()->do_shutdown && d_func()->tx_bytes == d_func()->tx_bytes_requested )
684        actualShutdown();
685}
686
687
688void
689Connection::calcStats()
690{
691    Q_D( Connection );
692    int elapsed = d->statstimer_mark.restart(); // ms since last calc
693
694    d->stats_tx_bytes_per_sec = (float)1000 * ( (d->tx_bytes - d->tx_bytes_last) / (float)elapsed );
695    d->stats_rx_bytes_per_sec = (float)1000 * ( (d->rx_bytes - d->rx_bytes_last) / (float)elapsed );
696
697    d->rx_bytes_last = d->rx_bytes;
698    d->tx_bytes_last = d->tx_bytes;
699
700    emit statsTick( d->stats_tx_bytes_per_sec, d->stats_rx_bytes_per_sec );
701}