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