/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "connection.h" #include static const int TransferTimeout = 30 * 1000; static const int PongTimeout = 60 * 1000; static const int PingInterval = 5 * 1000; static const char SeparatorToken = ' '; Connection::Connection(QObject *parent) : QTcpSocket(parent) { greetingMessage = tr("undefined"); username = tr("unknown"); state = WaitingForGreeting; currentDataType = Undefined; numBytesForCurrentDataType = -1; transferTimerId = 0; isGreetingMessageSent = false; pingTimer.setInterval(PingInterval); QObject::connect(this, SIGNAL(readyRead()), this, SLOT(processReadyRead())); QObject::connect(this, SIGNAL(disconnected()), &pingTimer, SLOT(stop())); QObject::connect(&pingTimer, SIGNAL(timeout()), this, SLOT(sendPing())); QObject::connect(this, SIGNAL(connected()), this, SLOT(sendGreetingMessage())); } QString Connection::name() const { return username; } void Connection::setGreetingMessage(const QString &message) { greetingMessage = message; } bool Connection::sendMessage(const QString &message) { if (message.isEmpty()) return false; QByteArray msg = message.toUtf8(); QByteArray data = "MESSAGE " + QByteArray::number(msg.size()) + ' ' + msg; return write(data) == data.size(); } void Connection::timerEvent(QTimerEvent *timerEvent) { if (timerEvent->timerId() == transferTimerId) { abort(); killTimer(transferTimerId); transferTimerId = 0; } } void Connection::processReadyRead() { if (state == WaitingForGreeting) { if (!readProtocolHeader()) return; if (currentDataType != Greeting) { abort(); return; } state = ReadingGreeting; } if (state == ReadingGreeting) { if (!hasEnoughData()) return; buffer = read(numBytesForCurrentDataType); if (buffer.size() != numBytesForCurrentDataType) { abort(); return; } username = QString(buffer) + '@' + peerAddress().toString() + ':' + QString::number(peerPort()); currentDataType = Undefined; numBytesForCurrentDataType = 0; buffer.clear(); if (!isValid()) { abort(); return; } if (!isGreetingMessageSent) sendGreetingMessage(); pingTimer.start(); pongTime.start(); state = ReadyForUse; emit readyForUse(); } do { if (currentDataType == Undefined) { if (!readProtocolHeader()) return; } if (!hasEnoughData()) return; processData(); } while (bytesAvailable() > 0); } void Connection::sendPing() { if (pongTime.elapsed() > PongTimeout) { abort(); return; } write("PING 1 p"); } void Connection::sendGreetingMessage() { QByteArray greeting = greetingMessage.toUtf8(); QByteArray data = "GREETING " + QByteArray::number(greeting.size()) + ' ' + greeting; if (write(data) == data.size()) isGreetingMessageSent = true; } int Connection::readDataIntoBuffer(int maxSize) { if (maxSize > MaxBufferSize) return 0; int numBytesBeforeRead = buffer.size(); if (numBytesBeforeRead == MaxBufferSize) { abort(); return 0; } while (bytesAvailable() > 0 && buffer.size() < maxSize) { buffer.append(read(1)); if (buffer.endsWith(SeparatorToken)) break; } return buffer.size() - numBytesBeforeRead; } int Connection::dataLengthForCurrentDataType() { if (bytesAvailable() <= 0 || readDataIntoBuffer() <= 0 || !buffer.endsWith(SeparatorToken)) return 0; buffer.chop(1); int number = buffer.toInt(); buffer.clear(); return number; } bool Connection::readProtocolHeader() { if (transferTimerId) { killTimer(transferTimerId); transferTimerId = 0; } if (readDataIntoBuffer() <= 0) { transferTimerId = startTimer(TransferTimeout); return false; } if (buffer == "PING ") { currentDataType = Ping; } else if (buffer == "PONG ") { currentDataType = Pong; } else if (buffer == "MESSAGE ") { currentDataType = PlainText; } else if (buffer == "GREETING ") { currentDataType = Greeting; } else { currentDataType = Undefined; abort(); return false; } buffer.clear(); numBytesForCurrentDataType = dataLengthForCurrentDataType(); return true; } bool Connection::hasEnoughData() { if (transferTimerId) { QObject::killTimer(transferTimerId); transferTimerId = 0; } if (numBytesForCurrentDataType <= 0) numBytesForCurrentDataType = dataLengthForCurrentDataType(); if (bytesAvailable() < numBytesForCurrentDataType || numBytesForCurrentDataType <= 0) { transferTimerId = startTimer(TransferTimeout); return false; } return true; } void Connection::processData() { buffer = read(numBytesForCurrentDataType); if (buffer.size() != numBytesForCurrentDataType) { abort(); return; } switch (currentDataType) { case PlainText: emit newMessage(username, QString::fromUtf8(buffer)); break; case Ping: write("PONG 1 p"); break; case Pong: pongTime.restart(); break; default: break; } currentDataType = Undefined; numBytesForCurrentDataType = 0; buffer.clear(); }