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