Refactor SignalProxy, network and protocol code
[quassel.git] / src / common / protocols / legacy / legacyconnection.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2012 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 "legacyconnection.h"
22
23 LegacyConnection::LegacyConnection(QTcpSocket *socket, QObject *parent)
24     : RemoteConnection(socket, parent),
25     _blockSize(0),
26     _useCompression(false)
27 {
28     _stream.setDevice(socket);
29     _stream.setVersion(QDataStream::Qt_4_2);
30
31     connect(socket, SIGNAL(readyRead()), SLOT(socketDataAvailable()));
32 }
33
34
35 void LegacyConnection::setSignalProxy(SignalProxy *proxy)
36 {
37     RemoteConnection::setSignalProxy(proxy);
38
39     if (proxy) {
40         // enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
41         _useCompression = socket()->property("UseCompression").toBool();
42     }
43
44 }
45
46
47 void LegacyConnection::socketDataAvailable()
48 {
49     QVariant item;
50     while (readSocketData(item)) {
51         // if no sigproxy is set, we're in handshake mode and let the data be handled elsewhere
52         if (!signalProxy())
53             emit dataReceived(item);
54         else
55             handlePackedFunc(item);
56     }
57 }
58
59
60 bool LegacyConnection::readSocketData(QVariant &item)
61 {
62     if (_blockSize == 0) {
63         if (socket()->bytesAvailable() < 4)
64             return false;
65         _stream >> _blockSize;
66     }
67
68     if (_blockSize > 1 << 22) {
69         close("Peer tried to send package larger than max package size!");
70         return false;
71     }
72
73     if (_blockSize == 0) {
74         close("Peer tried to send 0 byte package!");
75         return false;
76     }
77
78     if (socket()->bytesAvailable() < _blockSize) {
79         emit transferProgress(socket()->bytesAvailable(), _blockSize);
80         return false;
81     }
82
83     emit transferProgress(_blockSize, _blockSize);
84
85     _blockSize = 0;
86
87     if (_useCompression) {
88         QByteArray rawItem;
89         _stream >> rawItem;
90
91         int nbytes = rawItem.size();
92         if (nbytes <= 4) {
93             const char *data = rawItem.constData();
94             if (nbytes < 4 || (data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 0)) {
95                 close("Peer sent corrupted compressed data!");
96                 return false;
97             }
98         }
99
100         rawItem = qUncompress(rawItem);
101
102         QDataStream itemStream(&rawItem, QIODevice::ReadOnly);
103         itemStream.setVersion(QDataStream::Qt_4_2);
104         itemStream >> item;
105     }
106     else {
107         _stream >> item;
108     }
109
110     if (!item.isValid()) {
111         close("Peer sent corrupt data: unable to load QVariant!");
112         return false;
113     }
114
115     return true;
116 }
117
118
119 void LegacyConnection::writeSocketData(const QVariant &item)
120 {
121     if (!socket()->isOpen()) {
122         qWarning() << Q_FUNC_INFO << "Can't write to a closed socket!";
123         return;
124     }
125
126     QByteArray block;
127     QDataStream out(&block, QIODevice::WriteOnly);
128     out.setVersion(QDataStream::Qt_4_2);
129
130     if (_useCompression) {
131         QByteArray rawItem;
132         QDataStream itemStream(&rawItem, QIODevice::WriteOnly);
133         itemStream.setVersion(QDataStream::Qt_4_2);
134         itemStream << item;
135
136         rawItem = qCompress(rawItem);
137
138         out << rawItem;
139     }
140     else {
141         out << item;
142     }
143
144     _stream << block;  // also writes the length as part of the serialization format
145 }
146
147
148 void LegacyConnection::handlePackedFunc(const QVariant &packedFunc)
149 {
150     QVariantList params(packedFunc.toList());
151
152     if (params.isEmpty()) {
153         qWarning() << Q_FUNC_INFO << "Received incompatible data:" << packedFunc;
154         return;
155     }
156
157     RequestType requestType = (RequestType)params.takeFirst().value<int>();
158     switch (requestType) {
159         case Sync: {
160             if (params.count() < 3) {
161                 qWarning() << Q_FUNC_INFO << "Received invalid sync call:" << params;
162                 return;
163             }
164             QByteArray className = params.takeFirst().toByteArray();
165             QString objectName = params.takeFirst().toString();
166             QByteArray slotName = params.takeFirst().toByteArray();
167             handle(SignalProxy::SyncMessage(className, objectName, slotName, params));
168             break;
169         }
170         case RpcCall: {
171             if (params.empty()) {
172                 qWarning() << Q_FUNC_INFO << "Received empty RPC call!";
173                 return;
174             }
175             QByteArray slotName = params.takeFirst().toByteArray();
176             handle(SignalProxy::RpcCall(slotName, params));
177             break;
178         }
179         case InitRequest: {
180             if (params.count() != 2) {
181                 qWarning() << Q_FUNC_INFO << "Received invalid InitRequest:" << params;
182                 return;
183             }
184             QByteArray className = params[0].toByteArray();
185             QString objectName = params[1].toString();
186             handle(SignalProxy::InitRequest(className, objectName));
187             break;
188         }
189         case InitData: {
190             if (params.count() != 3) {
191                 qWarning() << Q_FUNC_INFO << "Received invalid InitData:" << params;
192                 return;
193             }
194             QByteArray className = params[0].toByteArray();
195             QString objectName = params[1].toString();
196             QVariantMap initData = params[2].toMap();
197             handle(SignalProxy::InitData(className, objectName, initData));
198             break;
199         }
200         case HeartBeat: {
201             if (params.count() != 1) {
202                 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
203                 return;
204             }
205             // The legacy protocol would only send a QTime, no QDateTime
206             // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
207             QDateTime dateTime = QDateTime::currentDateTimeUtc();
208             dateTime.setTime(params[0].toTime());
209             handle(RemoteConnection::HeartBeat(dateTime));
210             break;
211         }
212         case HeartBeatReply: {
213             if (params.count() != 1) {
214                 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
215                 return;
216             }
217             // The legacy protocol would only send a QTime, no QDateTime
218             // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
219             QDateTime dateTime = QDateTime::currentDateTimeUtc();
220             dateTime.setTime(params[0].toTime());
221             handle(RemoteConnection::HeartBeatReply(dateTime));
222             break;
223         }
224
225     }
226 }
227
228
229 void LegacyConnection::dispatch(const SignalProxy::SyncMessage &msg)
230 {
231     dispatchPackedFunc(QVariantList() << (qint16)Sync << msg.className() << msg.objectName() << msg.slotName() << msg.params());
232 }
233
234
235 void LegacyConnection::dispatch(const SignalProxy::RpcCall &msg)
236 {
237     dispatchPackedFunc(QVariantList() << (qint16)RpcCall << msg.slotName() << msg.params());
238 }
239
240
241 void LegacyConnection::dispatch(const SignalProxy::InitRequest &msg)
242 {
243     dispatchPackedFunc(QVariantList() << (qint16)InitRequest << msg.className() << msg.objectName());
244 }
245
246
247 void LegacyConnection::dispatch(const SignalProxy::InitData &msg)
248 {
249     dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className() << msg.objectName() << msg.initData());
250 }
251
252
253 void LegacyConnection::dispatch(const RemoteConnection::HeartBeat &msg)
254 {
255     dispatchPackedFunc(QVariantList() << (qint16)HeartBeat << msg.timestamp().time());
256 }
257
258
259 void LegacyConnection::dispatch(const RemoteConnection::HeartBeatReply &msg)
260 {
261     dispatchPackedFunc(QVariantList() << (qint16)HeartBeatReply << msg.timestamp().time());
262 }
263
264
265 void LegacyConnection::dispatchPackedFunc(const QVariantList &packedFunc)
266 {
267     writeSocketData(QVariant(packedFunc));
268 }