summaryrefslogtreecommitdiff
path: root/agents
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2016-01-01 22:05:42 +0100
committerJavier <dev.git@javispedro.com>2016-01-01 22:05:42 +0100
commita45977185a485624095bff1a15024e9199eee676 (patch)
tree6cc57d085bdd01e493477c870dbe0548137998e1 /agents
parenta24034bdfea259cdc09c74217be75d4f9de0dce5 (diff)
downloadsapd-a45977185a485624095bff1a15024e9199eee676.tar.gz
sapd-a45977185a485624095bff1a15024e9199eee676.zip
reorganize source files into SAP and agents
Diffstat (limited to 'agents')
-rw-r--r--agents/hostmanageragent.cc57
-rw-r--r--agents/hostmanageragent.h27
-rw-r--r--agents/hostmanagerconn.cc233
-rw-r--r--agents/hostmanagerconn.h68
-rw-r--r--agents/musicagent.cc56
-rw-r--r--agents/musicagent.h26
-rw-r--r--agents/musicconn.cc181
-rw-r--r--agents/musicconn.h35
-rw-r--r--agents/notificationagent.cc57
-rw-r--r--agents/notificationagent.h27
-rw-r--r--agents/notificationconn.cc190
-rw-r--r--agents/notificationconn.h86
-rw-r--r--agents/webproxyagent.cc64
-rw-r--r--agents/webproxyagent.h27
-rw-r--r--agents/webproxyconn.cc187
-rw-r--r--agents/webproxyconn.h63
-rw-r--r--agents/webproxytrans.cc74
-rw-r--r--agents/webproxytrans.h42
18 files changed, 1500 insertions, 0 deletions
diff --git a/agents/hostmanageragent.cc b/agents/hostmanageragent.cc
new file mode 100644
index 0000000..3cc67c2
--- /dev/null
+++ b/agents/hostmanageragent.cc
@@ -0,0 +1,57 @@
+#include "sapsocket.h"
+#include "sapconnectionrequest.h"
+#include "sapserviceinfo.h"
+#include "sapchannelinfo.h"
+#include "hostmanagerconn.h"
+#include "hostmanageragent.h"
+
+static HostManagerAgent *agent = 0;
+static const QLatin1String hostmanager_profile("/system/hostmanager");
+
+HostManagerAgent::HostManagerAgent(QObject *parent)
+ : QObject(parent), _peer(0), _socket(0)
+{
+}
+
+HostManagerAgent* HostManagerAgent::instance()
+{
+ if (!agent) {
+ agent = new HostManagerAgent;
+ }
+ return agent;
+}
+
+void HostManagerAgent::peerFound(SAPPeer *peer)
+{
+ Q_UNUSED(peer);
+}
+
+void HostManagerAgent::requestConnection(SAPConnectionRequest *request)
+{
+ qDebug() << "Host manager request connection from" << request->peer()->peerName();
+ SAPConnection *conn = request->connection();
+ new HostManagerConn(conn, this);
+
+ request->accept();
+}
+
+void HostManagerAgent::registerServices(SAPManager *manager)
+{
+ SAPServiceInfo service;
+ SAPChannelInfo channel;
+
+ service.setProfile(hostmanager_profile);
+ service.setFriendlyName("HostManager");
+ service.setRole(SAPServiceInfo::RoleProvider);
+ service.setVersion(1, 0);
+ service.setConnectionTimeout(0);
+
+ channel.setChannelId(103);
+ channel.setPayloadType(SAPChannelInfo::PayloadJson);
+ channel.setQoSType(SAPChannelInfo::QoSUnrestrictedInOrder);
+ channel.setQoSDataRate(SAPChannelInfo::QoSDataRateLow);
+ channel.setQoSPriority(SAPChannelInfo::QoSPriorityHigh);
+ service.addChannel(channel);
+
+ manager->registerServiceAgent(service, instance());
+}
diff --git a/agents/hostmanageragent.h b/agents/hostmanageragent.h
new file mode 100644
index 0000000..aa9e148
--- /dev/null
+++ b/agents/hostmanageragent.h
@@ -0,0 +1,27 @@
+#ifndef HOSTMANAGER_H
+#define HOSTMANAGER_H
+
+#include <QtCore/QObject>
+#include "sappeer.h"
+#include "sapmanager.h"
+#include "sapagent.h"
+
+class HostManagerAgent : public QObject, public SAPAgent
+{
+ Q_OBJECT
+
+ explicit HostManagerAgent(QObject *parent = 0);
+
+public:
+ static HostManagerAgent * instance();
+ static void registerServices(SAPManager *manager);
+
+ void peerFound(SAPPeer *peer);
+ void requestConnection(SAPConnectionRequest *request);
+
+private:
+ SAPPeer *_peer;
+ SAPSocket *_socket;
+};
+
+#endif // HOSTMANAGER_H
diff --git a/agents/hostmanagerconn.cc b/agents/hostmanagerconn.cc
new file mode 100644
index 0000000..0e1f7bb
--- /dev/null
+++ b/agents/hostmanagerconn.cc
@@ -0,0 +1,233 @@
+#include <QtCore/QDebug>
+#include <QtCore/QDateTime>
+#include <QtCore/QTimeZone>
+#include <QtCore/QXmlStreamReader>
+#include <QtCore/QXmlStreamWriter>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+
+#if SAILFISH
+#include <libwatchfish/walltimemonitor.h>
+static watchfish::WallTimeMonitor *monitor = 0;
+#endif
+
+#include "sapmanager.h"
+#include "sappeer.h"
+#include "hostmanagerconn.h"
+
+HostManagerConn::HostManagerConn(SAPConnection *conn, QObject *parent)
+ : QObject(parent), _conn(conn), _socket(conn->getSocket(103))
+{
+ connect(_conn, SIGNAL(disconnected()), SLOT(deleteLater()));
+ connect(_conn, SIGNAL(destroyed()), SLOT(deleteLater()));
+ Q_ASSERT(_socket);
+ connect(_socket, SIGNAL(connected()), SLOT(handleConnected()));
+ connect(_socket, SIGNAL(messageReceived()), SLOT(handleMessageReceived()));
+}
+
+HostManagerConn::DeviceInfo HostManagerConn::parseDeviceInfo(const QString &xmlData)
+{
+ QXmlStreamReader r(xmlData);
+ DeviceInfo info;
+
+ // TODO
+
+ return info;
+}
+
+void HostManagerConn::sendMessage(const QJsonObject &msg)
+{
+ QJsonDocument doc(msg);
+ QByteArray data = doc.toJson(QJsonDocument::Compact);
+ qDebug() << "Send JSON:" << data;
+ _socket->send(QByteArray(2, '\0') + data);
+}
+
+void HostManagerConn::handleMessage(const QJsonObject &msg)
+{
+ const QString msgId = msg["msgId"].toString();
+ qDebug() << "Got JSON msg" << msgId;
+ if (msgId == "mgr_watch_info_res") {
+ QJsonObject reply;
+ QDateTime timestamp = QLocale("C").toDateTime(QString::fromLatin1(__DATE__ " " __TIME__).simplified(),
+ "MMM d yyyy HH:mm:ss");
+ reply["timestamp"] = QString("%1_%2").arg(timestamp.toTime_t())
+ .arg(_conn->peer()->localName().right(2));
+ reply["type"] = QLatin1String("connect");
+ reply["msgId"] = QLatin1String("mgr_wearable_status_req");
+ sendMessage(reply);
+ } else if (msgId == "mgr_wearable_status_res") {
+ // Do nothing; watch will next ask for host status
+ } else if (msgId == "mgr_host_status_req") {
+ QJsonObject reply;
+ reply["type"] = QLatin1String("connect");
+ reply["msgId"] = QLatin1String("mgr_host_status_res");
+ reply["preinstalled"] = QLatin1String("true");
+ reply["data"] = generateHostXml();
+ sendMessage(reply);
+ } else if (msgId == "mgr_status_exchange_done") {
+ performTimeSync();
+ QJsonObject reply;
+ reply["btMac"] = _conn->peer()->localName();
+ reply["msgId"] = QLatin1String("mgr_setupwizard_eula_finished_req");
+ reply["isOld"] = 1;
+ sendMessage(reply);
+ }
+}
+
+void HostManagerConn::performTimeSync()
+{
+ //{"date1224":"24","datetimeepoch":"1409343828044","safety_declared":"0","locale":"es_ES","safety_voice":"1",
+ // "safetyVersion":0,"timezone":"Europe\/Madrid","safety":"false","tablet":"true","dateformat":"dd-MM-yyyy",
+ // "isfrominitial":true,"msgId":"mgr_sync_init_setting_req","usingCamera":"false","safety_cam":"0",
+ // "datetime":"2014 08 29 22 23 48","incomingCall":"false"}
+
+ QJsonObject msg;
+ msg["msgId"] = QLatin1String("mgr_sync_init_setting_req");
+
+ msg["safety_declared"] = QLatin1String("0");
+ msg["safety_voice"] = QLatin1String("0");
+ msg["safetyVersion"] = QLatin1String("0");
+ msg["safety"] = QLatin1String("false");
+ msg["tablet"] = QLatin1String("false");
+ msg["incomingCall"] = QLatin1String("false");
+ msg["usingCamera"] = QLatin1String("false");
+ msg["safety_cam"] = QLatin1String("0");
+
+ QLocale l = QLocale::system();
+ msg["locale"] = l.name(); // i.e. es_ES
+ msg["date1224"] = l.timeFormat().contains('a', Qt::CaseInsensitive) ? QLatin1String("12") : QLatin1String("24");
+ msg["dateformat"] = QLocale::system().dateFormat(QLocale::ShortFormat);
+
+#if SAILFISH
+ // QTimeZone does not seem to work on Sailfish; use timed.
+ msg["timezone"] = monitor->timezone();
+#else
+ msg["timezone"] = QString::fromLatin1(QTimeZone::systemTimeZoneId());
+#endif
+
+ QDateTime dt = QDateTime::currentDateTime();
+ msg["datetimeepoch"] = QString::number(dt.currentMSecsSinceEpoch());
+ msg["datetime"] = dt.toString("yyyy MM dd hh mm ss");
+
+ sendMessage(msg);
+}
+
+QString HostManagerConn::generateHostXml()
+{
+ QString xml;
+ QXmlStreamWriter w(&xml);
+
+ w.setCodec("UTF-8");
+ w.setAutoFormatting(true);
+
+ w.writeStartDocument();
+
+ w.writeStartElement("DeviceStatus");
+ w.writeStartElement("device");
+ w.writeTextElement("deviceID", _conn->peer()->localName());
+ w.writeTextElement("deviceName", "none");
+ w.writeTextElement("devicePlatform", "android");
+ w.writeTextElement("devicePlatformVersion", "4.4.2");
+ w.writeTextElement("deviceType", "Host");
+ w.writeTextElement("modelNumber", "GT-I9500");
+ w.writeTextElement("swVersion", "android 4.4.2");
+
+ w.writeEmptyElement("connectivity");
+
+#if 0
+ w.writeStartElement("apps");
+ SAPManager *manager = SAPManager::instance();
+ foreach (const SAPManager::RegisteredApplication &app, manager->allPackages()) {
+ w.writeStartElement("app");
+ w.writeTextElement("name", app.name);
+ w.writeTextElement("packagename", app.package);
+ w.writeTextElement("version", QString::number(app.version));
+ w.writeTextElement("preloaded", app.preinstalled ? QLatin1String("true") : QLatin1String("false"));
+ w.writeTextElement("isAppWidget", "false");
+ w.writeStartElement("features");
+ w.writeTextElement("Installed", "true");
+ w.writeEndElement();
+ w.writeEndElement();
+ }
+ w.writeEndElement();
+#else
+ xml.append(QString::fromLatin1("<apps>"
+ "<app><name>Actualizar el software del Gear</name><packagename>com.sec.android.fotaprovider</packagename><version>2</version><preloaded>false</preloaded><isAppWidget>false</isAppWidget><features/></app>"
+ "<app><name>ConnectionManager</name><packagename>com.sec.android.service.connectionmanager</packagename><version>1004</version><preloaded>false</preloaded><isAppWidget>false</isAppWidget><features/></app>"
+ "<app><name>goproviders</name><packagename>com.samsung.accessory.goproviders</packagename><version>61</version><preloaded>false</preloaded><isAppWidget>false</isAppWidget><features/></app>"
+ "<app><name>SAFileTransferCore</name><packagename>com.samsung.accessory.safiletransfer</packagename><version>1</version><preloaded>false</preloaded><isAppWidget>false</isAppWidget><features/></app>"
+ "<app><name>SANotiProvider</name><packagename>com.samsung.accessory.sanotiprovider</packagename><version>1</version><preloaded>false</preloaded><isAppWidget>false</isAppWidget><features/></app>"
+ "<app><name>saproviders</name><packagename>com.samsung.accessory.saproviders</packagename><version>64</version><preloaded>false</preloaded><isAppWidget>false</isAppWidget><features/></app><app><name>TextTemplateProvider</name><packagename>com.samsung.accessory.texttemplateprovider</packagename><version>1300</version><preloaded>false</preloaded><isAppWidget>false</isAppWidget><features/></app>"
+ "<app><packagename>com.samsung.accessory.saproviders</packagename><version>64</version><features><RequiringPackage>com.samsung.w-calendar2</RequiringPackage><Installed>true</Installed></features></app><app><packagename>com.samsung.accessory.goproviders</packagename><version>61</version><features><RequiringPackage>com.samsung.wfmd</RequiringPackage><Installed>true</Installed></features></app>"
+ "<app><packagename>com.sec.android.weatherprovider</packagename><features><RequiringPackage>com.samsung.weather</RequiringPackage><Installed>false</Installed></features></app>"
+ "<app><packagename>com.samsung.accessory.goproviders</packagename><version>61</version><features><RequiringPackage>com.samsung.w-contacts2</RequiringPackage><Installed>true</Installed></features></app><app><packagename>com.samsung.accessory.saproviders</packagename><version>64</version><features><RequiringPackage>com.samsung.w-media-controller</RequiringPackage><Installed>true</Installed></features></app>"
+ "<app><packagename>com.samsung.accessory.saproviders</packagename><version>64</version><features><RequiringPackage>com.samsung.alarm</RequiringPackage><Installed>true</Installed></features></app><app><packagename>com.samsung.accessory.saproviders</packagename><version>64</version><features><RequiringPackage>com.samsung.message</RequiringPackage><Installed>true</Installed></features></app>"
+ "<app><packagename>com.samsung.accessory.saproviders</packagename><version>64</version><features><RequiringPackage>com.samsung.w-logs2</RequiringPackage><Installed>true</Installed></features></app><app><packagename>com.samsung.accessory.saproviders</packagename><version>64</version><features><RequiringPackage>com.samsung.idle-clock-event</RequiringPackage><Installed>true</Installed></features></app>"
+ "<app><packagename>com.sec.android.weatherprovider</packagename><features><RequiringPackage>com.samsung.w-idle-clock-weather2</RequiringPackage><Installed>false</Installed></features></app><app><packagename>com.samsung.accessory.saproviders</packagename><version>64</version><features><RequiringPackage>com.samsung.idle-clock-dual</RequiringPackage><Installed>true</Installed></features></app>"
+ "<app><packagename>com.samsung.accessory.saproviders</packagename><version>64</version><features><RequiringPackage>com.samsung.svoice-w</RequiringPackage><Installed>true</Installed></features></app>"
+ "</apps>"));
+#endif
+
+ w.writeStartElement("deviceFeature");
+ w.writeTextElement("telephony", "true");
+ w.writeTextElement("messaging", "true");
+ w.writeTextElement("tablet", "false");
+ w.writeTextElement("autolock", "true");
+ w.writeTextElement("smartrelay", "true");
+ w.writeTextElement("safetyassistance", "false");
+ w.writeTextElement("vendor", "Samsung");
+ w.writeEndElement();
+
+ w.writeEmptyElement("security");
+ w.writeEmptyElement("notification");
+ w.writeEmptyElement("settings");
+
+ w.writeEndElement();
+
+ w.writeEndElement();
+
+ w.writeEndDocument();
+
+ return xml;
+}
+
+void HostManagerConn::handleConnected()
+{
+ qDebug() << "Manager socket now connected!";
+
+#if SAILFISH
+ if (!monitor) {
+ monitor = new watchfish::WallTimeMonitor;
+ }
+#endif
+
+ QJsonObject obj;
+ obj["btMac"] = _conn->peer()->localName();
+ obj["msgId"] = QLatin1String("mgr_watch_info_req");
+ obj["hmVer"] = QLatin1String("2.0.14041404");
+ sendMessage(obj);
+}
+
+void HostManagerConn::handleMessageReceived()
+{
+ QByteArray data = _socket->receive();
+
+ if (data.size() < 4) {
+ qWarning() << "Invalid HostManager message received";
+ return;
+ }
+
+ data.remove(0, 2); // Remove still-unknown header
+
+ qDebug() << "Got JSON:" << QString::fromUtf8(data);
+
+ QJsonParseError error;
+ QJsonDocument json = QJsonDocument::fromJson(data, &error);
+
+ if (json.isObject()) {
+ handleMessage(json.object());
+ } else {
+ qWarning() << "Cannot parse JSON msg:" << error.errorString();
+ }
+}
diff --git a/agents/hostmanagerconn.h b/agents/hostmanagerconn.h
new file mode 100644
index 0000000..3543a6c
--- /dev/null
+++ b/agents/hostmanagerconn.h
@@ -0,0 +1,68 @@
+#ifndef HOSTMANAGERPEER_H
+#define HOSTMANAGERPEER_H
+
+#include <QtCore/QVariant>
+#include "sapconnection.h"
+#include "sapsocket.h"
+
+class HostManagerConn : public QObject
+{
+ Q_OBJECT
+
+public:
+ HostManagerConn(SAPConnection *conn, QObject *parent = 0);
+
+protected:
+ struct AppInfo {
+ QString name;
+ QString packageName;
+ QString version;
+ bool preloaded;
+ bool isAppWidget;
+
+ QStringList requiredPackages;
+ QStringList requiringPackages;
+ bool installed;
+ };
+
+ struct DeviceInfo {
+ QString deviceId;
+ QString deviceName;
+ QString devicePlatform;
+ QString devicePlatformVersion;
+ QString deviceType;
+ QString modelNumber;
+ QString swVersion;
+
+ QList<AppInfo> apps;
+
+ bool telephony;
+ bool messaging;
+ bool tablet;
+ bool autolock;
+ bool smartrelay;
+ bool safetyassistence;
+ QString vendor;
+ };
+
+ static DeviceInfo parseDeviceInfo(const QString &xmlData);
+
+private:
+ void sendMessage(const QJsonObject &obj);
+
+ void handleMessage(const QJsonObject &obj);
+
+ void performTimeSync();
+
+ QString generateHostXml();
+
+private slots:
+ void handleConnected();
+ void handleMessageReceived();
+
+private:
+ SAPConnection *_conn;
+ SAPSocket *_socket;
+};
+
+#endif // HOSTMANAGERPEER_H
diff --git a/agents/musicagent.cc b/agents/musicagent.cc
new file mode 100644
index 0000000..f67cc1d
--- /dev/null
+++ b/agents/musicagent.cc
@@ -0,0 +1,56 @@
+#include "sapsocket.h"
+#include "sapconnectionrequest.h"
+#include "sapserviceinfo.h"
+#include "sapchannelinfo.h"
+#include "musicconn.h"
+#include "musicagent.h"
+
+static MusicAgent *agent = 0;
+static const QLatin1String music_profile("/system/music");
+
+MusicAgent::MusicAgent(QObject *parent)
+ : QObject(parent), _peer(0), _socket(0)
+{
+}
+
+MusicAgent* MusicAgent::instance()
+{
+ if (!agent) {
+ agent = new MusicAgent;
+ }
+ return agent;
+}
+
+void MusicAgent::peerFound(SAPPeer *peer)
+{
+ Q_UNUSED(peer);
+}
+
+void MusicAgent::requestConnection(SAPConnectionRequest *request)
+{
+ qDebug() << "MusicAgent request connection from" << request->peer()->peerName();
+ SAPConnection *conn = request->connection();
+ new MusicConn(conn, this);
+ request->accept();
+}
+
+void MusicAgent::registerServices(SAPManager *manager)
+{
+ SAPServiceInfo service;
+ SAPChannelInfo channel;
+
+ service.setProfile(music_profile);
+ service.setFriendlyName("Media controller");
+ service.setRole(SAPServiceInfo::RoleProvider);
+ service.setVersion(1, 0);
+ service.setConnectionTimeout(0);
+
+ channel.setChannelId(100);
+ channel.setPayloadType(SAPChannelInfo::PayloadNone);
+ channel.setQoSType(SAPChannelInfo::QoSReliabilityDisable);
+ channel.setQoSDataRate(SAPChannelInfo::QoSDataRateLow);
+ channel.setQoSPriority(SAPChannelInfo::QoSPriorityLow);
+ service.addChannel(channel);
+
+ manager->registerServiceAgent(service, instance());
+}
diff --git a/agents/musicagent.h b/agents/musicagent.h
new file mode 100644
index 0000000..32121d4
--- /dev/null
+++ b/agents/musicagent.h
@@ -0,0 +1,26 @@
+#ifndef MUSICAGENT_H
+#define MUSICAGENT_H
+
+#include <QtCore/QObject>
+#include "sappeer.h"
+#include "sapmanager.h"
+#include "sapagent.h"
+
+class MusicAgent : public QObject, public SAPAgent
+{
+public:
+ explicit MusicAgent(QObject *parent = 0);
+
+public:
+ static MusicAgent * instance();
+ static void registerServices(SAPManager *manager);
+
+ void peerFound(SAPPeer *peer);
+ void requestConnection(SAPConnectionRequest *request);
+
+private:
+ SAPPeer *_peer;
+ SAPSocket *_socket;
+};
+
+#endif // MUSICAGENT_H
diff --git a/agents/musicconn.cc b/agents/musicconn.cc
new file mode 100644
index 0000000..cfc375d
--- /dev/null
+++ b/agents/musicconn.cc
@@ -0,0 +1,181 @@
+#include <QtCore/QDebug>
+#include <QtCore/QBuffer>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtGui/QImage>
+
+#include "sappeer.h"
+#include "musicconn.h"
+
+#if SAILFISH
+#include "libwatchfish/musiccontroller.h"
+
+static watchfish::MusicController *controller = 0;
+#endif
+
+MusicConn::MusicConn(SAPConnection *conn, QObject *parent)
+ : QObject(parent), _conn(conn), _socket(conn->getSocket(100))
+{
+ connect(_conn, SIGNAL(disconnected()), SLOT(deleteLater()));
+ connect(_conn, SIGNAL(destroyed()), SLOT(deleteLater()));
+ Q_ASSERT(_socket);
+ connect(_socket, SIGNAL(connected()), SLOT(handleConnected()));
+ connect(_socket, SIGNAL(messageReceived()), SLOT(handleMessageReceived()));
+}
+
+QString MusicConn::encodeAlbumArt(const QString &albumArt)
+{
+ QImage image;
+
+ if (image.load(albumArt)) {
+ QByteArray imgData;
+ QBuffer buf(&imgData);
+ buf.open(QIODevice::WriteOnly);
+ image = image.scaled(160, 160, Qt::KeepAspectRatio);
+ image.save(&buf, "JPEG", 60);
+ buf.close();
+
+ return QString::fromLatin1(imgData.toBase64());
+ } else {
+ return QString();
+ }
+}
+
+void MusicConn::sendMessage(const QJsonObject &msg)
+{
+ QJsonDocument doc(msg);
+ QByteArray data = doc.toJson(QJsonDocument::Compact);
+
+ qDebug() << data;
+
+ _socket->send(data);
+}
+
+void MusicConn::handleMessage(const QJsonObject &msg)
+{
+ const QString msgId = msg["msgId"].toString();
+ if (msgId == "music-mediachanged-req") {
+ sendResponse("music-mediachanged-rsp", "success", 0);
+ sendMediaChangedInd();
+ } else if (msgId == "music-getattribute-req") {
+ QJsonObject rsp;
+ rsp.insert("msgId", QLatin1String("music-getattribute-rsp"));
+ rsp.insert("result", QLatin1String("success"));
+ rsp.insert("reason", 0);
+ QString repeat("repeatoff"), shuffle("off");
+ int volume = 100;
+#if SAILFISH
+ switch (controller->repeat()) {
+ case watchfish::MusicController::RepeatNone:
+ repeat = "repeatoff";
+ break;
+ case watchfish::MusicController::RepeatTrack:
+ repeat = "repeatone";
+ break;
+ case watchfish::MusicController::RepeatPlaylist:
+ repeat = "repeatall";
+ break;
+ }
+
+ if (controller->shuffle()) {
+ shuffle = "on";
+ }
+#endif
+ rsp.insert("repeat", repeat);
+ rsp.insert("shuffle", shuffle);
+ rsp.insert("volume", volume);
+
+ sendMessage(rsp);
+ } else if (msgId == "music-remotecontrol-req") {
+ QString action = msg["action"].toString();
+ QString status = msg["status"].toString();
+ bool pressed = status == "pressed";
+
+#if SAILFISH
+ if (action == "playpause" && pressed) {
+ controller->playPause();
+ } else if (action == "forward" && pressed) {
+ controller->next();
+ } else if (action == "rewind" && pressed) {
+ controller->previous();
+ }
+#endif
+ }
+}
+
+void MusicConn::sendResponse(const QString &id, const QString &result, int reason)
+{
+ QJsonObject obj;
+ obj.insert("msgId", id);
+ obj.insert("result", result);
+ obj.insert("reason", reason);
+ sendMessage(obj);
+}
+
+void MusicConn::sendMediaChangedInd()
+{
+ QJsonObject obj;
+ obj.insert("msgId", QLatin1String("music-mediachanged-ind"));
+
+#if SAILFISH
+ obj.insert("artist", controller->artist());
+ obj.insert("album", controller->album());
+ obj.insert("title", controller->title());
+ obj.insert("duration", QString::number(controller->duration()));
+ obj.insert("audioId", QString());
+ obj.insert("artistId", QString());
+ obj.insert("albumId", QString());
+ obj.insert("albumArt", QString());
+ if (controller->status() == watchfish::MusicController::StatusPlaying) {
+ obj.insert("playStatus", QLatin1String("true"));
+ } else {
+ obj.insert("playStatus", QLatin1String("false"));
+ }
+ QString albumArtPath = controller->albumArt();
+ if (!albumArtPath.isEmpty()) {
+ obj.insert("image", encodeAlbumArt(albumArtPath));
+ } else {
+ obj.insert("image", QString());
+ }
+ obj.insert("favoriteStatus", QLatin1String("false"));
+#endif
+
+ sendMessage(obj);
+}
+
+void MusicConn::handleConnected()
+{
+ qDebug() << "Music connected";
+#if SAILFISH
+ if (!controller) {
+ controller = new watchfish::MusicController;
+ }
+ connect(controller, &watchfish::MusicController::metadataChanged,
+ this, &MusicConn::handleMetadataChanged);
+ connect(controller, &watchfish::MusicController::statusChanged,
+ this, &MusicConn::handleMetadataChanged);
+#endif
+}
+
+void MusicConn::handleMessageReceived()
+{
+ QByteArray data = _socket->receive();
+
+ qDebug() << "Music msg received" << data;
+
+ QJsonParseError error;
+ QJsonDocument json = QJsonDocument::fromJson(data, &error);
+
+ if (json.isObject()) {
+ handleMessage(json.object());
+ } else {
+ qWarning() << "Cannot parse JSON msg:" << error.errorString();
+ }
+}
+
+void MusicConn::handleMetadataChanged()
+{
+ if (_socket->isOpen()) {
+ sendMediaChangedInd();
+ }
+}
diff --git a/agents/musicconn.h b/agents/musicconn.h
new file mode 100644
index 0000000..3cee914
--- /dev/null
+++ b/agents/musicconn.h
@@ -0,0 +1,35 @@
+#ifndef MUSICCONN_H
+#define MUSICCONN_H
+
+#include <QtCore/QObject>
+#include "sapconnection.h"
+#include "sapsocket.h"
+
+class MusicConn : public QObject
+{
+ Q_OBJECT
+
+public:
+ MusicConn(SAPConnection *conn, QObject *parent = 0);
+
+protected:
+ static QString encodeAlbumArt(const QString &albumArt);
+
+private:
+ void sendMessage(const QJsonObject &msg);
+ void handleMessage(const QJsonObject &msg);
+
+ void sendResponse(const QString &id, const QString &result, int reason);
+ void sendMediaChangedInd();
+
+private slots:
+ void handleConnected();
+ void handleMessageReceived();
+ void handleMetadataChanged();
+
+private:
+ SAPConnection *_conn;
+ SAPSocket *_socket;
+};
+
+#endif // MUSICCONN_H
diff --git a/agents/notificationagent.cc b/agents/notificationagent.cc
new file mode 100644
index 0000000..58d27d4
--- /dev/null
+++ b/agents/notificationagent.cc
@@ -0,0 +1,57 @@
+#include "sapsocket.h"
+#include "sapconnectionrequest.h"
+#include "sapserviceinfo.h"
+#include "sapchannelinfo.h"
+#include "notificationconn.h"
+#include "notificationagent.h"
+
+static NotificationAgent *agent = 0;
+static const QLatin1String notification_profile("/system/NotificationService");
+
+NotificationAgent::NotificationAgent(QObject *parent)
+ : QObject(parent), _peer(0), _socket(0)
+{
+}
+
+NotificationAgent* NotificationAgent::instance()
+{
+ if (!agent) {
+ agent = new NotificationAgent;
+ }
+ return agent;
+}
+
+void NotificationAgent::peerFound(SAPPeer *peer)
+{
+ Q_UNUSED(peer);
+}
+
+void NotificationAgent::requestConnection(SAPConnectionRequest *request)
+{
+ qDebug() << "Notification request connection from" << request->peer()->peerName();
+ SAPConnection *conn = request->connection();
+ new NotificationConn(conn, this);
+
+ request->accept();
+}
+
+void NotificationAgent::registerServices(SAPManager *manager)
+{
+ SAPServiceInfo service;
+ SAPChannelInfo channel;
+
+ service.setProfile(notification_profile);
+ service.setFriendlyName("Notification");
+ service.setRole(SAPServiceInfo::RoleProvider);
+ service.setVersion(1, 0);
+ service.setConnectionTimeout(0);
+
+ channel.setChannelId(104);
+ channel.setPayloadType(SAPChannelInfo::PayloadBinary);
+ channel.setQoSType(SAPChannelInfo::QoSReliabilityDisable);
+ channel.setQoSDataRate(SAPChannelInfo::QoSDataRateLow);
+ channel.setQoSPriority(SAPChannelInfo::QoSPriorityLow);
+ service.addChannel(channel);
+
+ manager->registerServiceAgent(service, instance());
+}
diff --git a/agents/notificationagent.h b/agents/notificationagent.h
new file mode 100644
index 0000000..1b9c9cb
--- /dev/null
+++ b/agents/notificationagent.h
@@ -0,0 +1,27 @@
+#ifndef NOTIFICATIONAGENT_H
+#define NOTIFICATIONAGENT_H
+
+#include <QtCore/QObject>
+#include "sappeer.h"
+#include "sapmanager.h"
+#include "sapagent.h"
+
+class NotificationAgent : public QObject, public SAPAgent
+{
+ Q_OBJECT
+
+ explicit NotificationAgent(QObject *parent = 0);
+
+public:
+ static NotificationAgent * instance();
+ static void registerServices(SAPManager *manager);
+
+ void peerFound(SAPPeer *peer);
+ void requestConnection(SAPConnectionRequest *request);
+
+private:
+ SAPPeer *_peer;
+ SAPSocket *_socket;
+};
+
+#endif // NOTIFICATIONAGENT_H
diff --git a/agents/notificationconn.cc b/agents/notificationconn.cc
new file mode 100644
index 0000000..bfe719b
--- /dev/null
+++ b/agents/notificationconn.cc
@@ -0,0 +1,190 @@
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+#include <QtCore/QFile>
+
+#include "sappeer.h"
+#include "endianhelpers.h"
+#include "notificationconn.h"
+
+#if SAILFISH
+#include "libwatchfish/notificationmonitor.h"
+#include "libwatchfish/notification.h"
+
+static watchfish::NotificationMonitor *monitor = 0;
+#endif
+
+NotificationConn::Notification::Notification()
+ : type(NotificationPopup), sequenceNumber(0), urgent(false),
+ applicationId(0), category(0),
+ count(0), sender(0),
+ notificationId(-1)
+{
+}
+
+NotificationConn::NotificationConn(SAPConnection *conn, QObject *parent)
+ : QObject(parent), _conn(conn), _socket(conn->getSocket(104))
+{
+ connect(_conn, SIGNAL(disconnected()), SLOT(deleteLater()));
+ connect(_conn, SIGNAL(destroyed()), SLOT(deleteLater()));
+ Q_ASSERT(_socket);
+ connect(_socket, SIGNAL(connected()), SLOT(handleConnected()));
+ connect(_socket, SIGNAL(messageReceived()), SLOT(handleMessageReceived()));
+}
+
+QByteArray NotificationConn::packNotification(const Notification &n)
+{
+ QByteArray data;
+
+ append<quint8>(data, 0x10 | n.type);
+ append<quint16>(data, n.sequenceNumber | (n.urgent ? 0x4000 : 0));
+
+ append<quint16>(data, n.applicationId);
+ append<quint8>(data, n.category);
+
+ int attributeCount = 0;
+
+ // Let's count attributes first
+ if (!n.packageName.isEmpty()) attributeCount++;
+ if (n.count >= 0) attributeCount++;
+ if (!n.title.isEmpty()) attributeCount++;
+ if (n.time.isValid()) attributeCount++;
+ if (n.sender >= 0) attributeCount++;
+ if (!n.body.isEmpty()) attributeCount++;
+ if (!n.thumbnail.isEmpty()) attributeCount++;
+ if (!n.applicationName.isEmpty()) attributeCount++;
+ // TODO if (n.openInHost) attributeCount++;
+ //if (n.openInWatch) attributeCount++;
+ if (n.notificationId != -1) attributeCount++;
+
+ append<quint8>(data, attributeCount);
+
+ if (n.time.isValid()) {
+ append<quint32>(data, (NotificationTime << 24) | sizeof(qint64));
+ append<qint64>(data, n.time.toMSecsSinceEpoch());
+ }
+ if (!n.thumbnail.isEmpty()) {
+ append<quint32>(data, (NotificationThumbnail << 24) | n.thumbnail.size());
+ data.append(n.thumbnail);
+ }
+ if (!n.applicationName.isEmpty()) {
+ QByteArray str = n.applicationName.toUtf8();
+ append<quint32>(data, (NotificationApplicationName << 24) | str.length());
+ data.append(str);
+ }
+ if (!n.packageName.isEmpty()) {
+ QByteArray str = n.packageName.toUtf8();
+ append<quint32>(data, (NotificationPackageName << 24) | str.length());
+ data.append(str);
+ }
+ if (!n.title.isEmpty()) {
+ QByteArray str = n.title.toUtf8();
+ append<quint32>(data, (NotificationTitle << 24) | str.length());
+ data.append(str);
+ }
+ if (!n.body.isEmpty()) {
+ QByteArray str = n.body.toUtf8();
+ append<quint32>(data, (NotificationBody << 24) | str.length());
+ data.append(str);
+ }
+ if (n.count >= 0) {
+ append<quint32>(data, (NotificationCount << 24) | sizeof(quint16));
+ append<quint16>(data, n.count);
+ }
+ if (n.sender >= 0) {
+ append<quint32>(data, (NotificationSender << 24) | sizeof(quint16));
+ append<quint16>(data, n.sender);
+ }
+ if (n.notificationId != -1) {
+ append<quint32>(data, (NotificationId << 24) | sizeof(quint32));
+ append<quint32>(data, n.notificationId);
+ }
+
+ return data;
+}
+
+void NotificationConn::sendNotification(const Notification &n)
+{
+ QByteArray packet = packNotification(n);
+
+ qDebug() << packet.toHex();
+
+ _socket->send(packet);
+}
+
+void NotificationConn::handleConnected()
+{
+ qDebug() << "NotificationManager socket now connected!";
+
+#if SAILFISH
+ if (!monitor) {
+ monitor = new watchfish::NotificationMonitor;
+ }
+ connect(monitor, &watchfish::NotificationMonitor::notification,
+ this, &NotificationConn::handleNotification);
+#else
+ QTimer::singleShot(2000, this, SLOT(performTest()));
+#endif
+}
+
+void NotificationConn::handleMessageReceived()
+{
+ QByteArray data = _socket->receive();
+
+ qDebug() << "Got notif msg" << data.toHex();
+
+ // TODO Seems that we receive the notification ID that we should act upon.
+}
+
+#if SAILFISH
+void NotificationConn::handleNotification(watchfish::Notification *wfn)
+{
+ if (wfn->transient()) {
+ // Ignore this notification
+ return;
+ }
+
+ QString sender = wfn->sender();
+ short applicationId = _knownSenders.value(sender, 0);
+ if (!applicationId) {
+ applicationId = _knownSenders.size() + 1;
+ _knownSenders.insert(sender, applicationId);
+ }
+
+ Notification n;
+ n.sequenceNumber = ++_lastSeqNumber;
+ n.type = NotificationPopup;
+ n.time = wfn->timestamp();
+ n.title = wfn->summary();
+ n.packageName = sender;
+ n.applicationName = sender;
+ n.body = wfn->body();
+ n.sender = 0;
+ n.count = 0; // TODO figure this out
+ n.category = 0;
+ n.applicationId = applicationId;
+ n.notificationId = ++_lastNotifId;
+
+ sendNotification(n);
+}
+#endif
+
+void NotificationConn::performTest()
+{
+ qDebug() << "Performing notif test";
+
+ Notification n;
+ n.sequenceNumber = ++_lastSeqNumber;
+ n.type = NotificationPopup;
+ n.time = QDateTime::currentDateTime();
+ n.title = "A title";
+ n.packageName = "com.example.package";
+ n.applicationName = "Example package";
+ n.body = "A long body";
+ n.sender = 0;
+ n.count = 13;
+ n.category = 0;
+ n.applicationId = 0xc2d7;
+ n.notificationId = ++_lastNotifId;
+
+ sendNotification(n);
+}
diff --git a/agents/notificationconn.h b/agents/notificationconn.h
new file mode 100644
index 0000000..092804a
--- /dev/null
+++ b/agents/notificationconn.h
@@ -0,0 +1,86 @@
+#ifndef NOTIFICATIONCONN_H
+#define NOTIFICATIONCONN_H
+
+#include <QtCore/QObject>
+#include <QtCore/QDateTime>
+#include <QtCore/QHash>
+#include "sapconnection.h"
+#include "sapsocket.h"
+
+#if SAILFISH
+namespace watchfish
+{
+class Notification;
+class NotificationMonitor;
+}
+#endif
+
+class NotificationConn : public QObject
+{
+ Q_OBJECT
+
+public:
+ NotificationConn(SAPConnection *conn, QObject *parent = 0);
+
+protected:
+ enum NotificationType {
+ NotificationPopup = 0
+ };
+
+ enum NotificationAttributeType {
+ NotificationPackageName = 0,
+ NotificationCount = 1,
+ NotificationTitle = 2,
+ NotificationTime = 3,
+ NotificationSender = 4,
+ NotificationBody = 5,
+ NotificationThumbnail = 6,
+ NotificationApplicationName = 7,
+ NotificationOpenInWatch = 9,
+ NotificationOpenInHost = 10,
+ NotificationId = 11
+ };
+
+ struct Notification {
+ Notification();
+
+ NotificationType type;
+ int sequenceNumber;
+ bool urgent;
+ int applicationId;
+ int category;
+ QString packageName;
+ int count;
+ QString title;
+ QDateTime time;
+ int sender;
+ QString body;
+ QByteArray thumbnail;
+ QString applicationName;
+ //bool openInWatch;
+ //bool openInHost;
+ int notificationId;
+ };
+
+ QByteArray packNotification(const Notification &n);
+ void sendNotification(const Notification &n);
+
+private slots:
+ void handleConnected();
+ void handleMessageReceived();
+
+#if SAILFISH
+ void handleNotification(watchfish::Notification *n);
+#endif
+
+ void performTest();
+
+private:
+ SAPConnection *_conn;
+ SAPSocket *_socket;
+ QHash<QString, short> _knownSenders;
+ int _lastSeqNumber;
+ int _lastNotifId;
+};
+
+#endif // NOTIFICATIONCONN_H
diff --git a/agents/webproxyagent.cc b/agents/webproxyagent.cc
new file mode 100644
index 0000000..c9d5580
--- /dev/null
+++ b/agents/webproxyagent.cc
@@ -0,0 +1,64 @@
+#include "sapsocket.h"
+#include "sapconnectionrequest.h"
+#include "sapserviceinfo.h"
+#include "sapchannelinfo.h"
+#include "webproxyconn.h"
+#include "webproxyagent.h"
+
+static WebProxyAgent *agent = 0;
+static const QLatin1String webproxy_profile("/system/webproxy");
+
+WebProxyAgent::WebProxyAgent(QObject *parent)
+ : QObject(parent), _peer(0), _socket(0)
+{
+}
+
+WebProxyAgent* WebProxyAgent::instance()
+{
+ if (!agent) {
+ agent = new WebProxyAgent;
+ }
+ return agent;
+}
+
+void WebProxyAgent::peerFound(SAPPeer *peer)
+{
+ Q_UNUSED(peer);
+}
+
+void WebProxyAgent::requestConnection(SAPConnectionRequest *request)
+{
+ qDebug() << "WebProxy request connection from" << request->peer()->peerName();
+ SAPConnection *conn = request->connection();
+ new WebProxyConn(conn, this);
+
+ request->accept();
+}
+
+void WebProxyAgent::registerServices(SAPManager *manager)
+{
+ SAPServiceInfo service;
+ SAPChannelInfo channel;
+
+ service.setProfile(webproxy_profile);
+ service.setFriendlyName("WebProxy");
+ service.setRole(SAPServiceInfo::RoleProvider);
+ service.setVersion(2, 0);
+ service.setConnectionTimeout(0);
+
+ channel.setChannelId(501);
+ channel.setPayloadType(SAPChannelInfo::PayloadBinary);
+ channel.setQoSType(SAPChannelInfo::QoSReliabilityEnable);
+ channel.setQoSDataRate(SAPChannelInfo::QoSDataRateLow);
+ channel.setQoSPriority(SAPChannelInfo::QoSPriorityLow);
+ service.addChannel(channel);
+
+ channel.setChannelId(502);
+ channel.setPayloadType(SAPChannelInfo::PayloadBinary);
+ channel.setQoSType(SAPChannelInfo::QoSReliabilityEnable);
+ channel.setQoSDataRate(SAPChannelInfo::QoSDataRateLow);
+ channel.setQoSPriority(SAPChannelInfo::QoSPriorityLow);
+ service.addChannel(channel);
+
+ manager->registerServiceAgent(service, instance());
+}
diff --git a/agents/webproxyagent.h b/agents/webproxyagent.h
new file mode 100644
index 0000000..e3e7329
--- /dev/null
+++ b/agents/webproxyagent.h
@@ -0,0 +1,27 @@
+#ifndef WEBPROXYAGENT_H
+#define WEBPROXYAGENT_H
+
+#include <QtCore/QObject>
+#include "sappeer.h"
+#include "sapmanager.h"
+#include "sapagent.h"
+
+class WebProxyAgent : public QObject, public SAPAgent
+{
+ Q_OBJECT
+
+ explicit WebProxyAgent(QObject *parent = 0);
+
+public:
+ static WebProxyAgent * instance();
+ static void registerServices(SAPManager *manager);
+
+ void peerFound(SAPPeer *peer);
+ void requestConnection(SAPConnectionRequest *request);
+
+private:
+ SAPPeer *_peer;
+ SAPSocket *_socket;
+};
+
+#endif // WEBPROXYAGENT_H
diff --git a/agents/webproxyconn.cc b/agents/webproxyconn.cc
new file mode 100644
index 0000000..91a9510
--- /dev/null
+++ b/agents/webproxyconn.cc
@@ -0,0 +1,187 @@
+#include <QtCore/QDebug>
+#include <QtCore/QUrl>
+
+#include "sappeer.h"
+#include "endianhelpers.h"
+#include "webproxytrans.h"
+#include "webproxyconn.h"
+
+WebProxyConn::WebProxyConn(SAPConnection *conn, QObject *parent)
+ : QObject(parent), _conn(conn),
+ _in(conn->getSocket(501)), _out(conn->getSocket(502))
+{
+ connect(_conn, SIGNAL(disconnected()), SLOT(deleteLater()));
+ connect(_conn, SIGNAL(destroyed()), SLOT(deleteLater()));
+ Q_ASSERT(_in && _out);
+ connect(_in, SIGNAL(messageReceived()), SLOT(handleMessageReceived()));
+}
+
+WebProxyConn::Message WebProxyConn::unpackMessage(const QByteArray &data)
+{
+ Message msg;
+ int offset = 0;
+ msg.command = read<quint8>(data, offset);
+ msg.subCommand = read<quint8>(data, offset);
+ msg.type = static_cast<MessageType>(read<quint8>(data, offset));
+ msg.transactionId = read<quint8>(data, offset);
+
+ const quint32 len = read<quint32>(data, offset);
+ msg.payload = data.mid(offset, len);
+
+ return msg;
+}
+
+QByteArray WebProxyConn::packMessage(const Message &msg)
+{
+ QByteArray data;
+ append<quint8>(data, msg.command);
+ append<quint8>(data, msg.subCommand);
+ append<quint8>(data, msg.type);
+ append<quint8>(data, msg.transactionId);
+ append<quint32>(data, msg.payload.size());
+ data.append(msg.payload);
+ return data;
+}
+
+WebProxyConn::RequestHeader WebProxyConn::parseRequestHeader(const QByteArray &req)
+{
+ RequestHeader hdr;
+ int first = req.indexOf('\n');
+ if (first <= 0) {
+ qWarning() << "Invalid request header";
+ return hdr;
+ }
+
+ QStringList reqLine = QString::fromLatin1(req.mid(0, first).trimmed()).split(' ');
+ QString method = reqLine.at(0);
+
+ if (QString::compare(method, "connect", Qt::CaseInsensitive) == 0) {
+ hdr.connect = true;
+ QStringList host = reqLine.at(1).split(':');
+ hdr.host = host.at(0);
+ hdr.port = host.at(1).toUInt();
+ } else {
+ QUrl url(reqLine.at(1));
+ hdr.connect = false;
+ hdr.host = url.host(QUrl::EncodeUnicode);
+ if (QString::compare(url.scheme(), "https", Qt::CaseInsensitive) == 0) {
+ hdr.port = url.port(443);
+ } else {
+ hdr.port = url.port(80);
+ }
+ }
+
+ return hdr;
+}
+
+QByteArray WebProxyConn::removeHeaders(const QByteArray &req)
+{
+ int offset = 0;
+ int next;
+
+ while ((next = req.indexOf('\n', offset)) > 0) {
+ QByteArray line = req.mid(offset, next - offset).trimmed();
+ if (line.isEmpty()) {
+ return req.mid(next + 1);
+ }
+ offset = next + 1;
+ }
+
+ return QByteArray();
+}
+
+void WebProxyConn::sendMessage(const Message &msg)
+{
+ _out->send(packMessage(msg));
+}
+
+void WebProxyConn::handleRequest(const Message &msg)
+{
+ QByteArray payload = msg.payload;
+ WebProxyTrans *trans = _trans.value(msg.transactionId, 0);
+
+ if (!trans) {
+ RequestHeader hdr = parseRequestHeader(msg.payload);
+ qDebug() << "Starting transaction" << msg.transactionId << "to" << hdr.host << hdr.port << (hdr.connect ? "tunnel" : "http");
+
+ trans = new WebProxyTrans(msg.transactionId, hdr.connect, hdr.host, hdr.port, this);
+ connect(trans, SIGNAL(dataReceived(QByteArray)), this, SLOT(handleTransDataReceived(QByteArray)));
+ connect(trans, SIGNAL(disconnected()), this, SLOT(handleTransDisconnected()));
+
+ // Discard request body, but if it was a CONNECT request.
+ if (hdr.connect) {
+ payload = removeHeaders(payload);
+ }
+
+ _trans.insert(msg.transactionId, trans);
+ }
+
+ if (!payload.isEmpty()) {
+ trans->write(payload);
+ }
+}
+
+void WebProxyConn::handleAbort(const Message &msg)
+{
+ qDebug() << "Abort transaction" << msg.transactionId;
+ WebProxyTrans *trans = _trans.value(msg.transactionId, 0);
+ if (trans) {
+ delete trans;
+ _trans.remove(msg.transactionId);
+ } else {
+ qWarning() << "Transaction" << msg.transactionId << "does not exist";
+ }
+}
+
+void WebProxyConn::handleMessageReceived()
+{
+ QByteArray data = _in->receive();
+ Message msg = unpackMessage(data);
+
+ if (msg.command != 1 || msg.subCommand != 1) {
+ qWarning() << "Invalid command/subcommand: " << msg.command << "/" << msg.subCommand;
+ return;
+ }
+
+ switch (msg.type) {
+ case MessageRequest:
+ handleRequest(msg);
+ break;
+ case MessageAbort:
+ handleAbort(msg);
+ break;
+ default:
+ qWarning() << "Unknown request type" << msg.type;
+ }
+}
+
+void WebProxyConn::handleTransDataReceived(const QByteArray &data)
+{
+ WebProxyTrans *trans = static_cast<WebProxyTrans*>(sender());
+ Message msg;
+ msg.command = 1;
+ msg.subCommand = 1;
+ msg.type = MessageResponse;
+ msg.transactionId = trans->transactionId();
+ msg.payload = data;
+
+ sendMessage(msg);
+}
+
+void WebProxyConn::handleTransDisconnected()
+{
+ WebProxyTrans *trans = static_cast<WebProxyTrans*>(sender());
+ Message msg;
+ msg.command = 1;
+ msg.subCommand = 1;
+ msg.type = MessageAbort;
+ msg.transactionId = trans->transactionId();
+ msg.payload.clear(); // Empty payload signals disconnection
+
+ qDebug() << "Sending disconnected event";
+
+ sendMessage(msg);
+
+ _trans.remove(msg.transactionId);
+ trans->deleteLater();
+}
diff --git a/agents/webproxyconn.h b/agents/webproxyconn.h
new file mode 100644
index 0000000..c183462
--- /dev/null
+++ b/agents/webproxyconn.h
@@ -0,0 +1,63 @@
+#ifndef WEBPROXYCONN_H
+#define WEBPROXYCONN_H
+
+#include <QtCore/QObject>
+#include "sapconnection.h"
+#include "sapsocket.h"
+
+class WebProxyTrans;
+
+class WebProxyConn : public QObject
+{
+ Q_OBJECT
+
+public:
+ WebProxyConn(SAPConnection *conn, QObject *parent = 0);
+
+protected:
+ enum MessageType {
+ MessageRequest = 1,
+ MessageResponse = 2,
+ MessageError = 3,
+ MessageAbort = 4
+ };
+
+ struct Message {
+ quint8 command; // Seems to be always 1
+ quint8 subCommand; // Seems to be always 1
+ MessageType type;
+ quint8 transactionId; // Monotonically increasing
+ QByteArray payload;
+ };
+
+ static Message unpackMessage(const QByteArray &data);
+ static QByteArray packMessage(const Message &msg);
+
+ struct RequestHeader {
+ /** Whether this is a CONNECT request, i.e. tunnel. */
+ bool connect;
+ QString host;
+ int port;
+ };
+
+ static RequestHeader parseRequestHeader(const QByteArray &req);
+ static QByteArray removeHeaders(const QByteArray &req);
+
+ void sendMessage(const Message &msg);
+
+ void handleRequest(const Message &msg);
+ void handleAbort(const Message &msg);
+
+private slots:
+ void handleMessageReceived();
+ void handleTransDataReceived(const QByteArray &data);
+ void handleTransDisconnected();
+
+private:
+ SAPConnection *_conn;
+ SAPSocket *_in;
+ SAPSocket *_out;
+ QMap<int, WebProxyTrans*> _trans;
+};
+
+#endif // WEBPROXYCONN_H
diff --git a/agents/webproxytrans.cc b/agents/webproxytrans.cc
new file mode 100644
index 0000000..23b41b7
--- /dev/null
+++ b/agents/webproxytrans.cc
@@ -0,0 +1,74 @@
+#include <QtCore/QDebug>
+#include <QtCore/QUrl>
+
+#include "webproxyconn.h"
+#include "webproxytrans.h"
+
+static const QByteArray connectResponse("HTTP/1.1 200 Connection established\r\n\r\n");
+
+WebProxyTrans::WebProxyTrans(int transactionId, bool tunnel, const QString &host, int port, WebProxyConn *conn)
+ : QObject(conn), _transactionId(transactionId), _tunnel(tunnel), _socket(new QTcpSocket(this))
+{
+ connect(_socket, SIGNAL(connected()), this, SLOT(handleSocketConnected()));
+ connect(_socket, SIGNAL(bytesWritten(qint64)), this, SLOT(handleSocketDataWritten(qint64)));
+ connect(_socket, SIGNAL(readyRead()), this, SLOT(handleSocketDataReady()));
+ connect(_socket, SIGNAL(disconnected()), this, SLOT(handleSocketDisconnected()));
+
+ _socket->connectToHost(host, port);
+}
+
+int WebProxyTrans::transactionId() const
+{
+ return _transactionId;
+}
+
+void WebProxyTrans::write(const QByteArray &data)
+{
+ if (!_outBuf.isEmpty() || !_socket->isValid()) {
+ _outBuf.append(data);
+ } else if (_socket->isValid()) {
+ // Write directly to socket
+ _socket->write(data);
+ }
+}
+
+void WebProxyTrans::handleSocketConnected()
+{
+ qDebug() << "Transaction" << _transactionId << "Connected";
+ if (_tunnel) {
+ emit dataReceived(connectResponse);
+ }
+ writeFromBuffer();
+}
+
+void WebProxyTrans::handleSocketDataWritten(qint64 written)
+{
+ Q_UNUSED(written);
+ writeFromBuffer();
+}
+
+void WebProxyTrans::handleSocketDataReady()
+{
+ emit dataReceived(_socket->readAll());
+}
+
+void WebProxyTrans::handleSocketDisconnected()
+{
+ qDebug() << "Transaction" << _transactionId << "Disconnected";
+ emit disconnected();
+}
+
+WebProxyConn* WebProxyTrans::conn()
+{
+ return static_cast<WebProxyConn*>(parent());
+}
+
+void WebProxyTrans::writeFromBuffer()
+{
+ if (!_outBuf.isEmpty()) {
+ qint64 written = _socket->write(_outBuf);
+ if (written > 0) {
+ _outBuf.remove(0, written);
+ }
+ }
+}
diff --git a/agents/webproxytrans.h b/agents/webproxytrans.h
new file mode 100644
index 0000000..950c7b9
--- /dev/null
+++ b/agents/webproxytrans.h
@@ -0,0 +1,42 @@
+#ifndef WEBPROXYTRANS_H
+#define WEBPROXYTRANS_H
+
+#include <QtCore/QIODevice>
+#include <QtNetwork/QTcpSocket>
+
+class WebProxyConn;
+
+class WebProxyTrans : public QObject
+{
+ Q_OBJECT
+public:
+ explicit WebProxyTrans(int transactionId, bool tunnel, const QString &host, int port, WebProxyConn *conn);
+
+ int transactionId() const;
+
+ void write(const QByteArray &data);
+
+signals:
+ void connected();
+ void dataReceived(const QByteArray &data);
+ void error();
+ void disconnected();
+
+private slots:
+ void handleSocketConnected();
+ void handleSocketDataWritten(qint64 written);
+ void handleSocketDataReady();
+ void handleSocketDisconnected();
+
+private:
+ WebProxyConn* conn();
+ void writeFromBuffer();
+
+private:
+ int _transactionId;
+ bool _tunnel;
+ QTcpSocket *_socket;
+ QByteArray _outBuf;
+};
+
+#endif // WEBPROXYTRANS_H