1 /***************************************************************************
2 * Copyright (C) 2005-2014 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 "datastreampeer.h"
27 /* version.inc is no longer used for this */
28 const uint protocolVersion = 10;
29 const uint coreNeedsProtocol = protocolVersion;
30 const uint clientNeedsProtocol = protocolVersion;
32 using namespace Protocol;
34 DataStreamPeer::DataStreamPeer(::AuthHandler *authHandler, QTcpSocket *socket, quint16 features, QObject *parent)
35 : RemotePeer(authHandler, socket, parent),
37 _useCompression(false)
41 _stream.setDevice(socket);
42 _stream.setVersion(QDataStream::Qt_4_2);
46 void DataStreamPeer::setSignalProxy(::SignalProxy *proxy)
48 RemotePeer::setSignalProxy(proxy);
50 // FIXME only in compat mode
52 // enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
53 _useCompression = socket()->property("UseCompression").toBool();
55 qDebug() << "Using compression for peer:" << qPrintable(socket()->peerAddress().toString());
61 quint16 DataStreamPeer::supportedFeatures()
67 bool DataStreamPeer::acceptsFeatures(quint16 peerFeatures)
69 Q_UNUSED(peerFeatures);
74 quint16 DataStreamPeer::enabledFeatures() const
80 void DataStreamPeer::onSocketDataAvailable()
83 while (readSocketData(item)) {
84 // if no sigproxy is set, we're in handshake mode and let the data be handled elsewhere
86 handleHandshakeMessage(item);
88 handlePackedFunc(item);
93 bool DataStreamPeer::readSocketData(QVariant &item)
95 if (_blockSize == 0) {
96 if (socket()->bytesAvailable() < 4)
98 _stream >> _blockSize;
101 if (_blockSize > 1 << 22) {
102 close("Peer tried to send package larger than max package size!");
106 if (_blockSize == 0) {
107 close("Peer tried to send 0 byte package!");
111 if (socket()->bytesAvailable() < _blockSize) {
112 emit transferProgress(socket()->bytesAvailable(), _blockSize);
116 emit transferProgress(_blockSize, _blockSize);
120 if (_useCompression) {
124 int nbytes = rawItem.size();
126 const char *data = rawItem.constData();
127 if (nbytes < 4 || (data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 0)) {
128 close("Peer sent corrupted compressed data!");
133 rawItem = qUncompress(rawItem);
135 QDataStream itemStream(&rawItem, QIODevice::ReadOnly);
136 itemStream.setVersion(QDataStream::Qt_4_2);
143 if (!item.isValid()) {
144 close("Peer sent corrupt data: unable to load QVariant!");
152 void DataStreamPeer::writeSocketData(const QVariant &item)
154 if (!socket()->isOpen()) {
155 qWarning() << Q_FUNC_INFO << "Can't write to a closed socket!";
160 QDataStream out(&block, QIODevice::WriteOnly);
161 out.setVersion(QDataStream::Qt_4_2);
163 if (_useCompression) {
165 QDataStream itemStream(&rawItem, QIODevice::WriteOnly);
166 itemStream.setVersion(QDataStream::Qt_4_2);
169 rawItem = qCompress(rawItem);
177 _stream << block; // also writes the length as part of the serialization format
181 /*** Handshake messages ***/
183 /* These messages are transmitted during handshake phase, which in case of the legacy protocol means they have
184 * a structure different from those being used after the handshake.
185 * Also, the legacy handshake does not fully match the redesigned one, so we'll have to do various mappings here.
188 void DataStreamPeer::handleHandshakeMessage(const QVariant &msg)
190 QVariantMap m = msg.toMap();
192 QString msgType = m["MsgType"].toString();
193 if (msgType.isEmpty()) {
194 emit protocolError(tr("Invalid handshake message!"));
198 if (msgType == "ClientInit") {
199 // FIXME only in compat mode
200 uint ver = m["ProtocolVersion"].toUInt();
201 if (ver < coreNeedsProtocol) {
202 emit protocolVersionMismatch((int)ver, (int)coreNeedsProtocol);
206 #ifndef QT_NO_COMPRESS
207 // FIXME only in compat mode
208 if (m["UseCompression"].toBool()) {
209 socket()->setProperty("UseCompression", true);
212 handle(RegisterClient(m["ClientVersion"].toString(), m["UseSsl"].toBool()));
215 else if (msgType == "ClientInitReject") {
216 handle(ClientDenied(m["Error"].toString()));
219 else if (msgType == "ClientInitAck") {
220 // FIXME only in compat mode
221 uint ver = m["ProtocolVersion"].toUInt(); // actually an UInt
222 if (ver < clientNeedsProtocol) {
223 emit protocolVersionMismatch((int)ver, (int)clientNeedsProtocol);
226 #ifndef QT_NO_COMPRESS
227 if (m["SupportsCompression"].toBool())
228 socket()->setProperty("UseCompression", true);
231 handle(ClientRegistered(m["CoreFeatures"].toUInt(), m["Configured"].toBool(), m["StorageBackends"].toList(), m["SupportSsl"].toBool(), QDateTime()));
234 else if (msgType == "CoreSetupData") {
235 QVariantMap map = m["SetupData"].toMap();
236 handle(SetupData(map["AdminUser"].toString(), map["AdminPasswd"].toString(), map["Backend"].toString(), map["ConnectionProperties"].toMap()));
239 else if (msgType == "CoreSetupReject") {
240 handle(SetupFailed(m["Error"].toString()));
243 else if (msgType == "CoreSetupAck") {
247 else if (msgType == "ClientLogin") {
248 handle(Login(m["User"].toString(), m["Password"].toString()));
251 else if (msgType == "ClientLoginReject") {
252 handle(LoginFailed(m["Error"].toString()));
255 else if (msgType == "ClientLoginAck") {
256 handle(LoginSuccess());
259 else if (msgType == "SessionInit") {
260 QVariantMap map = m["SessionState"].toMap();
261 handle(SessionState(map["Identities"].toList(), map["BufferInfos"].toList(), map["NetworkIds"].toList()));
265 emit protocolError(tr("Unknown protocol message of type %1").arg(msgType));
270 void DataStreamPeer::dispatch(const RegisterClient &msg) {
272 m["MsgType"] = "ClientInit";
273 m["ClientVersion"] = msg.clientVersion;
274 m["ClientDate"] = Quassel::buildInfo().buildDate;
276 // FIXME only in compat mode
277 m["ProtocolVersion"] = protocolVersion;
278 m["UseSsl"] = msg.sslSupported;
279 #ifndef QT_NO_COMPRESS
280 m["UseCompression"] = true;
282 m["UseCompression"] = false;
289 void DataStreamPeer::dispatch(const ClientDenied &msg) {
291 m["MsgType"] = "ClientInitReject";
292 m["Error"] = msg.errorString;
298 void DataStreamPeer::dispatch(const ClientRegistered &msg) {
300 m["MsgType"] = "ClientInitAck";
301 m["CoreFeatures"] = msg.coreFeatures;
302 m["StorageBackends"] = msg.backendInfo;
304 // FIXME only in compat mode
305 m["ProtocolVersion"] = protocolVersion;
306 m["SupportSsl"] = msg.sslSupported;
307 m["SupportsCompression"] = socket()->property("UseCompression").toBool(); // this property gets already set in the ClientInit handler
309 // This is only used for old v10 clients (pre-0.5)
310 int uptime = msg.coreStartTime.secsTo(QDateTime::currentDateTime().toUTC());
311 int updays = uptime / 86400; uptime %= 86400;
312 int uphours = uptime / 3600; uptime %= 3600;
313 int upmins = uptime / 60;
314 m["CoreInfo"] = tr("<b>Quassel Core Version %1</b><br>"
316 "Up %3d%4h%5m (since %6)").arg(Quassel::buildInfo().fancyVersionString)
317 .arg(Quassel::buildInfo().buildDate)
318 .arg(updays).arg(uphours, 2, 10, QChar('0')).arg(upmins, 2, 10, QChar('0')).arg(msg.coreStartTime.toString(Qt::TextDate));
320 m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
326 void DataStreamPeer::dispatch(const SetupData &msg)
329 map["AdminUser"] = msg.adminUser;
330 map["AdminPasswd"] = msg.adminPassword;
331 map["Backend"] = msg.backend;
332 map["ConnectionProperties"] = msg.setupData;
335 m["MsgType"] = "CoreSetupData";
336 m["SetupData"] = map;
341 void DataStreamPeer::dispatch(const SetupFailed &msg)
344 m["MsgType"] = "CoreSetupReject";
345 m["Error"] = msg.errorString;
351 void DataStreamPeer::dispatch(const SetupDone &msg)
356 m["MsgType"] = "CoreSetupAck";
362 void DataStreamPeer::dispatch(const Login &msg)
365 m["MsgType"] = "ClientLogin";
366 m["User"] = msg.user;
367 m["Password"] = msg.password;
373 void DataStreamPeer::dispatch(const LoginFailed &msg)
376 m["MsgType"] = "ClientLoginReject";
377 m["Error"] = msg.errorString;
383 void DataStreamPeer::dispatch(const LoginSuccess &msg)
388 m["MsgType"] = "ClientLoginAck";
394 void DataStreamPeer::dispatch(const SessionState &msg)
397 m["MsgType"] = "SessionInit";
400 map["BufferInfos"] = msg.bufferInfos;
401 map["NetworkIds"] = msg.networkIds;
402 map["Identities"] = msg.identities;
403 m["SessionState"] = map;
409 /*** Standard messages ***/
411 void DataStreamPeer::handlePackedFunc(const QVariant &packedFunc)
413 QVariantList params(packedFunc.toList());
415 if (params.isEmpty()) {
416 qWarning() << Q_FUNC_INFO << "Received incompatible data:" << packedFunc;
420 // TODO: make sure that this is a valid request type
421 RequestType requestType = (RequestType)params.takeFirst().value<int>();
422 switch (requestType) {
424 if (params.count() < 3) {
425 qWarning() << Q_FUNC_INFO << "Received invalid sync call:" << params;
428 QByteArray className = params.takeFirst().toByteArray();
429 QString objectName = params.takeFirst().toString();
430 QByteArray slotName = params.takeFirst().toByteArray();
431 handle(Protocol::SyncMessage(className, objectName, slotName, params));
435 if (params.empty()) {
436 qWarning() << Q_FUNC_INFO << "Received empty RPC call!";
439 QByteArray slotName = params.takeFirst().toByteArray();
440 handle(Protocol::RpcCall(slotName, params));
444 if (params.count() != 2) {
445 qWarning() << Q_FUNC_INFO << "Received invalid InitRequest:" << params;
448 QByteArray className = params[0].toByteArray();
449 QString objectName = params[1].toString();
450 handle(Protocol::InitRequest(className, objectName));
454 if (params.count() != 3) {
455 qWarning() << Q_FUNC_INFO << "Received invalid InitData:" << params;
458 QByteArray className = params[0].toByteArray();
459 QString objectName = params[1].toString();
460 QVariantMap initData = params[2].toMap();
461 handle(Protocol::InitData(className, objectName, initData));
465 if (params.count() != 1) {
466 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
469 // The legacy protocol would only send a QTime, no QDateTime
470 // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
471 QDateTime dateTime = QDateTime::currentDateTime().toUTC();
472 dateTime.setTime(params[0].toTime());
473 handle(Protocol::HeartBeat(dateTime));
476 case HeartBeatReply: {
477 if (params.count() != 1) {
478 qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
481 // The legacy protocol would only send a QTime, no QDateTime
482 // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
483 QDateTime dateTime = QDateTime::currentDateTime().toUTC();
484 dateTime.setTime(params[0].toTime());
485 handle(Protocol::HeartBeatReply(dateTime));
493 void DataStreamPeer::dispatch(const Protocol::SyncMessage &msg)
495 dispatchPackedFunc(QVariantList() << (qint16)Sync << msg.className << msg.objectName << msg.slotName << msg.params);
499 void DataStreamPeer::dispatch(const Protocol::RpcCall &msg)
501 dispatchPackedFunc(QVariantList() << (qint16)RpcCall << msg.slotName << msg.params);
505 void DataStreamPeer::dispatch(const Protocol::InitRequest &msg)
507 dispatchPackedFunc(QVariantList() << (qint16)InitRequest << msg.className << msg.objectName);
511 void DataStreamPeer::dispatch(const Protocol::InitData &msg)
513 dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << msg.initData);
517 void DataStreamPeer::dispatch(const Protocol::HeartBeat &msg)
519 dispatchPackedFunc(QVariantList() << (qint16)HeartBeat << msg.timestamp.time());
523 void DataStreamPeer::dispatch(const Protocol::HeartBeatReply &msg)
525 dispatchPackedFunc(QVariantList() << (qint16)HeartBeatReply << msg.timestamp.time());
529 void DataStreamPeer::dispatchPackedFunc(const QVariantList &packedFunc)
531 writeSocketData(QVariant(packedFunc));