/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2018 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, 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 The Qt Company Ltd 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; /* * Protocol is defined as follows, using the CBOR Data Definition Language: * * protocol = [ * greeting, ; must start with a greeting command * * command ; zero or more regular commands after * ] * command = plaintext / ping / pong / greeting * plaintext = { 0 => text } * ping = { 1 => null } * pong = { 2 => null } * greeting = { 3 => text } */ Connection::Connection(QObject *parent) : QTcpSocket(parent), writer(this) { greetingMessage = tr("undefined"); username = tr("unknown"); state = WaitingForGreeting; currentDataType = Undefined; transferTimerId = -1; isGreetingMessageSent = false; pingTimer.setInterval(PingInterval); connect(this, &QTcpSocket::readyRead, this, &Connection::processReadyRead); connect(this, &QTcpSocket::disconnected, &pingTimer, &QTimer::stop); connect(&pingTimer, &QTimer::timeout, this, &Connection::sendPing); connect(this, &QTcpSocket::connected, this, &Connection::sendGreetingMessage); } Connection::Connection(qintptr socketDescriptor, QObject *parent) : Connection(parent) { setSocketDescriptor(socketDescriptor); reader.setDevice(this); } Connection::~Connection() { if (isGreetingMessageSent) { // Indicate clean shutdown. writer.endArray(); waitForBytesWritten(2000); } } 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; writer.startMap(1); writer.append(PlainText); writer.append(message); writer.endMap(); return true; } void Connection::timerEvent(QTimerEvent *timerEvent) { if (timerEvent->timerId() == transferTimerId) { abort(); killTimer(transferTimerId); transferTimerId = -1; } } void Connection::processReadyRead() { // we've got more data, let's parse reader.reparse(); while (reader.lastError() == QCborError::NoError) { if (state == WaitingForGreeting) { if (!reader.isArray()) break; // protocol error reader.enterContainer(); // we'll be in this array forever state = ReadingGreeting; } else if (reader.containerDepth() == 1) { // Current state: no command read // Next state: read command ID if (!reader.hasNext()) { reader.leaveContainer(); disconnectFromHost(); return; } if (!reader.isMap() || !reader.isLengthKnown() || reader.length() != 1) break; // protocol error reader.enterContainer(); } else if (currentDataType == Undefined) { // Current state: read command ID // Next state: read command payload if (!reader.isInteger()) break; // protocol error currentDataType = DataType(reader.toInteger()); reader.next(); } else { // Current state: read command payload if (reader.isString()) { auto r = reader.readString(); buffer += r.data; if (r.status != QCborStreamReader::EndOfString) continue; } else if (reader.isNull()) { reader.next(); } else { break; // protocol error } // Next state: no command read reader.leaveContainer(); if (transferTimerId != -1) { killTimer(transferTimerId); transferTimerId = -1; } if (state == ReadingGreeting) { if (currentDataType != Greeting) break; // protocol error processGreeting(); } else { processData(); } } } if (reader.lastError() != QCborError::EndOfFile) abort(); // parse error if (transferTimerId != -1 && reader.containerDepth() > 1) transferTimerId = startTimer(TransferTimeout); } void Connection::sendPing() { if (pongTime.elapsed() > PongTimeout) { abort(); return; } writer.startMap(1); writer.append(Ping); writer.append(nullptr); // no payload writer.endMap(); } void Connection::sendGreetingMessage() { writer.startArray(); // this array never ends writer.startMap(1); writer.append(Greeting); writer.append(greetingMessage); writer.endMap(); isGreetingMessageSent = true; if (!reader.device()) reader.setDevice(this); } void Connection::processGreeting() { username = buffer + '@' + peerAddress().toString() + ':' + QString::number(peerPort()); currentDataType = Undefined; buffer.clear(); if (!isValid()) { abort(); return; } if (!isGreetingMessageSent) sendGreetingMessage(); pingTimer.start(); pongTime.start(); state = ReadyForUse; emit readyForUse(); } void Connection::processData() { switch (currentDataType) { case PlainText: emit newMessage(username, buffer); break; case Ping: writer.startMap(1); writer.append(Pong); writer.append(nullptr); // no payload writer.endMap(); break; case Pong: pongTime.restart(); break; default: break; } currentDataType = Undefined; buffer.clear(); }