Happy New Year!
[quassel.git] / src / common / protocols / legacy / legacypeer.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2014 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 <QHostAddress>
22 #include <QTcpSocket>
23
24 #include "legacypeer.h"
25 #include "quassel.h"
26
27 /* version.inc is no longer used for this */
28 const uint protocolVersion = 10;
29 const uint coreNeedsProtocol = protocolVersion;
30 const uint clientNeedsProtocol = protocolVersion;
31
32 using namespace Protocol;
33
34 LegacyPeer::LegacyPeer(::AuthHandler *authHandler, QTcpSocket *socket, QObject *parent)
35     : RemotePeer(authHandler, socket, parent),
36     _blockSize(0),
37     _useCompression(false)
38 {
39     _stream.setDevice(socket);
40     _stream.setVersion(QDataStream::Qt_4_2);
41
42     connect(socket, SIGNAL(readyRead()), SLOT(socketDataAvailable()));
43 }
44
45
46 void LegacyPeer::setSignalProxy(::SignalProxy *proxy)
47 {
48     RemotePeer::setSignalProxy(proxy);
49
50     // FIXME only in compat mode
51     if (proxy) {
52         // enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
53         _useCompression = socket()->property("UseCompression").toBool();
54         if (_useCompression)
55             qDebug() << "Using compression for peer:" << qPrintable(socket()->peerAddress().toString());
56     }
57
58 }
59
60
61 void LegacyPeer::socketDataAvailable()
62 {
63     QVariant item;
64     while (readSocketData(item)) {
65         // if no sigproxy is set, we're in handshake mode and let the data be handled elsewhere
66         if (!signalProxy())
67             handleHandshakeMessage(item);
68         else
69             handlePackedFunc(item);
70     }
71 }
72
73
74 bool LegacyPeer::readSocketData(QVariant &item)
75 {
76     if (_blockSize == 0) {
77         if (socket()->bytesAvailable() < 4)
78             return false;
79         _stream >> _blockSize;
80     }
81
82     if (_blockSize > 1 << 22) {
83         close("Peer tried to send package larger than max package size!");
84         return false;
85     }
86
87     if (_blockSize == 0) {
88         close("Peer tried to send 0 byte package!");
89         return false;
90     }
91
92     if (socket()->bytesAvailable() < _blockSize) {
93         emit transferProgress(socket()->bytesAvailable(), _blockSize);
94         return false;
95     }
96
97     emit transferProgress(_blockSize, _blockSize);
98
99     _blockSize = 0;
100
101     if (_useCompression) {
102         QByteArray rawItem;
103         _stream >> rawItem;
104
105         int nbytes = rawItem.size();
106         if (nbytes <= 4) {
107             const char *data = rawItem.constData();
108             if (nbytes < 4 || (data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 0)) {
109                 close("Peer sent corrupted compressed data!");
110                 return false;
111             }
112         }
113
114         rawItem = qUncompress(rawItem);
115
116         QDataStream itemStream(&rawItem, QIODevice::ReadOnly);
117         itemStream.setVersion(QDataStream::Qt_4_2);
118         itemStream >> item;
119     }
120     else {
121         _stream >> item;
122     }
123
124     if (!item.isValid()) {
125         close("Peer sent corrupt data: unable to load QVariant!");
126         return false;
127     }
128
129     return true;
130 }
131
132
133 void LegacyPeer::writeSocketData(const QVariant &item)
134 {
135     if (!socket()->isOpen()) {
136         qWarning() << Q_FUNC_INFO << "Can't write to a closed socket!";
137         return;
138     }
139
140     QByteArray block;
141     QDataStream out(&block, QIODevice::WriteOnly);
142     out.setVersion(QDataStream::Qt_4_2);
143
144     if (_useCompression) {
145         QByteArray rawItem;
146         QDataStream itemStream(&rawItem, QIODevice::WriteOnly);
147         itemStream.setVersion(QDataStream::Qt_4_2);
148         itemStream << item;
149
150         rawItem = qCompress(rawItem);
151
152         out << rawItem;
153     }
154     else {
155         out << item;
156     }
157
158     _stream << block;  // also writes the length as part of the serialization format
159 }
160
161
162 /*** Handshake messages ***/
163
164 /* These messages are transmitted during handshake phase, which in case of the legacy protocol means they have
165  * a structure different from those being used after the handshake.
166  * Also, the legacy handshake does not fully match the redesigned one, so we'll have to do various mappings here.
167  */
168
169 void LegacyPeer::handleHandshakeMessage(const QVariant &msg)
170 {
171     QVariantMap m = msg.toMap();
172
173     QString msgType = m["MsgType"].toString();
174     if (msgType.isEmpty()) {
175         emit protocolError(tr("Invalid handshake message!"));
176         return;
177     }
178
179     if (msgType == "ClientInit") {
180         // FIXME only in compat mode
181         uint ver = m["ProtocolVersion"].toUInt();
182         if (ver < coreNeedsProtocol) {
183             emit protocolVersionMismatch((int)ver, (int)coreNeedsProtocol);
184             return;
185         }
186
187 #ifndef QT_NO_COMPRESS
188         // FIXME only in compat mode
189         if (m["UseCompression"].toBool()) {
190             socket()->setProperty("UseCompression", true);
191         }
192 #endif
193         handle(RegisterClient(m["ClientVersion"].toString(), m["UseSsl"].toBool()));
194     }
195
196     else if (msgType == "ClientInitReject") {
197         handle(ClientDenied(m["Error"].toString()));
198     }
199
200     else if (msgType == "ClientInitAck") {
201         // FIXME only in compat mode
202         uint ver = m["ProtocolVersion"].toUInt(); // actually an UInt
203         if (ver < clientNeedsProtocol) {
204             emit protocolVersionMismatch((int)ver, (int)clientNeedsProtocol);
205             return;
206         }
207 #ifndef QT_NO_COMPRESS
208         if (m["SupportsCompression"].toBool())
209             socket()->setProperty("UseCompression", true);
210 #endif
211
212         handle(ClientRegistered(m["CoreFeatures"].toUInt(), m["Configured"].toBool(), m["StorageBackends"].toList(), m["SupportSsl"].toBool(), QDateTime()));
213     }
214
215     else if (msgType == "CoreSetupData") {
216         QVariantMap map = m["SetupData"].toMap();
217         handle(SetupData(map["AdminUser"].toString(), map["AdminPasswd"].toString(), map["Backend"].toString(), map["ConnectionProperties"].toMap()));
218     }
219
220     else if (msgType == "CoreSetupReject") {
221         handle(SetupFailed(m["Error"].toString()));
222     }
223
224     else if (msgType == "CoreSetupAck") {
225         handle(SetupDone());
226     }
227
228     else if (msgType == "ClientLogin") {
229         handle(Login(m["User"].toString(), m["Password"].toString()));
230     }
231
232     else if (msgType == "ClientLoginReject") {
233         handle(LoginFailed(m["Error"].toString()));
234     }
235
236     else if (msgType == "ClientLoginAck") {
237         handle(LoginSuccess());
238     }
239
240     else if (msgType == "SessionInit") {
241         QVariantMap map = m["SessionState"].toMap();
242         handle(SessionState(map["Identities"].toList(), map["BufferInfos"].toList(), map["NetworkIds"].toList()));
243     }
244
245     else {
246         emit protocolError(tr("Unknown protocol message of type %1").arg(msgType));
247     }
248 }
249
250
251 void LegacyPeer::dispatch(const RegisterClient &msg) {
252     QVariantMap m;
253     m["MsgType"] = "ClientInit";
254     m["ClientVersion"] = msg.clientVersion;
255     m["ClientDate"] = Quassel::buildInfo().buildDate;
256
257     // FIXME only in compat mode
258     m["ProtocolVersion"] = protocolVersion;
259     m["UseSsl"] = msg.sslSupported;
260 #ifndef QT_NO_COMPRESS
261     m["UseCompression"] = true;
262 #else
263     m["UseCompression"] = false;
264 #endif
265
266     writeSocketData(m);
267 }
268
269
270 void LegacyPeer::dispatch(const ClientDenied &msg) {
271     QVariantMap m;
272     m["MsgType"] = "ClientInitReject";
273     m["Error"] = msg.errorString;
274
275     writeSocketData(m);
276 }
277
278
279 void LegacyPeer::dispatch(const ClientRegistered &msg) {
280     QVariantMap m;
281     m["MsgType"] = "ClientInitAck";
282     m["CoreFeatures"] = msg.coreFeatures;
283     m["StorageBackends"] = msg.backendInfo;
284
285     // FIXME only in compat mode
286     m["ProtocolVersion"] = protocolVersion;
287     m["SupportSsl"] = msg.sslSupported;
288     m["SupportsCompression"] = socket()->property("UseCompression").toBool(); // this property gets already set in the ClientInit handler
289
290     // This is only used for old v10 clients (pre-0.5)
291     int uptime = msg.coreStartTime.secsTo(QDateTime::currentDateTime().toUTC());
292     int updays = uptime / 86400; uptime %= 86400;
293     int uphours = uptime / 3600; uptime %= 3600;
294     int upmins = uptime / 60;
295     m["CoreInfo"] = tr("<b>Quassel Core Version %1</b><br>"
296                        "Built: %2<br>"
297                        "Up %3d%4h%5m (since %6)").arg(Quassel::buildInfo().fancyVersionString)
298                        .arg(Quassel::buildInfo().buildDate)
299                        .arg(updays).arg(uphours, 2, 10, QChar('0')).arg(upmins, 2, 10, QChar('0')).arg(msg.coreStartTime.toString(Qt::TextDate));
300
301     m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
302
303     writeSocketData(m);
304 }
305
306
307 void LegacyPeer::dispatch(const SetupData &msg)
308 {
309     QVariantMap map;
310     map["AdminUser"] = msg.adminUser;
311     map["AdminPasswd"] = msg.adminPassword;
312     map["Backend"] = msg.backend;
313     map["ConnectionProperties"] = msg.setupData;
314
315     QVariantMap m;
316     m["MsgType"] = "CoreSetupData";
317     m["SetupData"] = map;
318     writeSocketData(m);
319 }
320
321
322 void LegacyPeer::dispatch(const SetupFailed &msg)
323 {
324     QVariantMap m;
325     m["MsgType"] = "CoreSetupReject";
326     m["Error"] = msg.errorString;
327
328     writeSocketData(m);
329 }
330
331
332 void LegacyPeer::dispatch(const SetupDone &msg)
333 {
334     Q_UNUSED(msg)
335
336     QVariantMap m;
337     m["MsgType"] = "CoreSetupAck";
338
339     writeSocketData(m);
340 }
341
342
343 void LegacyPeer::dispatch(const Login &msg)
344 {
345     QVariantMap m;
346     m["MsgType"] = "ClientLogin";
347     m["User"] = msg.user;
348     m["Password"] = msg.password;
349
350     writeSocketData(m);
351 }
352
353
354 void LegacyPeer::dispatch(const LoginFailed &msg)
355 {
356     QVariantMap m;
357     m["MsgType"] = "ClientLoginReject";
358     m["Error"] = msg.errorString;
359
360     writeSocketData(m);
361 }
362
363
364 void LegacyPeer::dispatch(const LoginSuccess &msg)
365 {
366     Q_UNUSED(msg)
367
368     QVariantMap m;
369     m["MsgType"] = "ClientLoginAck";
370
371     writeSocketData(m);
372 }
373
374
375 void LegacyPeer::dispatch(const SessionState &msg)
376 {
377     QVariantMap m;
378     m["MsgType"] = "SessionInit";
379
380     QVariantMap map;
381     map["BufferInfos"] = msg.bufferInfos;
382     map["NetworkIds"] = msg.networkIds;
383     map["Identities"] = msg.identities;
384     m["SessionState"] = map;
385
386     writeSocketData(m);
387 }
388
389
390 /*** Standard messages ***/
391
392 void LegacyPeer::handlePackedFunc(const QVariant &packedFunc)
393 {
394     QVariantList params(packedFunc.toList());
395
396     if (params.isEmpty()) {
397         qWarning() << Q_FUNC_INFO << "Received incompatible data:" << packedFunc;
398         return;
399     }
400
401     // TODO: make sure that this is a valid request type
402     RequestType requestType = (RequestType)params.takeFirst().value<int>();
403     switch (requestType) {
404         case Sync: {
405             if (params.count() < 3) {
406                 qWarning() << Q_FUNC_INFO << "Received invalid sync call:" << params;
407                 return;
408             }
409             QByteArray className = params.takeFirst().toByteArray();
410             QString objectName = params.takeFirst().toString();
411             QByteArray slotName = params.takeFirst().toByteArray();
412             handle(Protocol::SyncMessage(className, objectName, slotName, params));
413             break;
414         }
415         case RpcCall: {
416             if (params.empty()) {
417                 qWarning() << Q_FUNC_INFO << "Received empty RPC call!";
418                 return;
419             }
420             QByteArray slotName = params.takeFirst().toByteArray();
421             handle(Protocol::RpcCall(slotName, params));
422             break;
423         }
424         case InitRequest: {
425             if (params.count() != 2) {
426                 qWarning() << Q_FUNC_INFO << "Received invalid InitRequest:" << params;
427                 return;
428             }
429             QByteArray className = params[0].toByteArray();
430             QString objectName = params[1].toString();
431             handle(Protocol::InitRequest(className, objectName));
432             break;
433         }
434         case InitData: {
435             if (params.count() != 3) {
436                 qWarning() << Q_FUNC_INFO << "Received invalid InitData:" << params;
437                 return;
438             }
439             QByteArray className = params[0].toByteArray();
440             QString objectName = params[1].toString();
441             QVariantMap initData = params[2].toMap();
442             handle(Protocol::InitData(className, objectName, initData));
443             break;
444         }
445         case HeartBeat: {
446             if (params.count() != 1) {
447                 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
448                 return;
449             }
450             // The legacy protocol would only send a QTime, no QDateTime
451             // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
452             QDateTime dateTime = QDateTime::currentDateTime().toUTC();
453             dateTime.setTime(params[0].toTime());
454             handle(Protocol::HeartBeat(dateTime));
455             break;
456         }
457         case HeartBeatReply: {
458             if (params.count() != 1) {
459                 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
460                 return;
461             }
462             // The legacy protocol would only send a QTime, no QDateTime
463             // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
464             QDateTime dateTime = QDateTime::currentDateTime().toUTC();
465             dateTime.setTime(params[0].toTime());
466             handle(Protocol::HeartBeatReply(dateTime));
467             break;
468         }
469
470     }
471 }
472
473
474 void LegacyPeer::dispatch(const Protocol::SyncMessage &msg)
475 {
476     dispatchPackedFunc(QVariantList() << (qint16)Sync << msg.className << msg.objectName << msg.slotName << msg.params);
477 }
478
479
480 void LegacyPeer::dispatch(const Protocol::RpcCall &msg)
481 {
482     dispatchPackedFunc(QVariantList() << (qint16)RpcCall << msg.slotName << msg.params);
483 }
484
485
486 void LegacyPeer::dispatch(const Protocol::InitRequest &msg)
487 {
488     dispatchPackedFunc(QVariantList() << (qint16)InitRequest << msg.className << msg.objectName);
489 }
490
491
492 void LegacyPeer::dispatch(const Protocol::InitData &msg)
493 {
494     dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << msg.initData);
495 }
496
497
498 void LegacyPeer::dispatch(const Protocol::HeartBeat &msg)
499 {
500     dispatchPackedFunc(QVariantList() << (qint16)HeartBeat << msg.timestamp.time());
501 }
502
503
504 void LegacyPeer::dispatch(const Protocol::HeartBeatReply &msg)
505 {
506     dispatchPackedFunc(QVariantList() << (qint16)HeartBeatReply << msg.timestamp.time());
507 }
508
509
510 void LegacyPeer::dispatchPackedFunc(const QVariantList &packedFunc)
511 {
512     writeSocketData(QVariant(packedFunc));
513 }