# include "clientconnection.hh" // Qt specific libraries # include // Local headers # include "../base/PluginNotification.pb.h" # include "../base/MyApplicationNotification.pb.h" # include "../base/settings.hh" // External libraries # include # include # define FileName "ClientConnection" ClientConnection::ClientConnection ( void ) : BaseConnection () , mConnectionAlreadyIdentified ( false ) , mID ( 0 ) { mPendingMessages = QSharedPointer< QList< QByteArray > > (); } ClientConnection::~ClientConnection ( void ) { } QSharedPointer< QSslSocket > ClientConnection::connectionSocket ( void ) const { return ( mConnectionSocket ); } void ClientConnection::disconnectFromClient ( void ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Disconnecting from client" ).arg ( QString::number ( mID ) ) ); if ( mConnectionSocket ) { mConnectionSocket->close (); mConnectionSocket.clear (); } emit disconnectedFromClient ( mID ); return; } qint32 ClientConnection::id ( void ) const { return ( mID ); } bool ClientConnection::isConnected ( void ) const { return ( mConnectionSocket ? mConnectionSocket->isOpen () : false ); } bool ClientConnection::isMessageIdentificationMessage ( const QByteArray& pMessage , unsigned int& pIdentificationType ) { using namespace SharedComponents::Values::Commands::Instruction::AuthCommands; debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage" ); pIdentificationType = 0; // 0 = NO, 1 = MyApplication, 2 = PLUGIN QString direct_message; QString bytes_message; for ( auto x : pMessage ) { direct_message += QString ( x ); bytes_message += QString::number ( x ); bytes_message += " "; } debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage Direct message: " + direct_message ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage Bytes message: " + bytes_message ); // See if I can convert it to the MyApplication identification notification format. network::MyApplication::notifications::MyApplicationIdentification MyApplication_identification; bool result = MyApplication_identification.ParseFromArray ( pMessage.data () , pMessage.size () ); if ( result ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Message is identification A." ).arg ( QString::number ( mID ) ) ); // Ok, the message is an identification message. However, even if the message was correctly // parsed for MyApplication, the Plugin and MyApplication identification messages have the same structure, so I // need to extract the "identification" field, which is a string defining who is this // mysterious client. QString identification_string ( MyApplication_identification.identification ().c_str () ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage Identification message: \"" + identification_string + "\"" ); // Change to upper case identification_string = identification_string.toUpper (); // Who is this? if ( identification_string.contains ( FromMyApplication::InitialHandshake , Qt::CaseInsensitive ) ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Client is MyApplication." ).arg ( mID ) ); // Its MyApplication. pIdentificationType = 1; return ( true ); } if ( identification_string.contains ( ServiceSettings::SSL::PluginIdentification , Qt::CaseInsensitive ) ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Client is a Plugin." ).arg ( QString::number ( mID ) ) ); // Its a Plugin pIdentificationType = 2; return ( true ); } debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage Can't identify it" ); } debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage Is not identification A" ); // I was unable to convert the message as an identification notification from MyApplication. Try from the // Plugin type. network::concerto::notifications::ConcertoIdentification plugin_identification; result = plugin_identification.ParseFromArray ( pMessage.data () , pMessage.size () ); if ( result ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Message is identification B." ).arg ( QString::number ( mID ) ) ); // Ok, the message is an identification message. However, even if the message was correctly // parsed for a Plugin, the Plugin and MyApplication identification messages have the same structure, so // I need to extract the "identification" field, which is a string defining who is this // mysterious client. QString identification_string ( plugin_identification.identification ().c_str () ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage Identification string: \"" + identification_string + "\"" ); // Change to upper case identification_string = identification_string.toUpper (); // Who is this? if ( identification_string.contains ( FromMyApplication::InitialHandshake , Qt::CaseInsensitive ) ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Client is MyApplication." ).arg ( QString::number ( mID ) ) ); // Its MyApplication. pIdentificationType = 1; return ( true ); } if ( identification_string.contains ( ServiceSettings::SSL::PluginIdentification , Qt::CaseInsensitive ) ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Client is a Plugin." ).arg ( QString::number ( mID ) ) ); // Its a Plugin pIdentificationType = 2; return ( true ); } debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage Can't identify 2" ); } debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage Looking for final form of identification" ); // I was unable to convert the message as an identification notification from MyApplication or a Plugin. If // the initial buffer is not empty, the message could be joined with some trash. So, on this // buffer exist the MyApplication message? QByteArray MyApplication_identification_array = ServiceSettings::SSL::MyApplicationIdentification.toLocal8Bit (); QByteArray plugin_identification_array = ServiceSettings::SSL::PluginIdentification.toLocal8Bit (); if ( pMessage.contains ( MyApplication_identification_array ) ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Client is MyApplication." ).arg ( QString::number ( mID ) ) ); // Its MyApplication. pIdentificationType = 1; return ( true ); } if ( pMessage.contains ( plugin_identification_array ) ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%2) Client is a Plugin." ).arg ( QString::number ( mID ) ) ); // Its MyApplication. pIdentificationType = 2; return ( true ); } debug ( FileName , "LOG (" + QString::number ( mID ) + ") isMessageIdentificationMessage Finally unable to tell what it is" ); return ( false ); } void ClientConnection::onReadyRead ( void ) { using namespace SharedComponents::Values::Commands::Instruction::AuthCommands; using namespace SharedComponents::Functions; debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead" ); // Avoid read from invalid socket if ( !mConnectionSocket ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead Connection socket is null" ); return; } // Avoid reading if there is nothing to read. if ( mConnectionSocket->bytesAvailable () ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead There are bytes ready, trying to read" ); // Load as much data as possible into the buffer mInputBuffer.append ( mConnectionSocket->readAll () ); } else { debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead Nothing to read, check if there is stuff in buffer" ); } debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead Current input buffer: " + QString::number ( mInputBuffer.size () ) + " -> \"" + QString ( mInputBuffer ) + "\"" ); QByteArray remaining_buffer; debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead Splitting input buffer into messages" ); // Try to extract messages SplitProtobufMessages ( mInputBuffer , mPendingMessages , remaining_buffer ); // Save the reamining buffer as the current input buffer. mInputBuffer = remaining_buffer; debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead Total messages identified: " + QString::number ( mPendingMessages->size () ) ); // See if there are messages already split from the input buffer. if ( mPendingMessages->isEmpty () ) { // No messages identified yet... wait. debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead No input messages identified, stop" ); return; } // If we are already identified as something in specific, then "just" inform that there are // messages pending for consumption. if ( mConnectionAlreadyIdentified ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead We already know what we are, informing that there is stuff ready to read" ); emit newMessagesReady (); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead Notified about messages pending to read, stop" ); return; } debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead Iterating over messages to try to get identification message" ); // Iterate over the identified messages (there should be at least one). while ( !mPendingMessages->isEmpty () && !mConnectionAlreadyIdentified ) { // Take one and remove it from the list. QByteArray message; message = mPendingMessages->first (); mPendingMessages->removeFirst (); parseMessage ( message ); } // We might have finished reading messages and found the identification message. However, at that // moment, we might still have pending messages. Check if that's the case and notify to our new // lords and masters. if ( mPendingMessages->isEmpty () ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead No more messages after identification or running out of messages to check" ); return; } // We were left with some messages to be processed. Are we identified? if ( !mConnectionAlreadyIdentified ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead There are still messages pending to be identified, but we don't know what we are yet" ); return; } // We were identified and have stuff to parse, notify debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead Notifying about pending messages" ); emit newMessagesReady (); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onReadyRead Finished checking for input messages" ); return; } void ClientConnection::onSocketAlertReceived ( QSsl::AlertLevel pLevel , QSsl::AlertType pType , const QString& pDescription ) { using namespace SharedComponents::Functions; Q_UNUSED ( pLevel ) Q_UNUSED ( pType ) Q_UNUSED ( pDescription ) QString level; QString type; level = SocketAlertLevelToString ( pLevel ); type = SocketAlertTypeToString ( pType ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketAlertReceived Type=" + type + ", Level=" + level + ", Description=" + pDescription ); return; } void ClientConnection::onSocketAlertSent ( QSsl::AlertLevel pLevel , QSsl::AlertType pType , const QString& pDescription ) { using namespace SharedComponents::Functions; Q_UNUSED ( pLevel ) Q_UNUSED ( pType ) Q_UNUSED ( pDescription ) QString level; QString type; level = SocketAlertLevelToString ( pLevel ); type = SocketAlertTypeToString ( pType ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketAlertSent Type=" + type + ", Level=" + level + ", Description=" + pDescription ); return; } void ClientConnection::onSocketConnectionError ( QAbstractSocket::SocketError pSocketError ) { using namespace SharedComponents::Functions; debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Connection problem found." ).arg ( QString::number ( mID ) ) ); bool ignore_problem_found = false; QString problem = SocketErrorToString ( pSocketError ); // Also define if the problem should be ignored or not ignore_problem_found = ( pSocketError == QAbstractSocket::SslInternalError ); debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Problem=%2 (%3)" ).arg ( QString::number ( mID ) , mConnectionSocket->errorString () , problem ) ); /* if ( !ignore_problem_found ) { disconnectFromClient (); }*/ return; } void ClientConnection::onSocketEncrypted ( void ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Connection prepared as E." ).arg ( QString::number ( mID ) ) ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketEncrypted" ); return; } void ClientConnection::onSocketEncryptedBytesWritten ( qint64 pBytesWritten ) { Q_UNUSED ( pBytesWritten ) debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Total amount of EB sent to connection: %2" ).arg ( QString::number ( mID ) , QString::number ( pBytesWritten ) ) ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onEncryptedBytesWritten Bytes=" + QString::number ( pBytesWritten ) ); return; } void ClientConnection::onSocketHandshakeInterruptedOnError ( const QSslError& pError ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) HS error identified: %2" ).arg ( QString::number ( mID ) , pError.errorString () ) ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketHandshakeInterruptedOnError Error=" + pError.errorString () ); mConnectionSocket->continueInterruptedHandshake (); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketHandshakeInterruptedOnError Asked socket to continue handshake" ); return; } void ClientConnection::onSocketModeChanged ( QSslSocket::SslMode pMode ) { using namespace SharedComponents::Functions; QString mode; mode = SocketModeToString ( pMode ); debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Client mode: %2" ).arg ( QString::number ( mID ) , mode ) ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketModeChanged Mode=" + mode ); return; } void ClientConnection::onSocketNewSessionTicketReceived ( void ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketNewSessionTicketReceived" ); return; } void ClientConnection::onSocketPeerVerifyError ( const QSslError& pError ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Client PV error: %2" ).arg ( QString::number ( mID ) , pError.errorString () ) ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketPeerVerifyError Error=" + pError.errorString () ); return; } void ClientConnection::onSocketPreSharedKeyAuthenticationRequired ( QSslPreSharedKeyAuthenticator* pAuthenticator ) { Q_UNUSED ( pAuthenticator ) debug ( FileName , "LOG (" + QString::number ( mID ) + ") preSharedKeyAuthenticationRequired" ); return; } void ClientConnection::onSocketSslErrors ( const QList< QSslError >& pSslErrors ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketSslErrors Total=" + QString::number ( pSslErrors.size () ) ); for ( auto x : pSslErrors ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") onSocketSslErrors Error: " + x.errorString () ); } if ( !mConnectionSocket ) { return; } mConnectionSocket->ignoreSslErrors ( pSslErrors ); return; } void ClientConnection::onValidateConnectionLost ( void ) { debug ( FileName , QSTRING_OBFUSCATED ( "(%1) Connection lost to client. Stopping connection." ).arg ( QString::number ( mID ) ) ); disconnectFromClient (); return; } void ClientConnection::parseMessage ( const QByteArray& pMessage ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") parseMessage Message to check: " + QString ( pMessage ) ); unsigned int identification_type; debug ( FileName , "LOG (" + QString::number ( mID ) + ") parseMessage We haven't been identified the current connection." ); if ( isMessageIdentificationMessage ( pMessage , identification_type ) ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") parseMessage It appears to be identification of this type: " + QString::number ( identification_type ) ); // Message identified as something. if ( identification_type == 1 ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") parseMessage Its identification from MyApplication" ); // It's MyApplication. mConnectionAlreadyIdentified = true; // Notify its MyApplication. Provide the remaining messages identified and the remaining input // buffer. emit newMyApplicationConnection ( mID ); return; } if ( identification_type == 2 ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") parseMessage Its identification from Plugin" ); // It's a Plugin. mConnectionAlreadyIdentified = true; // Notify its a Plugin. Provide the remaining messages identified and the remaining // input buffer. emit newPluginConnection ( mID ); return; } debug ( FileName , "LOG (" + QString::number ( mID ) + ") parseMessage Can't tell what it is" ); } return; } QSharedPointer< QList< QByteArray > > ClientConnection::pendingMessages ( void ) const { return ( mPendingMessages ); } void ClientConnection::setConnectionSocket ( QSharedPointer< QSslSocket > pConnectionSocket ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Starting" ); if ( mConnectionSocket ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Already had a connection, cleaning" ); mConnectionSocket->close (); mConnectionSocket.clear (); } debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Storing and connecting" ); mConnectionSocket = pConnectionSocket; connect ( mConnectionSocket.data () , &QSslSocket::disconnected , this , &ClientConnection::onValidateConnectionLost ); connect ( mConnectionSocket.data () , &QSslSocket::readyRead , this , &ClientConnection::onReadyRead ); connect ( mConnectionSocket.data () , &QSslSocket::alertReceived , this , &ClientConnection::onSocketAlertReceived ); connect ( mConnectionSocket.data () , &QSslSocket::alertSent , this , &ClientConnection::onSocketAlertSent ); connect ( mConnectionSocket.data () , &QSslSocket::encrypted , this , &ClientConnection::onSocketEncrypted ); connect ( mConnectionSocket.data () , &QSslSocket::modeChanged , this , &ClientConnection::onSocketModeChanged ); connect ( mConnectionSocket.data () , &QSslSocket::peerVerifyError , this , &ClientConnection::onSocketPeerVerifyError ); connect ( mConnectionSocket.data () , &QSslSocket::preSharedKeyAuthenticationRequired , this , &ClientConnection::onSocketPreSharedKeyAuthenticationRequired ); connect ( mConnectionSocket.data () , &QSslSocket::newSessionTicketReceived , this , &ClientConnection::onSocketNewSessionTicketReceived ); connect ( mConnectionSocket.data () , &QSslSocket::handshakeInterruptedOnError , this , &ClientConnection::onSocketHandshakeInterruptedOnError ); connect ( mConnectionSocket.data () , &QSslSocket::encryptedBytesWritten , this , &ClientConnection::onSocketEncryptedBytesWritten ); connect ( mConnectionSocket.data () , &QSslSocket::sslErrors , this , &ClientConnection::onSocketSslErrors ); connect ( mConnectionSocket.data () , &QSslSocket::errorOccurred , this , &ClientConnection::onSocketConnectionError ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Connections done, checking for available bytes" ); quint64 bytes_available = mConnectionSocket->bytesAvailable (); debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Pending bytes so far: " + QString::number ( bytes_available ) ); if ( !bytes_available ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Nothing to read, stop new client operation" ); return; } debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket There are bytes ready to read, try to read into buffer" ); mInputBuffer = mConnectionSocket->readAll (); debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Pending data retrieved: " + QString ( mInputBuffer ) ); debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Size of pending data: " + QString::number ( mInputBuffer.size () ) ); if ( mInputBuffer.size () ) { debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Identified that there is pending data to process right away, process" ); onReadyRead (); } else { debug ( FileName , "LOG (" + QString::number ( mID ) + ") setConnectionSocket Nothing readed into the buffer, stop for now" ); } return; } void ClientConnection::setID ( const qint32& pNewID ) { mID = pNewID; debug ( FileName , "LOG (" + QString::number ( mID ) + ") setID" ); return; }