telldus/telldus-gui/Plugins/Live/LiveObject.cpp

347 lines
11 KiB
C++

#include "LiveObject.h"
#include "LiveMessageToken.h"
#include "config.h"
#include <QtNetwork>
#include <QtCore>
#include <QDesktopServices>
#include <QMessageBox>
#include <QDebug>
class LiveObject::PrivateData {
public:
class Server;
QSslSocket *socket;
QTimer pingTimer, pongTimer;
bool registered;
QUrl registerUrl;
QString uuid, hashMethod;
QNetworkAccessManager *manager;
QList<Server> serverList;
QDateTime serverRefreshTime;
};
class LiveObject::PrivateData::Server {
public:
QString address;
int port;
};
LiveObject::LiveObject( QScriptEngine *engine, QObject * parent )
: QObject(parent)
{
d = new PrivateData;
d->hashMethod = "sha1";
d->registered = false;
QSslSocket::addDefaultCaCertificates(":/Equifax_Secure_CA.pem");
d->socket = new QSslSocket(this);
d->socket->setProtocol( QSsl::TlsV1 );
connect(d->socket, SIGNAL(encrypted()), this, SLOT(p_connected()));
connect(d->socket, SIGNAL(disconnected()), this, SLOT(p_disconnected()));
connect(d->socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(d->socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));
connect(d->socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(stateChanged(QAbstractSocket::SocketState)));
connect(d->socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
d->pingTimer.setInterval(120000); //Two minutes
d->pongTimer.setInterval(360000); //Six minutes
d->pongTimer.setSingleShot(true);
connect(&d->pingTimer, SIGNAL(timeout()), this, SLOT(pingServer()));
connect(&d->pongTimer, SIGNAL(timeout()), this, SLOT(pongTimeout()));
d->manager = new QNetworkAccessManager(this);
connect(d->manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(serverAssignReply(QNetworkReply*)));
}
LiveObject::~LiveObject() {
delete d;
}
void LiveObject::activate() {
if (d->uuid == "" || d->socket->state() != QAbstractSocket::ConnectedState || !d->registerUrl.isValid()) {
return;
}
if (!QDesktopServices::openUrl(d->registerUrl)) {
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Information);
msgBox.setTextFormat(Qt::RichText);
msgBox.setWindowTitle(tr("Default browser could not be opened"));
msgBox.setText(tr("We could not open your default internet browser"));
msgBox.setInformativeText(tr("Please copy the following url and enter it in your browser:<br><a href='%1'>%1</a>").arg(d->registerUrl.toString()));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
}
}
void LiveObject::connectToServer() {
if (d->serverList.isEmpty()) {
this->refreshServerList();
} else if (d->serverRefreshTime.secsTo(QDateTime::currentDateTime()) > 300) { //Valid 5 min
this->refreshServerList();
} else {
d->socket->abort();
PrivateData::Server server = d->serverList.takeFirst();
emit errorChanged("");
emit statusChanged("Connecting to server " + server.address);
d->socket->connectToHostEncrypted(server.address, server.port);
}
}
void LiveObject::disconnect() {
d->socket->disconnectFromHost();
}
void LiveObject::readyRead() {
QByteArray ba = d->socket->readAll();
//qDebug() << ba;
QScopedPointer<LiveMessage> envelope(LiveMessage::fromByteArray(ba));
QString signature = envelope->name();
QScopedPointer<LiveMessage> msg(LiveMessage::fromByteArray(envelope->arg(0).stringVal.toUtf8()));
if (signatureForMessage(envelope->arg(0).stringVal.toUtf8()) != signature) {
//qDebug() << "HASH mismatch!" << msg->name();
return;
}
d->pongTimer.stop();
d->pongTimer.start();
if (msg->name() == "") {
return;
} else if (msg->name() == "disconnect") {
this->disconnect();
} else if (msg->name() == "registered") {
d->registered = true;
emit registered(msg->argument(0));
emit errorChanged("Registered");
} else if (msg->name() == "notregistered") {
LiveMessageToken token = msg->arg(0);
if (token.valueType != LiveMessageToken::Dictionary) {
return;
}
d->registerUrl = token.dictVal["url"].stringVal;
d->uuid = token.dictVal["uuid"].stringVal;
QSettings s;
s.setValue("Live/UUID", d->uuid);
emit notRegistered();
emit errorChanged("Not registered");
} else if (msg->name() == "command") {
if (msg->arg(0).valueType == LiveMessageToken::Dictionary && msg->arg(0).dictVal.contains("ACK")) {
int ack = msg->arg(0).dictVal["ACK"].intVal;
LiveMessage msg("ACK");
msg.append(ack);
this->sendMessage(msg);
}
emit messageReceived(msg.data());
} else {
emit messageReceived(msg.data());
}
}
void LiveObject::refreshServerList() {
emit errorChanged("");
emit statusChanged("Discover servers");
d->serverList.clear();
QUrl url(TELLDUS_LIVE_URI);
QPair<QString, QString> version("protocolVersion", "2");
QList<QPair<QString, QString> > query;
query.append(version);
url.setQueryItems(query);
d->manager->get(QNetworkRequest(url));
}
void LiveObject::pingServer() {
if (d->socket->state() != QAbstractSocket::ConnectedState) {
return;
}
LiveMessage msg("Ping");
this->sendMessage(msg);
}
void LiveObject::sendMessage(const LiveMessage &message) {
//Create a new signed message
QByteArray body = message.toByteArray();
LiveMessage msg(signatureForMessage(body));
msg.append(body);
d->socket->write(msg.toByteArray());
d->socket->flush();
d->pingTimer.stop();
d->pingTimer.start();
}
void LiveObject::sendMessage(LiveMessage *message) {
if (!d->registered) {
return;
}
this->sendMessage(*message);
}
void LiveObject::p_connected() {
//Lets find out our uuid
QSettings settings;
d->uuid = settings.value("Live/UUID", "").toString();
d->pingTimer.start(); //For pings
d->pongTimer.start(); //For pongs
LiveMessage msg("Register");
LiveMessageToken token;
token.valueType = LiveMessageToken::Dictionary;
token.dictVal["uuid"] = LiveMessageToken(d->uuid);
token.dictVal["key"] = LiveMessageToken(TELLDUS_LIVE_PUBLIC_KEY);
token.dictVal["hash"] = LiveMessageToken(d->hashMethod);
msg.append(token);
msg.append(generateVersionToken());
this->sendMessage(msg);
emit errorChanged("");
emit statusChanged("Connected");
emit connected();
}
void LiveObject::p_disconnected() {
d->pingTimer.stop();
d->pongTimer.stop();
if (d->registered) {
//Clear the registered status
emit errorChanged("Disconnected from server");
}
d->registered = false;
}
void LiveObject::error( QAbstractSocket::SocketError socketError ) {
emit errorChanged(d->socket->errorString());
}
void LiveObject::stateChanged( QAbstractSocket::SocketState socketState ) {
if (socketState == QAbstractSocket::UnconnectedState) {
int timeout = rand() % 40 + 10; //Random timeout from 10-50s to avoid flooding the servers
QTimer::singleShot(timeout*1000, this, SLOT(connectToServer()));
emit statusChanged("Reconnecting in " + QString::number(timeout) + " seconds...");
} else if (socketState == QAbstractSocket::ConnectingState) {
emit statusChanged("Connecting...");
emit errorChanged("");
}
}
void LiveObject::sslErrors( const QList<QSslError> & errors ) {
bool everythingOK = true;
foreach( QSslError error, errors ) {
switch( error.error() ) {
case QSslError::SelfSignedCertificate:
continue;
default:
//qDebug() << "SSL" << error.errorString();
everythingOK = false;
emit statusChanged("SSL Error");
emit errorChanged(error.errorString());
break;
}
}
if (everythingOK) {
d->socket->ignoreSslErrors();
}
}
void LiveObject::serverAssignReply( QNetworkReply *r ) {
r->deleteLater();
if (r->error() != QNetworkReply::NoError) {
int timeout = rand() % 300 + 60; //Random timeout from 60s-6min to avoid flooding the servers
emit errorChanged(r->errorString());
emit statusChanged("Retrying in " + QString::number(timeout) + " seconds...");
QTimer::singleShot(timeout * 1000, this, SLOT(connectToServer()));
return;
}
QXmlStreamReader xml(r);
xml.readNextStartElement(); // enter <servers>
bool found = false;
while (xml.readNextStartElement()) {
if (xml.name().toString().toUpper() == "SERVER") {
QXmlStreamAttributes attrs = xml.attributes();
PrivateData::Server server;
server.address = attrs.value("address").toString();
server.port = attrs.value("port").toString().toInt();
d->serverList << server;
found = true;
}
xml.skipCurrentElement();
}
if (found) {
//qDebug() << "Servers found, retry direct...";
d->serverRefreshTime = QDateTime::currentDateTime();
QTimer::singleShot(0, this, SLOT(connectToServer()));
} else {
int timeout = rand() % 300 + 60; //Random timeout from 60-6min to avoid flooding the servers
emit errorChanged("No servers found");
emit statusChanged("Retrying in " + QString::number(timeout) + " seconds...");
QTimer::singleShot(timeout * 1000, this, SLOT(connectToServer()));
}
}
QByteArray LiveObject::signatureForMessage( const QByteArray &message ) {
QCryptographicHash signature( QCryptographicHash::Sha1 );
signature.addData(message);
signature.addData(TELLDUS_LIVE_PRIVATE_KEY);
return signature.result().toHex();
}
LiveMessageToken LiveObject::generateVersionToken() {
LiveMessageToken token;
token.valueType = LiveMessageToken::Dictionary;
token.dictVal["protocol"] = LiveMessageToken(2);
token.dictVal["version"] = LiveMessageToken(TELLDUS_CENTER_VERSION);
#if defined(Q_WS_WIN)
token.dictVal["os"] = LiveMessageToken("windows");
switch (QSysInfo::WindowsVersion) {
case QSysInfo::WV_XP:
token.dictVal["os-version"] = LiveMessageToken("xp");
break;
case QSysInfo::WV_2003:
token.dictVal["os-version"] = LiveMessageToken("2003");
break;
case QSysInfo::WV_VISTA:
token.dictVal["os-version"] = LiveMessageToken("vista");
break;
case QSysInfo::WV_WINDOWS7:
token.dictVal["os-version"] = LiveMessageToken("win7");
break;
default:
token.dictVal["os-version"] = LiveMessageToken(QString("unknown %1").arg(QSysInfo::WindowsVersion));
}
#elif defined(Q_OS_MAC)
token.dictVal["os"] = LiveMessageToken("macosx");
switch (QSysInfo::MacintoshVersion) {
case QSysInfo::MV_LEOPARD:
token.dictVal["os-version"] = LiveMessageToken("leopard");
break;
case QSysInfo::MV_SNOWLEOPARD:
token.dictVal["os-version"] = LiveMessageToken("snowleopard");
break;
case QSysInfo::MV_SNOWLEOPARD+1: //TODO: MV_LION when we build against Qt-4.8
token.dictVal["os-version"] = LiveMessageToken("lion");
break;
case QSysInfo::MV_SNOWLEOPARD+2: //TODO: MV_MOUNTAINLION? when Qt has it
token.dictVal["os-version"] = LiveMessageToken("mountainlion");
break;
default:
token.dictVal["os-version"] = LiveMessageToken("unknown");
}
#elif defined(Q_OS_LINUX)
token.dictVal["os"] = LiveMessageToken("linux");
token.dictVal["os-version"] = LiveMessageToken("unknown");
#else
token.dictVal["os"] = LiveMessageToken("unknown");
token.dictVal["os-version"] = LiveMessageToken("");
#endif
return token;
}
void LiveObject::pongTimeout() {
this->disconnect();
}