Fix lag display
[quassel.git] / src / common / protocols / legacy / legacypeer.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2013 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <QTcpSocket>
22
23 #include "legacypeer.h"
24
25 using namespace Protocol;
26
27 LegacyPeer::LegacyPeer(QTcpSocket *socket, QObject *parent)
28     : RemotePeer(socket, parent),
29     _blockSize(0),
30     _useCompression(false)
31 {
32     _stream.setDevice(socket);
33     _stream.setVersion(QDataStream::Qt_4_2);
34
35     connect(socket, SIGNAL(readyRead()), SLOT(socketDataAvailable()));
36 }
37
38
39 void LegacyPeer::setSignalProxy(::SignalProxy *proxy)
40 {
41     RemotePeer::setSignalProxy(proxy);
42
43     if (proxy) {
44         // enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
45         _useCompression = socket()->property("UseCompression").toBool();
46     }
47
48 }
49
50
51 void LegacyPeer::socketDataAvailable()
52 {
53     QVariant item;
54     while (readSocketData(item)) {
55         // if no sigproxy is set, we're in handshake mode and let the data be handled elsewhere
56         if (!signalProxy())
57             emit dataReceived(item);
58         else
59             handlePackedFunc(item);
60     }
61 }
62
63
64 bool LegacyPeer::readSocketData(QVariant &item)
65 {
66     if (_blockSize == 0) {
67         if (socket()->bytesAvailable() < 4)
68             return false;
69         _stream >> _blockSize;
70     }
71
72     if (_blockSize > 1 << 22) {
73         close("Peer tried to send package larger than max package size!");
74         return false;
75     }
76
77     if (_blockSize == 0) {
78         close("Peer tried to send 0 byte package!");
79         return false;
80     }
81
82     if (socket()->bytesAvailable() < _blockSize) {
83         emit transferProgress(socket()->bytesAvailable(), _blockSize);
84         return false;
85     }
86
87     emit transferProgress(_blockSize, _blockSize);
88
89     _blockSize = 0;
90
91     if (_useCompression) {
92         QByteArray rawItem;
93         _stream >> rawItem;
94
95         int nbytes = rawItem.size();
96         if (nbytes <= 4) {
97             const char *data = rawItem.constData();
98             if (nbytes < 4 || (data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 0)) {
99                 close("Peer sent corrupted compressed data!");
100                 return false;
101             }
102         }
103
104         rawItem = qUncompress(rawItem);
105
106         QDataStream itemStream(&rawItem, QIODevice::ReadOnly);
107         itemStream.setVersion(QDataStream::Qt_4_2);
108         itemStream >> item;
109     }
110     else {
111         _stream >> item;
112     }
113
114     if (!item.isValid()) {
115         close("Peer sent corrupt data: unable to load QVariant!");
116         return false;
117     }
118
119     return true;
120 }
121
122
123 void LegacyPeer::writeSocketData(const QVariant &item)
124 {
125     if (!socket()->isOpen()) {
126         qWarning() << Q_FUNC_INFO << "Can't write to a closed socket!";
127         return;
128     }
129
130     QByteArray block;
131     QDataStream out(&block, QIODevice::WriteOnly);
132     out.setVersion(QDataStream::Qt_4_2);
133
134     if (_useCompression) {
135         QByteArray rawItem;
136         QDataStream itemStream(&rawItem, QIODevice::WriteOnly);
137         itemStream.setVersion(QDataStream::Qt_4_2);
138         itemStream << item;
139
140         rawItem = qCompress(rawItem);
141
142         out << rawItem;
143     }
144     else {
145         out << item;
146     }
147
148     _stream << block;  // also writes the length as part of the serialization format
149 }
150
151
152 void LegacyPeer::handlePackedFunc(const QVariant &packedFunc)
153 {
154     QVariantList params(packedFunc.toList());
155
156     if (params.isEmpty()) {
157         qWarning() << Q_FUNC_INFO << "Received incompatible data:" << packedFunc;
158         return;
159     }
160
161     RequestType requestType = (RequestType)params.takeFirst().value<int>();
162     switch (requestType) {
163         case Sync: {
164             if (params.count() < 3) {
165                 qWarning() << Q_FUNC_INFO << "Received invalid sync call:" << params;
166                 return;
167             }
168             QByteArray className = params.takeFirst().toByteArray();
169             QString objectName = params.takeFirst().toString();
170             QByteArray slotName = params.takeFirst().toByteArray();
171             handle(Protocol::SyncMessage(className, objectName, slotName, params));
172             break;
173         }
174         case RpcCall: {
175             if (params.empty()) {
176                 qWarning() << Q_FUNC_INFO << "Received empty RPC call!";
177                 return;
178             }
179             QByteArray slotName = params.takeFirst().toByteArray();
180             handle(Protocol::RpcCall(slotName, params));
181             break;
182         }
183         case InitRequest: {
184             if (params.count() != 2) {
185                 qWarning() << Q_FUNC_INFO << "Received invalid InitRequest:" << params;
186                 return;
187             }
188             QByteArray className = params[0].toByteArray();
189             QString objectName = params[1].toString();
190             handle(Protocol::InitRequest(className, objectName));
191             break;
192         }
193         case InitData: {
194             if (params.count() != 3) {
195                 qWarning() << Q_FUNC_INFO << "Received invalid InitData:" << params;
196                 return;
197             }
198             QByteArray className = params[0].toByteArray();
199             QString objectName = params[1].toString();
200             QVariantMap initData = params[2].toMap();
201             handle(Protocol::InitData(className, objectName, initData));
202             break;
203         }
204         case HeartBeat: {
205             if (params.count() != 1) {
206                 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
207                 return;
208             }
209             // The legacy protocol would only send a QTime, no QDateTime
210             // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
211             QDateTime dateTime = QDateTime::currentDateTime().toUTC();
212             dateTime.setTime(params[0].toTime());
213             handle(Protocol::HeartBeat(dateTime));
214             break;
215         }
216         case HeartBeatReply: {
217             if (params.count() != 1) {
218                 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
219                 return;
220             }
221             // The legacy protocol would only send a QTime, no QDateTime
222             // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
223             QDateTime dateTime = QDateTime::currentDateTime().toUTC();
224             dateTime.setTime(params[0].toTime());
225             handle(Protocol::HeartBeatReply(dateTime));
226             break;
227         }
228
229     }
230 }
231
232
233 void LegacyPeer::dispatch(const Protocol::SyncMessage &msg)
234 {
235     dispatchPackedFunc(QVariantList() << (qint16)Sync << msg.className() << msg.objectName() << msg.slotName() << msg.params());
236 }
237
238
239 void LegacyPeer::dispatch(const Protocol::RpcCall &msg)
240 {
241     dispatchPackedFunc(QVariantList() << (qint16)RpcCall << msg.slotName() << msg.params());
242 }
243
244
245 void LegacyPeer::dispatch(const Protocol::InitRequest &msg)
246 {
247     dispatchPackedFunc(QVariantList() << (qint16)InitRequest << msg.className() << msg.objectName());
248 }
249
250
251 void LegacyPeer::dispatch(const Protocol::InitData &msg)
252 {
253     dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className() << msg.objectName() << msg.initData());
254 }
255
256
257 void LegacyPeer::dispatch(const Protocol::HeartBeat &msg)
258 {
259     dispatchPackedFunc(QVariantList() << (qint16)HeartBeat << msg.timestamp().time());
260 }
261
262
263 void LegacyPeer::dispatch(const Protocol::HeartBeatReply &msg)
264 {
265     dispatchPackedFunc(QVariantList() << (qint16)HeartBeatReply << msg.timestamp().time());
266 }
267
268
269 void LegacyPeer::dispatchPackedFunc(const QVariantList &packedFunc)
270 {
271     writeSocketData(QVariant(packedFunc));
272 }