1 /***************************************************************************
2 * Copyright (C) 2005-2013 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include <QHostAddress>
24 #include "legacypeer.h"
27 /* version.inc is no longer used for this */
28 const int protocolVersion = 10;
29 const int coreNeedsProtocol = protocolVersion;
30 const int clientNeedsProtocol = protocolVersion;
32 using namespace Protocol;
34 LegacyPeer::LegacyPeer(::AuthHandler *authHandler, QTcpSocket *socket, QObject *parent)
35 : RemotePeer(authHandler, socket, parent),
37 _useCompression(false)
39 _stream.setDevice(socket);
40 _stream.setVersion(QDataStream::Qt_4_2);
42 connect(socket, SIGNAL(readyRead()), SLOT(socketDataAvailable()));
46 void LegacyPeer::setSignalProxy(::SignalProxy *proxy)
48 RemotePeer::setSignalProxy(proxy);
50 // FIXME only in compat mode
53 // enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
54 _useCompression = socket()->property("UseCompression").toBool();
56 qDebug() << "Using compression for peer:" << qPrintable(socket()->peerAddress().toString());
62 void LegacyPeer::socketDataAvailable()
65 while (readSocketData(item)) {
66 // if no sigproxy is set, we're in handshake mode and let the data be handled elsewhere
68 handleHandshakeMessage(item);
70 handlePackedFunc(item);
75 bool LegacyPeer::readSocketData(QVariant &item)
77 if (_blockSize == 0) {
78 if (socket()->bytesAvailable() < 4)
80 _stream >> _blockSize;
83 if (_blockSize > 1 << 22) {
84 close("Peer tried to send package larger than max package size!");
88 if (_blockSize == 0) {
89 close("Peer tried to send 0 byte package!");
93 if (socket()->bytesAvailable() < _blockSize) {
94 emit transferProgress(socket()->bytesAvailable(), _blockSize);
98 emit transferProgress(_blockSize, _blockSize);
102 if (_useCompression) {
106 int nbytes = rawItem.size();
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!");
115 rawItem = qUncompress(rawItem);
117 QDataStream itemStream(&rawItem, QIODevice::ReadOnly);
118 itemStream.setVersion(QDataStream::Qt_4_2);
125 if (!item.isValid()) {
126 close("Peer sent corrupt data: unable to load QVariant!");
134 void LegacyPeer::writeSocketData(const QVariant &item)
136 if (!socket()->isOpen()) {
137 qWarning() << Q_FUNC_INFO << "Can't write to a closed socket!";
142 QDataStream out(&block, QIODevice::WriteOnly);
143 out.setVersion(QDataStream::Qt_4_2);
145 if (_useCompression) {
147 QDataStream itemStream(&rawItem, QIODevice::WriteOnly);
148 itemStream.setVersion(QDataStream::Qt_4_2);
151 rawItem = qCompress(rawItem);
159 _stream << block; // also writes the length as part of the serialization format
163 /*** Handshake messages ***/
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.
170 void LegacyPeer::handleHandshakeMessage(const QVariant &msg)
172 QVariantMap m = msg.toMap();
174 QString msgType = m["MsgType"].toString();
175 if (msgType.isEmpty()) {
176 emit protocolError(tr("Invalid handshake message!"));
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);
188 #ifndef QT_NO_COMPRESS
189 // FIXME only in compat mode
190 if (m["UseCompression"].toBool()) {
191 socket()->setProperty("UseCompression", true);
194 handle(RegisterClient(m["ClientVersion"].toString(), m["UseSsl"].toBool()));
197 else if (msgType == "ClientInitReject") {
198 handle(ClientDenied(m["Error"].toString()));
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);
208 #ifndef QT_NO_COMPRESS
209 if (m["SupportsCompression"].toBool())
210 socket()->setProperty("UseCompression", true);
213 handle(ClientRegistered(m["CoreFeatures"].toUInt(), m["Configured"].toBool(), m["StorageBackends"].toList(), m["SupportSsl"].toBool(), QDateTime()));
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()));
221 else if (msgType == "CoreSetupReject") {
222 handle(SetupFailed(m["Error"].toString()));
225 else if (msgType == "CoreSetupAck") {
229 else if (msgType == "ClientLogin") {
230 handle(Login(m["User"].toString(), m["Password"].toString()));
233 else if (msgType == "ClientLoginReject") {
234 handle(LoginFailed(m["Error"].toString()));
237 else if (msgType == "ClientLoginAck") {
238 handle(LoginSuccess());
241 else if (msgType == "SessionInit") {
242 QVariantMap map = m["SessionState"].toMap();
243 handle(SessionState(map["Identities"].toList(), map["BufferInfos"].toList(), map["NetworkIds"].toList()));
247 emit protocolError(tr("Unknown protocol message of type %1").arg(msgType));
252 void LegacyPeer::dispatch(const RegisterClient &msg) {
254 m["MsgType"] = "ClientInit";
255 m["ClientVersion"] = msg.clientVersion;
256 m["ClientDate"] = Quassel::buildInfo().buildDate;
258 // FIXME only in compat mode
259 m["ProtocolVersion"] = protocolVersion;
260 m["UseSsl"] = msg.sslSupported;
261 #ifndef QT_NO_COMPRESS
262 m["UseCompression"] = true;
264 m["UseCompression"] = false;
271 void LegacyPeer::dispatch(const ClientDenied &msg) {
273 m["MsgType"] = "ClientInitReject";
274 m["Error"] = msg.errorString;
280 void LegacyPeer::dispatch(const ClientRegistered &msg) {
282 m["MsgType"] = "ClientInitAck";
283 m["CoreFeatures"] = msg.coreFeatures;
284 m["StorageBackends"] = msg.backendInfo;
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
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>"
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));
302 m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
305 socket()->flush(); // ensure that the write cache is flushed before we switch to ssl
309 void LegacyPeer::dispatch(const SetupData &msg)
312 map["AdminUser"] = msg.adminUser;
313 map["AdminPasswd"] = msg.adminPassword;
314 map["Backend"] = msg.backend;
315 map["ConnectionProperties"] = msg.setupData;
318 m["MsgType"] = "CoreSetupData";
319 m["SetupData"] = map;
324 void LegacyPeer::dispatch(const SetupFailed &msg)
327 m["MsgType"] = "CoreSetupReject";
328 m["Error"] = msg.errorString;
334 void LegacyPeer::dispatch(const SetupDone &msg)
339 m["MsgType"] = "CoreSetupAck";
345 void LegacyPeer::dispatch(const Login &msg)
348 m["MsgType"] = "ClientLogin";
349 m["User"] = msg.user;
350 m["Password"] = msg.password;
356 void LegacyPeer::dispatch(const LoginFailed &msg)
359 m["MsgType"] = "ClientLoginReject";
360 m["Error"] = msg.errorString;
366 void LegacyPeer::dispatch(const LoginSuccess &msg)
371 m["MsgType"] = "ClientLoginAck";
377 void LegacyPeer::dispatch(const SessionState &msg)
380 m["MsgType"] = "SessionInit";
383 map["BufferInfos"] = msg.bufferInfos;
384 map["NetworkIds"] = msg.networkIds;
385 map["Identities"] = msg.identities;
386 m["SessionState"] = map;
392 /*** Standard messages ***/
394 void LegacyPeer::handlePackedFunc(const QVariant &packedFunc)
396 QVariantList params(packedFunc.toList());
398 if (params.isEmpty()) {
399 qWarning() << Q_FUNC_INFO << "Received incompatible data:" << packedFunc;
403 // TODO: make sure that this is a valid request type
404 RequestType requestType = (RequestType)params.takeFirst().value<int>();
405 switch (requestType) {
407 if (params.count() < 3) {
408 qWarning() << Q_FUNC_INFO << "Received invalid sync call:" << params;
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));
418 if (params.empty()) {
419 qWarning() << Q_FUNC_INFO << "Received empty RPC call!";
422 QByteArray slotName = params.takeFirst().toByteArray();
423 handle(Protocol::RpcCall(slotName, params));
427 if (params.count() != 2) {
428 qWarning() << Q_FUNC_INFO << "Received invalid InitRequest:" << params;
431 QByteArray className = params[0].toByteArray();
432 QString objectName = params[1].toString();
433 handle(Protocol::InitRequest(className, objectName));
437 if (params.count() != 3) {
438 qWarning() << Q_FUNC_INFO << "Received invalid InitData:" << params;
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));
448 if (params.count() != 1) {
449 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
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));
459 case HeartBeatReply: {
460 if (params.count() != 1) {
461 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
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));
476 void LegacyPeer::dispatch(const Protocol::SyncMessage &msg)
478 dispatchPackedFunc(QVariantList() << (qint16)Sync << msg.className << msg.objectName << msg.slotName << msg.params);
482 void LegacyPeer::dispatch(const Protocol::RpcCall &msg)
484 dispatchPackedFunc(QVariantList() << (qint16)RpcCall << msg.slotName << msg.params);
488 void LegacyPeer::dispatch(const Protocol::InitRequest &msg)
490 dispatchPackedFunc(QVariantList() << (qint16)InitRequest << msg.className << msg.objectName);
494 void LegacyPeer::dispatch(const Protocol::InitData &msg)
496 dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << msg.initData);
500 void LegacyPeer::dispatch(const Protocol::HeartBeat &msg)
502 dispatchPackedFunc(QVariantList() << (qint16)HeartBeat << msg.timestamp.time());
506 void LegacyPeer::dispatch(const Protocol::HeartBeatReply &msg)
508 dispatchPackedFunc(QVariantList() << (qint16)HeartBeatReply << msg.timestamp.time());
512 void LegacyPeer::dispatchPackedFunc(const QVariantList &packedFunc)
514 writeSocketData(QVariant(packedFunc));