From a45977185a485624095bff1a15024e9199eee676 Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 1 Jan 2016 22:05:42 +0100 Subject: reorganize source files into SAP and agents --- agents/hostmanageragent.cc | 57 +++++++++++ agents/hostmanageragent.h | 27 +++++ agents/hostmanagerconn.cc | 233 ++++++++++++++++++++++++++++++++++++++++++++ agents/hostmanagerconn.h | 68 +++++++++++++ agents/musicagent.cc | 56 +++++++++++ agents/musicagent.h | 26 +++++ agents/musicconn.cc | 181 ++++++++++++++++++++++++++++++++++ agents/musicconn.h | 35 +++++++ agents/notificationagent.cc | 57 +++++++++++ agents/notificationagent.h | 27 +++++ agents/notificationconn.cc | 190 ++++++++++++++++++++++++++++++++++++ agents/notificationconn.h | 86 ++++++++++++++++ agents/webproxyagent.cc | 64 ++++++++++++ agents/webproxyagent.h | 27 +++++ agents/webproxyconn.cc | 187 +++++++++++++++++++++++++++++++++++ agents/webproxyconn.h | 63 ++++++++++++ agents/webproxytrans.cc | 74 ++++++++++++++ agents/webproxytrans.h | 42 ++++++++ 18 files changed, 1500 insertions(+) create mode 100644 agents/hostmanageragent.cc create mode 100644 agents/hostmanageragent.h create mode 100644 agents/hostmanagerconn.cc create mode 100644 agents/hostmanagerconn.h create mode 100644 agents/musicagent.cc create mode 100644 agents/musicagent.h create mode 100644 agents/musicconn.cc create mode 100644 agents/musicconn.h create mode 100644 agents/notificationagent.cc create mode 100644 agents/notificationagent.h create mode 100644 agents/notificationconn.cc create mode 100644 agents/notificationconn.h create mode 100644 agents/webproxyagent.cc create mode 100644 agents/webproxyagent.h create mode 100644 agents/webproxyconn.cc create mode 100644 agents/webproxyconn.h create mode 100644 agents/webproxytrans.cc create mode 100644 agents/webproxytrans.h (limited to 'agents') 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 +#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 +#include +#include +#include +#include +#include +#include + +#if SAILFISH +#include +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("" + "Actualizar el software del Gearcom.sec.android.fotaprovider2falsefalse" + "ConnectionManagercom.sec.android.service.connectionmanager1004falsefalse" + "goproviderscom.samsung.accessory.goproviders61falsefalse" + "SAFileTransferCorecom.samsung.accessory.safiletransfer1falsefalse" + "SANotiProvidercom.samsung.accessory.sanotiprovider1falsefalse" + "saproviderscom.samsung.accessory.saproviders64falsefalseTextTemplateProvidercom.samsung.accessory.texttemplateprovider1300falsefalse" + "com.samsung.accessory.saproviders64com.samsung.w-calendar2truecom.samsung.accessory.goproviders61com.samsung.wfmdtrue" + "com.sec.android.weatherprovidercom.samsung.weatherfalse" + "com.samsung.accessory.goproviders61com.samsung.w-contacts2truecom.samsung.accessory.saproviders64com.samsung.w-media-controllertrue" + "com.samsung.accessory.saproviders64com.samsung.alarmtruecom.samsung.accessory.saproviders64com.samsung.messagetrue" + "com.samsung.accessory.saproviders64com.samsung.w-logs2truecom.samsung.accessory.saproviders64com.samsung.idle-clock-eventtrue" + "com.sec.android.weatherprovidercom.samsung.w-idle-clock-weather2falsecom.samsung.accessory.saproviders64com.samsung.idle-clock-dualtrue" + "com.samsung.accessory.saproviders64com.samsung.svoice-wtrue" + "")); +#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 +#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 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 +#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 +#include +#include +#include +#include + +#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 +#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 +#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 +#include +#include + +#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(data, 0x10 | n.type); + append(data, n.sequenceNumber | (n.urgent ? 0x4000 : 0)); + + append(data, n.applicationId); + append(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(data, attributeCount); + + if (n.time.isValid()) { + append(data, (NotificationTime << 24) | sizeof(qint64)); + append(data, n.time.toMSecsSinceEpoch()); + } + if (!n.thumbnail.isEmpty()) { + append(data, (NotificationThumbnail << 24) | n.thumbnail.size()); + data.append(n.thumbnail); + } + if (!n.applicationName.isEmpty()) { + QByteArray str = n.applicationName.toUtf8(); + append(data, (NotificationApplicationName << 24) | str.length()); + data.append(str); + } + if (!n.packageName.isEmpty()) { + QByteArray str = n.packageName.toUtf8(); + append(data, (NotificationPackageName << 24) | str.length()); + data.append(str); + } + if (!n.title.isEmpty()) { + QByteArray str = n.title.toUtf8(); + append(data, (NotificationTitle << 24) | str.length()); + data.append(str); + } + if (!n.body.isEmpty()) { + QByteArray str = n.body.toUtf8(); + append(data, (NotificationBody << 24) | str.length()); + data.append(str); + } + if (n.count >= 0) { + append(data, (NotificationCount << 24) | sizeof(quint16)); + append(data, n.count); + } + if (n.sender >= 0) { + append(data, (NotificationSender << 24) | sizeof(quint16)); + append(data, n.sender); + } + if (n.notificationId != -1) { + append(data, (NotificationId << 24) | sizeof(quint32)); + append(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 +#include +#include +#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 _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 +#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 +#include + +#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(data, offset); + msg.subCommand = read(data, offset); + msg.type = static_cast(read(data, offset)); + msg.transactionId = read(data, offset); + + const quint32 len = read(data, offset); + msg.payload = data.mid(offset, len); + + return msg; +} + +QByteArray WebProxyConn::packMessage(const Message &msg) +{ + QByteArray data; + append(data, msg.command); + append(data, msg.subCommand); + append(data, msg.type); + append(data, msg.transactionId); + append(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(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(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 +#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 _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 +#include + +#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(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 +#include + +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 -- cgit v1.2.3