344 lines
10 KiB
C++
344 lines
10 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;
|
|
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();
|
|
}
|