1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2018 Intel Corporation.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the examples of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:BSD$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** BSD License Usage
19** Alternatively, you may use this file under the terms of the BSD license
20** as follows:
21**
22** "Redistribution and use in source and binary forms, with or without
23** modification, are permitted provided that the following conditions are
24** met:
25** * Redistributions of source code must retain the above copyright
26** notice, this list of conditions and the following disclaimer.
27** * Redistributions in binary form must reproduce the above copyright
28** notice, this list of conditions and the following disclaimer in
29** the documentation and/or other materials provided with the
30** distribution.
31** * Neither the name of The Qt Company Ltd nor the names of its
32** contributors may be used to endorse or promote products derived
33** from this software without specific prior written permission.
34**
35**
36** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
37** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
38** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
39** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
40** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
43** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
45** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
46** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
47**
48** $QT_END_LICENSE$
49**
50****************************************************************************/
51
52#include "connection.h"
53
54#include <QtNetwork>
55
56static const int TransferTimeout = 30 * 1000;
57static const int PongTimeout = 60 * 1000;
58static const int PingInterval = 5 * 1000;
59
60/*
61 * Protocol is defined as follows, using the CBOR Data Definition Language:
62 *
63 * protocol = [
64 * greeting, ; must start with a greeting command
65 * * command ; zero or more regular commands after
66 * ]
67 * command = plaintext / ping / pong / greeting
68 * plaintext = { 0 => text }
69 * ping = { 1 => null }
70 * pong = { 2 => null }
71 * greeting = { 3 => text }
72 */
73
74Connection::Connection(QObject *parent)
75 : QTcpSocket(parent), writer(this)
76{
77 greetingMessage = tr("undefined");
78 username = tr("unknown");
79 state = WaitingForGreeting;
80 currentDataType = Undefined;
81 transferTimerId = -1;
82 isGreetingMessageSent = false;
83 pingTimer.setInterval(PingInterval);
84
85 connect(this, &QTcpSocket::readyRead, this,
86 &Connection::processReadyRead);
87 connect(this, &QTcpSocket::disconnected,
88 &pingTimer, &QTimer::stop);
89 connect(&pingTimer, &QTimer::timeout,
90 this, &Connection::sendPing);
91 connect(this, &QTcpSocket::connected,
92 this, &Connection::sendGreetingMessage);
93}
94
95Connection::Connection(qintptr socketDescriptor, QObject *parent)
96 : Connection(parent)
97{
98 setSocketDescriptor(socketDescriptor);
99 reader.setDevice(this);
100}
101
102Connection::~Connection()
103{
104 if (isGreetingMessageSent) {
105 // Indicate clean shutdown.
106 writer.endArray();
107 waitForBytesWritten(2000);
108 }
109}
110
111QString Connection::name() const
112{
113 return username;
114}
115
116void Connection::setGreetingMessage(const QString &message)
117{
118 greetingMessage = message;
119}
120
121bool Connection::sendMessage(const QString &message)
122{
123 if (message.isEmpty())
124 return false;
125
126 writer.startMap(1);
127 writer.append(PlainText);
128 writer.append(message);
129 writer.endMap();
130 return true;
131}
132
133void Connection::timerEvent(QTimerEvent *timerEvent)
134{
135 if (timerEvent->timerId() == transferTimerId) {
136 abort();
137 killTimer(transferTimerId);
138 transferTimerId = -1;
139 }
140}
141
142void Connection::processReadyRead()
143{
144 // we've got more data, let's parse
145 reader.reparse();
146 while (reader.lastError() == QCborError::NoError) {
147 if (state == WaitingForGreeting) {
148 if (!reader.isArray())
149 break; // protocol error
150
151 reader.enterContainer(); // we'll be in this array forever
152 state = ReadingGreeting;
153 } else if (reader.containerDepth() == 1) {
154 // Current state: no command read
155 // Next state: read command ID
156 if (!reader.hasNext()) {
157 reader.leaveContainer();
158 disconnectFromHost();
159 return;
160 }
161
162 if (!reader.isMap() || !reader.isLengthKnown() || reader.length() != 1)
163 break; // protocol error
164 reader.enterContainer();
165 } else if (currentDataType == Undefined) {
166 // Current state: read command ID
167 // Next state: read command payload
168 if (!reader.isInteger())
169 break; // protocol error
170 currentDataType = DataType(reader.toInteger());
171 reader.next();
172 } else {
173 // Current state: read command payload
174 if (reader.isString()) {
175 auto r = reader.readString();
176 buffer += r.data;
177 if (r.status != QCborStreamReader::EndOfString)
178 continue;
179 } else if (reader.isNull()) {
180 reader.next();
181 } else {
182 break; // protocol error
183 }
184
185 // Next state: no command read
186 reader.leaveContainer();
187 if (transferTimerId != -1) {
188 killTimer(transferTimerId);
189 transferTimerId = -1;
190 }
191
192 if (state == ReadingGreeting) {
193 if (currentDataType != Greeting)
194 break; // protocol error
195 processGreeting();
196 } else {
197 processData();
198 }
199 }
200 }
201
202 if (reader.lastError() != QCborError::EndOfFile)
203 abort(); // parse error
204
205 if (transferTimerId != -1 && reader.containerDepth() > 1)
206 transferTimerId = startTimer(TransferTimeout);
207}
208
209void Connection::sendPing()
210{
211 if (pongTime.elapsed() > PongTimeout) {
212 abort();
213 return;
214 }
215
216 writer.startMap(1);
217 writer.append(Ping);
218 writer.append(nullptr); // no payload
219 writer.endMap();
220}
221
222void Connection::sendGreetingMessage()
223{
224 writer.startArray(); // this array never ends
225
226 writer.startMap(1);
227 writer.append(Greeting);
228 writer.append(greetingMessage);
229 writer.endMap();
230 isGreetingMessageSent = true;
231
232 if (!reader.device())
233 reader.setDevice(this);
234}
235
236void Connection::processGreeting()
237{
238 username = buffer + '@' + peerAddress().toString() + ':'
239 + QString::number(peerPort());
240 currentDataType = Undefined;
241 buffer.clear();
242
243 if (!isValid()) {
244 abort();
245 return;
246 }
247
248 if (!isGreetingMessageSent)
249 sendGreetingMessage();
250
251 pingTimer.start();
252 pongTime.start();
253 state = ReadyForUse;
254 emit readyForUse();
255}
256
257void Connection::processData()
258{
259 switch (currentDataType) {
260 case PlainText:
261 emit newMessage(username, buffer);
262 break;
263 case Ping:
264 writer.startMap(1);
265 writer.append(Pong);
266 writer.append(nullptr); // no payload
267 writer.endMap();
268 break;
269 case Pong:
270 pongTime.restart();
271 break;
272 default:
273 break;
274 }
275
276 currentDataType = Undefined;
277 buffer.clear();
278}
279