From 49f1261bf9d635d5e3d881e87a93ed4e76abfe90 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 17:27:08 +0100 Subject: allow receiving responses to commands in watchconnector * the skeleton is in place for watchconnector to allow query->response messages. I've used call/cc style because it is impossible to make QBluetoothSocket synchronous (waitForReadyRead() is a no-op) * remove watchcommands, instead create musicmanager to listen for the music endpoint. The other (simpler) endpoints are now listened in watchconnector itself. hangupAll() slot is moved to voicecallmanager. * instead of emitting signals for each received message, listeners can now register for receiving messages targeted towards a given endpoint * when reading from bluetoothsocket, properly handle short reads * remove useless 'watch' namespace * create appmanager, which mantains a database of installed apps (installed on the phone, that is; watch installed apps will come later) * all the *Managers are now instantiated by the main Manager itself * introduce Unpacker helper class for decoding watch messages * implement getAppbankStatus and getAppbankUuids messages and response parsers * remove file logging for now (20MB is bad for eMMC!) * use dbus object path /org/pebbled instead of / --- daemon/watchconnector.cpp | 211 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 174 insertions(+), 37 deletions(-) (limited to 'daemon/watchconnector.cpp') diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index a240b04..61eeb67 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -1,11 +1,12 @@ -#include "watchconnector.h" -#include #include #include -using namespace watch; +#include "watchconnector.h" +#include "unpacker.h" + +static const int RECONNECT_TIMEOUT = 500; //ms -static int RECONNECT_TIMEOUT = 500; //ms +using std::function; WatchConnector::WatchConnector(QObject *parent) : QObject(parent), socket(nullptr), is_connected(false) @@ -83,49 +84,95 @@ void WatchConnector::handleWatch(const QString &name, const QString &address) QString WatchConnector::decodeEndpoint(uint val) { - QMetaEnum Endpoints = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Endpoints")); + QMetaEnum Endpoints = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Endpoint")); const char *endpoint = Endpoints.valueToKey(val); return endpoint ? QString(endpoint) : QString("watchUNKNOWN_%1").arg(val); } -void WatchConnector::decodeMsg(QByteArray data) +void WatchConnector::setEndpointHandler(uint endpoint, EndpointHandlerFunc func) { - //Sometimes pebble sends a "00", we ignore it without future action - if (data.length() == 1 && data.at(0) == 0) { - return; - } - - if (data.length() < 4) { - logger()->error() << "Can not decode message data length invalid: " << data.toHex(); - return; + if (func) { + handlers.insert(endpoint, func); + } else { + handlers.remove(endpoint); } +} - unsigned int datalen = 0; - int index = 0; - datalen = (data.at(index) << 8) + data.at(index+1); - index += 2; +void WatchConnector::clearEndpointHandler(uint endpoint) +{ + handlers.remove(endpoint); +} - unsigned int endpoint = 0; - endpoint = (data.at(index) << 8) + data.at(index+1); - index += 2; +bool WatchConnector::dispatchMessage(uint endpoint, const QByteArray &data) +{ + auto tmp_it = tmpHandlers.find(endpoint); + if (tmp_it != tmpHandlers.end()) { + QList& funcs = tmp_it.value(); + bool ok = false; + if (!funcs.empty()) { + if (funcs.first()(data)) { + ok = true; + funcs.removeFirst(); + } + } + if (funcs.empty()) { + tmpHandlers.erase(tmp_it); + } + if (ok) { + return true; + } + } - logger()->debug() << "Length:" << datalen << "Endpoint:" << decodeEndpoint(endpoint); - logger()->debug() << "Data:" << data.mid(index).toHex(); + auto it = handlers.find(endpoint); + if (it != handlers.end()) { + if (it.value() && it.value()(data)) { + return true; + } + } - emit messageDecoded(endpoint, data.mid(index, datalen)); + logger()->info() << "message to endpoint" << decodeEndpoint(endpoint) << "was not dispatched"; + emit messageReceived(endpoint, data); + return false; } void WatchConnector::onReadSocket() { - logger()->debug() << "read"; + static const int header_length = 4; + + logger()->debug() << "readyRead bytesAvailable =" << socket->bytesAvailable(); QBluetoothSocket *socket = qobject_cast(sender()); - if (!socket) return; + Q_ASSERT(socket && socket == this->socket); + + while (socket->bytesAvailable() >= header_length) { + // Do nothing if there is no message to read. + if (socket->bytesAvailable() < header_length) { + if (socket->bytesAvailable() > 0) { + logger()->debug() << "incomplete header in read buffer"; + } + return; + } + + uchar header[header_length]; + socket->peek(reinterpret_cast(header), header_length); + + quint16 message_length, endpoint; + message_length = qFromBigEndian(&header[0]); + endpoint = qFromBigEndian(&header[sizeof(quint16)]); + + // Now wait for the entire message + if (socket->bytesAvailable() < header_length + message_length) { + logger()->debug() << "incomplete msg body in read buffer"; + return; + } + + socket->read(header_length); // Skip the header - while (socket->bytesAvailable()) { - QByteArray line = socket->readAll(); - emit messageReceived(socket->peerName(), QString::fromUtf8(line.constData(), line.length())); - decodeMsg(line); + QByteArray data = socket->read(message_length); + + logger()->debug() << "received message of length" << message_length << "to endpoint" << decodeEndpoint(endpoint); + + dispatchMessage(endpoint, data); } } @@ -181,13 +228,11 @@ void WatchConnector::onError(QBluetoothSocket::SocketError error) void WatchConnector::sendData(const QByteArray &data) { - writeData = data; + writeData.append(data); if (socket == nullptr) { logger()->debug() << "No socket - reconnecting"; reconnect(); - return; - } - if (is_connected) { + } else if (is_connected) { logger()->debug() << "Writing" << data.length() << "bytes to socket"; socket->write(data); } @@ -195,13 +240,13 @@ void WatchConnector::sendData(const QByteArray &data) void WatchConnector::onBytesWritten(qint64 bytes) { - writeData = writeData.mid(bytes); + writeData.remove(0, bytes); logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left"; } -void WatchConnector::sendMessage(uint endpoint, QByteArray data) +void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) { - logger()->debug() << "Sending message"; + logger()->debug() << "sending message to endpoint" << decodeEndpoint(endpoint); QByteArray msg; // First send the length @@ -390,3 +435,95 @@ void WatchConnector::endPhoneCall(uint cookie) { phoneControl(callEND, cookie, QStringList()); } + +void WatchConnector::getAppbankStatus(const std::function& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS)); + + tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + if (data.at(0) != appmgrGET_APPBANK_STATUS) { + return false; + } + logger()->debug() << "getAppbankStatus response" << data.toHex(); + + if (data.size() < 9) { + logger()->warn() << "invalid getAppbankStatus response"; + return true; + } + + Unpacker u(data); + + u.skip(sizeof(quint8)); + + unsigned int num_banks = u.read(); + unsigned int apps_installed = u.read(); + + logger()->debug() << num_banks << "/" << apps_installed; + + for (unsigned int i = 0; i < apps_installed; i++) { + unsigned int id = u.read(); + unsigned int index = u.read(); + QString name = u.readFixedString(32); + QString company = u.readFixedString(32); + unsigned int flags = u.read(); + unsigned short version = u.read(); + + logger()->debug() << id << index << name << company << flags << version; + + if (u.bad()) { + logger()->warn() << "short read"; + return true; + } + } + + logger()->debug() << "finished"; + + return true; + }); +} + +void WatchConnector::getAppbankUuids(const function &)>& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS)); + + tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + if (data.at(0) != appmgrGET_APPBANK_UUIDS) { + return false; + } + logger()->debug() << "getAppbankUuids response" << data.toHex(); + + if (data.size() < 5) { + logger()->warn() << "invalid getAppbankUuids response"; + return true; + } + + Unpacker u(data); + + u.skip(sizeof(quint8)); + + unsigned int apps_installed = u.read(); + + logger()->debug() << apps_installed; + + QList uuids; + + for (unsigned int i = 0; i < apps_installed; i++) { + QUuid uuid = u.readUuid(); + + logger()->debug() << uuid.toString(); + + if (u.bad()) { + logger()->warn() << "short read"; + return true; + } + + uuids.push_back(uuid); + } + + logger()->debug() << "finished"; + + callback(uuids); + + return true; + }); +} -- cgit v1.2.3 From 4527ab9a4147a8f15bf8ca5613341df9d0029d0c Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 18:49:07 +0100 Subject: add stub datalogmanager --- daemon/daemon.pro | 6 ++++-- daemon/datalogmanager.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ daemon/datalogmanager.h | 25 +++++++++++++++++++++++++ daemon/manager.cpp | 9 +++++---- daemon/manager.h | 2 ++ daemon/watchconnector.cpp | 7 +++++++ daemon/watchconnector.h | 13 ++++++++++++- 7 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 daemon/datalogmanager.cpp create mode 100644 daemon/datalogmanager.h (limited to 'daemon/watchconnector.cpp') diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 768b50e..d4d7dbf 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -22,7 +22,8 @@ SOURCES += \ dbusconnector.cpp \ dbusadaptor.cpp \ appmanager.cpp \ - musicmanager.cpp + musicmanager.cpp \ + datalogmanager.cpp HEADERS += \ manager.h \ @@ -35,7 +36,8 @@ HEADERS += \ settings.h \ appmanager.h \ musicmanager.h \ - unpacker.h + unpacker.h \ + datalogmanager.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/datalogmanager.cpp b/daemon/datalogmanager.cpp new file mode 100644 index 0000000..8026e15 --- /dev/null +++ b/daemon/datalogmanager.cpp @@ -0,0 +1,42 @@ +#include "datalogmanager.h" +#include "unpacker.h" + +DataLogManager::DataLogManager(WatchConnector *watch, QObject *parent) : + QObject(parent), watch(watch) +{ + watch->setEndpointHandler(WatchConnector::watchDATA_LOGGING, [this](const QByteArray& data) { + if (data.size() < 2) { + logger()->warn() << "small data_logging packet"; + return false; + } + + const char command = data[0]; + const int session = data[1]; + + switch (command) { + case WatchConnector::datalogOPEN: + logger()->debug() << "open datalog session" << session; + return true; + case WatchConnector::datalogCLOSE: + logger()->debug() << "close datalog session" << session; + return true; + case WatchConnector::datalogTIMEOUT: + logger()->debug() << "timeout datalog session" << session; + return true; + case WatchConnector::datalogDATA: + handleDataCommand(session, data.mid(2)); + return true; + default: + return false; + } + }); +} + +void DataLogManager::handleDataCommand(int session, const QByteArray &data) +{ + Unpacker u(data); + + // TODO Seemingly related to analytics, so not important. + + logger()->debug() << "got datalog data" << session << data.size(); +} diff --git a/daemon/datalogmanager.h b/daemon/datalogmanager.h new file mode 100644 index 0000000..47fc948 --- /dev/null +++ b/daemon/datalogmanager.h @@ -0,0 +1,25 @@ +#ifndef DATALOGMANAGER_H +#define DATALOGMANAGER_H + +#include "watchconnector.h" + +class DataLogManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit DataLogManager(WatchConnector *watch, QObject *parent = 0); + +signals: + +public slots: + +private: + void handleDataCommand(int session, const QByteArray &data); + +private: + WatchConnector *watch; +}; + +#endif // DATALOGMANAGER_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 8a8acf4..7a02c86 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -12,6 +12,7 @@ Manager::Manager(Settings *settings, QObject *parent) : voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), + datalog(new DataLogManager(watch, this)), apps(new AppManager(this)), notification(MNotification::DeviceEvent) { @@ -35,15 +36,15 @@ Manager::Manager(Settings *settings, QObject *parent) : return true; }); watch->setEndpointHandler(WatchConnector::watchPHONE_CONTROL, - [this](const QByteArray& data) { + [this](const QByteArray &data) { if (data.at(0) == WatchConnector::callHANGUP) { voice->hangupAll(); } return true; }); - watch->setEndpointHandler(WatchConnector::watchDATA_LOGGING, - [this](const QByteArray& data) { - //logger()->debug() << data.toHex(); + watch->setEndpointHandler(WatchConnector::watchLAUNCHER, + [this](const QByteArray &data) { + logger()->debug() << "LAUNCHER msg:" << data.toHex(); return true; }); diff --git a/daemon/manager.h b/daemon/manager.h index b210700..8b2fd96 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -6,6 +6,7 @@ #include "voicecallmanager.h" #include "notificationmanager.h" #include "musicmanager.h" +#include "datalogmanager.h" #include "appmanager.h" #include "settings.h" @@ -42,6 +43,7 @@ class Manager : VoiceCallManager *voice; NotificationManager *notifications; MusicManager *music; + DataLogManager *datalog; AppManager *apps; MNotification notification; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 61eeb67..a3ea181 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -160,6 +160,13 @@ void WatchConnector::onReadSocket() message_length = qFromBigEndian(&header[0]); endpoint = qFromBigEndian(&header[sizeof(quint16)]); + if (message_length > 8 * 1024) { + // Protocol does not allow messages more than 8K long, seemingly. + logger()->warn() << "received message size too long: " << message_length; + socket->readAll(); // drop input buffer + return; + } + // Now wait for the entire message if (socket->bytesAvailable() < header_length + message_length) { logger()->debug() << "incomplete msg body in read buffer"; diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 01c3dac..c5ec332 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -109,7 +109,18 @@ public: appmgrGET_APPBANK_STATUS = 1, appmgrGET_APPBANK_UUIDS = 5 }; - + enum AppMessage { + appmsgPUSH = 1, + appmsgREQUEST = 2, + appmsgACK = 0xFF, + appmsgNACK = 0x7F + }; + enum DataLogMessage { + datalogOPEN = 1, + datalogDATA = 2, + datalogCLOSE = 3, + datalogTIMEOUT = 7 + }; enum { leadEMAIL = 0, leadSMS = 1, -- cgit v1.2.3 From 1e3794c476caf5c41360c36cc13c8425ec0dd26c Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 1 Dec 2014 02:21:30 +0100 Subject: implement message passing around jskit apps and watch --- daemon/appinfo.cpp | 26 +++++-- daemon/appinfo.h | 8 ++- daemon/appmsgmanager.cpp | 171 +++++++++++++++++++++++++++++++++++++++++++--- daemon/appmsgmanager.h | 17 ++++- daemon/daemon.pro | 6 +- daemon/jskitmanager.cpp | 25 ++++++- daemon/jskitmanager.h | 5 +- daemon/jskitobjects.cpp | 35 ++++++++-- daemon/jskitobjects.h | 14 +++- daemon/manager.cpp | 2 +- daemon/packer.cpp | 91 ++++++++++++++++++++++++ daemon/packer.h | 67 ++++++++++++++++++ daemon/unpacker.cpp | 2 +- daemon/watchconnector.cpp | 19 +++--- daemon/watchconnector.h | 4 +- 15 files changed, 448 insertions(+), 44 deletions(-) create mode 100644 daemon/packer.cpp create mode 100644 daemon/packer.h (limited to 'daemon/watchconnector.cpp') diff --git a/daemon/appinfo.cpp b/daemon/appinfo.cpp index a4442a3..e2406b8 100644 --- a/daemon/appinfo.cpp +++ b/daemon/appinfo.cpp @@ -10,7 +10,8 @@ struct AppInfoData : public QSharedData { QString versionLabel; bool watchface; bool jskit; - QHash appKeys; + QHash keyInts; + QHash keyNames; QString path; }; @@ -116,19 +117,30 @@ void AppInfo::setJSKit(bool b) d->jskit = b; } -QHash AppInfo::appKeys() const +void AppInfo::addAppKey(const QString &key, int value) { - return d->appKeys; + d->keyInts.insert(key, value); + d->keyNames.insert(value, key); } -void AppInfo::setAppKeys(const QHash &appKeys) +bool AppInfo::hasAppKeyValue(int value) const { - d->appKeys = appKeys; + return d->keyNames.contains(value); } -void AppInfo::addAppKey(const QString &key, int value) +QString AppInfo::appKeyForValue(int value) const +{ + return d->keyNames.value(value); +} + +bool AppInfo::hasAppKey(const QString &key) const +{ + return d->keyInts.contains(key); +} + +int AppInfo::valueForAppKey(const QString &key) const { - d->appKeys.insert(key, value); + return d->keyInts.value(key, -1); } QString AppInfo::path() const diff --git a/daemon/appinfo.h b/daemon/appinfo.h index da71dfc..038a708 100644 --- a/daemon/appinfo.h +++ b/daemon/appinfo.h @@ -52,10 +52,14 @@ public: bool isJSKit() const; void setJSKit(bool b); - QHash appKeys() const; - void setAppKeys(const QHash &string); void addAppKey(const QString &key, int value); + bool hasAppKeyValue(int value) const; + QString appKeyForValue(int value) const; + + bool hasAppKey(const QString &key) const; + int valueForAppKey(const QString &key) const; + QString path() const; void setPath(const QString &string); diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 23bf802..b620078 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -1,21 +1,28 @@ #include "appmsgmanager.h" #include "unpacker.h" +#include "packer.h" // TODO D-Bus server for non JS kit apps!!!! -AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) - : QObject(parent), watch(watch) +AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent) + : QObject(parent), apps(apps), watch(watch), lastTransactionId(0) { watch->setEndpointHandler(WatchConnector::watchLAUNCHER, [this](const QByteArray &data) { if (data.at(0) == WatchConnector::appmsgPUSH) { - Unpacker u(data); - u.skip(1); // skip data.at(0) which we just already checked above. - uint transaction = u.read(); - QUuid uuid = u.readUuid(); - WatchConnector::Dict dict = u.readDict(); - if (u.bad() || !dict.contains(1)) { - logger()->warn() << "Failed to parse LAUNCHER message"; + uint transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + // Failed to parse! + // Since we're the only one handling this endpoint, + // all messages must be accepted + logger()->warn() << "Failed to parser LAUNCHER PUSH message"; + return true; + } + if (!dict.contains(1)) { + logger()->warn() << "LAUNCHER message has no item in dict"; return true; } @@ -46,7 +53,26 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE, [this](const QByteArray &data) { switch (data.at(0)) { - case WatchConnector::appmsgPUSH: + case WatchConnector::appmsgPUSH: { + uint transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + logger()->warn() << "Failed to parse APP_MSG PUSH"; + return true; + } + + logger()->debug() << "Received appmsg PUSH from" << uuid << "with" << dict; + + QVariantMap data = mapAppKeys(uuid, dict); + logger()->debug() << "Mapped dict" << data; + + emit messageReceived(uuid, data); + break; + } + default: + logger()->warn() << "Unknown application message type:" << data.at(0); break; } @@ -54,9 +80,132 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) }); } +void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std::function &ackCallback, const std::function &nackCallback) +{ + WatchConnector::Dict dict = mapAppKeys(uuid, data); + quint8 transaction = ++lastTransactionId; + QByteArray msg = buildPushMessage(transaction, uuid, dict); + + logger()->debug() << "Sending appmsg" << transaction << "to" << uuid << "with" << dict; + + WatchConnector::Dict t_dict; + QUuid t_uuid; + uint t_trans; + if (unpackPushMessage(msg, &t_trans, &t_uuid, &t_dict)) { + logger()->debug() << t_trans << t_uuid << t_dict; + } else { + logger()->warn() << "not unpack my own"; + } + + + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg, + [this, ackCallback, nackCallback, transaction](const QByteArray &reply) { + if (reply.size() < 2) return false; + + quint8 type = reply[0]; + quint8 recv_transaction = reply[1]; + + logger()->debug() << "Got response to transaction" << transaction; + + if (recv_transaction != transaction) return false; + + switch (type) { + case WatchConnector::appmsgACK: + logger()->debug() << "Got ACK to transaction" << transaction; + if (ackCallback) ackCallback(); + return true; + case WatchConnector::appmsgNACK: + logger()->info() << "Got NACK to transaction" << transaction; + if (nackCallback) nackCallback(); + return true; + default: + return false; + } + }); +} + void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) { - // TODO + std::function nullCallback; + send(uuid, data, nullCallback, nullCallback); +} + +WatchConnector::Dict AppMsgManager::mapAppKeys(const QUuid &uuid, const QVariantMap &data) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "Unknown app GUID while sending message:" << uuid; + } + + WatchConnector::Dict d; + + for (QVariantMap::const_iterator it = data.constBegin(); it != data.constEnd(); ++it) { + if (info.hasAppKey(it.key())) { + d.insert(info.valueForAppKey(it.key()), it.value()); + } else { + // Even if we do not know about this appkey, try to see if it's already a numeric key we + // can send to the watch. + bool ok = false; + int num = it.key().toInt(&ok); + if (ok) { + d.insert(num, it.value()); + } else { + logger()->warn() << "Unknown appKey" << it.key() << "for app with GUID" << uuid; + } + } + } + + return d; +} + +QVariantMap AppMsgManager::mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "Unknown app GUID while sending message:" << uuid; + } + + QVariantMap data; + + for (WatchConnector::Dict::const_iterator it = dict.constBegin(); it != dict.constEnd(); ++it) { + if (info.hasAppKeyValue(it.key())) { + data.insert(info.appKeyForValue(it.key()), it.value()); + } else { + logger()->warn() << "Unknown appKey value" << it.key() << "for app with GUID" << uuid; + data.insert(QString::number(it.key()), it.value()); + } + } + + return data; +} + +bool AppMsgManager::unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict) +{ + Unpacker u(msg); + quint8 code = u.read(); + Q_ASSERT(code == WatchConnector::appmsgPUSH); + + *transaction = u.read(); + *uuid = u.readUuid(); + *dict = u.readDict(); + + if (u.bad()) { + return false; + } + + return true; +} + +QByteArray AppMsgManager::buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict) +{ + QByteArray ba; + Packer p(&ba); + p.write(WatchConnector::appmsgPUSH); + p.write(transaction); + p.writeUuid(uuid); + p.writeDict(dict); + + return ba; } QByteArray AppMsgManager::buildAckMessage(uint transaction) diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index 651d84e..ca1d484 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -2,6 +2,7 @@ #define APPMSGMANAGER_H #include "watchconnector.h" +#include "appmanager.h" class AppMsgManager : public QObject { @@ -9,7 +10,11 @@ class AppMsgManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit AppMsgManager(WatchConnector *watch, QObject *parent); + explicit AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent); + + void send(const QUuid &uuid, const QVariantMap &data, + const std::function &ackCallback, + const std::function &nackCallback); public slots: void send(const QUuid &uuid, const QVariantMap &data); @@ -17,14 +22,22 @@ public slots: signals: void appStarted(const QUuid &uuid); void appStopped(const QUuid &uuid); - void dataReceived(const QUuid &uuid, const QVariantMap &data); + void messageReceived(const QUuid &uuid, const QVariantMap &data); private: + WatchConnector::Dict mapAppKeys(const QUuid &uuid, const QVariantMap &data); + QVariantMap mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict); + + static bool unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict); + + static QByteArray buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict); static QByteArray buildAckMessage(uint transaction); static QByteArray buildNackMessage(uint transaction); private: + AppManager *apps; WatchConnector *watch; + quint8 lastTransactionId; }; #endif // APPMSGMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index a6631df..c59d408 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -28,7 +28,8 @@ SOURCES += \ appmsgmanager.cpp \ jskitmanager.cpp \ appinfo.cpp \ - jskitobjects.cpp + jskitobjects.cpp \ + packer.cpp HEADERS += \ manager.h \ @@ -46,7 +47,8 @@ HEADERS += \ appmsgmanager.h \ jskitmanager.h \ appinfo.h \ - jskitobjects.h + jskitobjects.h \ + packer.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 2da6c09..f8ec34a 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -9,6 +9,7 @@ JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *par { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); + connect(_appmsg, &AppMsgManager::messageReceived, this, &JSKitManager::handleAppMessage); } JSKitManager::~JSKitManager() @@ -40,6 +41,23 @@ void JSKitManager::handleAppStopped(const QUuid &uuid) } } +void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &data) +{ + if (_curApp.uuid() == uuid) { + logger()->debug() << "received a message for the current JSKit app"; + + if (!_engine) { + logger()->debug() << "but engine is stopped"; + return; + } + + QJSValue eventObj = _engine->newObject(); + eventObj.setProperty("payload", _engine->toScriptValue(data)); + + _jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj})); + } +} + void JSKitManager::startJsApp() { if (_engine) stopJsApp(); @@ -49,7 +67,8 @@ void JSKitManager::startJsApp() } _engine = new QJSEngine(this); - _jspebble = new JSKitPebble(this); + _jspebble = new JSKitPebble(_curApp, this); + _jsconsole = new JSKitConsole(this); _jsstorage = new JSKitLocalStorage(_curApp.uuid(), this); logger()->debug() << "starting JS app"; @@ -57,8 +76,12 @@ void JSKitManager::startJsApp() QJSValue globalObj = _engine->globalObject(); globalObj.setProperty("Pebble", _engine->newQObject(_jspebble)); + globalObj.setProperty("console", _engine->newQObject(_jsconsole)); globalObj.setProperty("localStorage", _engine->newQObject(_jsstorage)); + QJSValue windowObj = _engine->newObject(); + windowObj.setProperty("localStorage", globalObj.property("localStorage")); + globalObj.setProperty("window", windowObj); QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index f25a96f..d09bf54 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -6,6 +6,7 @@ #include "appmsgmanager.h" class JSKitPebble; +class JSKitConsole; class JSKitLocalStorage; class JSKitManager : public QObject @@ -18,12 +19,14 @@ public: ~JSKitManager(); signals: + void appNotification(const QUuid &uuid, const QString &title, const QString &body); public slots: private slots: void handleAppStarted(const QUuid &uuid); void handleAppStopped(const QUuid &uuid); + void handleAppMessage(const QUuid &uuid, const QVariantMap &data); private: void startJsApp(); @@ -37,8 +40,8 @@ private: AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; + QPointer _jsconsole; QPointer _jsstorage; - }; #endif // JSKITMANAGER_H diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index f40138a..aecc55d 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -4,8 +4,8 @@ #include #include "jskitobjects.h" -JSKitPebble::JSKitPebble(JSKitManager *mgr) - : QObject(mgr), _mgr(mgr) +JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) + : QObject(mgr), _appInfo(info), _mgr(mgr) { } @@ -34,17 +34,28 @@ void JSKitPebble::removeEventListener(const QString &type, QJSValue function) void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) { - // TODO contact _mgr->appmsg->... - logger()->debug() << "sendAppMessage" << message.toString(); + QVariantMap data = message.toVariant().toMap(); + + logger()->debug() << "sendAppMessage" << data; + + _mgr->_appmsg->send(_appInfo.uuid(), data, [this, callbackForAck]() mutable { + logger()->debug() << "Invoking ack callback"; + callbackForAck.call(); + }, [this, callbackForNack]() mutable { + logger()->debug() << "Invoking nack callback"; + callbackForNack.call(); + }); } void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body) { logger()->debug() << "showSimpleNotificationOnPebble" << title << body; + emit _mgr->appNotification(_appInfo.uuid(), title, body); } void JSKitPebble::openUrl(const QUrl &url) { + logger()->debug() << "opening url" << url.toString(); if (!QDesktopServices::openUrl(url)) { logger()->warn() << "Failed to open URL:" << url; } @@ -56,6 +67,7 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) QList &callbacks = _callbacks[type]; for (QList::iterator it = callbacks.begin(); it != callbacks.end(); ++it) { + logger()->debug() << "invoking callback" << type << it->toString(); QJSValue result = it->call(args); if (result.isError()) { logger()->warn() << "error while invoking callback" << type << it->toString() << ":" @@ -64,6 +76,16 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) } } +JSKitConsole::JSKitConsole(JSKitManager *mgr) + : QObject(mgr) +{ +} + +void JSKitConsole::log(const QString &msg) +{ + logger()->info() << msg; +} + JSKitLocalStorage::JSKitLocalStorage(const QUuid &uuid, JSKitManager *mgr) : QObject(mgr), _storage(new QSettings(getStorageFileFor(uuid), QSettings::IniFormat, this)) { @@ -117,5 +139,8 @@ QString JSKitLocalStorage::getStorageFileFor(const QUuid &uuid) { QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); dataDir.mkdir("js-storage"); - return dataDir.absoluteFilePath("js-storage/" + uuid.toString() + ".ini"); + QString fileName = uuid.toString(); + fileName.remove('{'); + fileName.remove('}'); + return dataDir.absoluteFilePath("js-storage/" + fileName + ".ini"); } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 2375084..c59bfac 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -10,7 +10,7 @@ class JSKitPebble : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit JSKitPebble(JSKitManager *mgr); + explicit JSKitPebble(const AppInfo &appInfo, JSKitManager *mgr); Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); @@ -24,10 +24,22 @@ public: void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); private: + AppInfo _appInfo; JSKitManager *_mgr; QHash> _callbacks; }; +class JSKitConsole : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit JSKitConsole(JSKitManager *mgr); + + Q_INVOKABLE void log(const QString &msg); +}; + class JSKitLocalStorage : public QObject { Q_OBJECT diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 778fdc6..8d41c89 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -14,7 +14,7 @@ Manager::Manager(Settings *settings, QObject *parent) : notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), - appmsg(new AppMsgManager(watch, this)), + appmsg(new AppMsgManager(apps, watch, this)), js(new JSKitManager(apps, appmsg, this)), notification(MNotification::DeviceEvent) { diff --git a/daemon/packer.cpp b/daemon/packer.cpp new file mode 100644 index 0000000..569f7a8 --- /dev/null +++ b/daemon/packer.cpp @@ -0,0 +1,91 @@ +#include "packer.h" +#include "watchconnector.h" + +void Packer::writeBytes(int n, const QByteArray &b) +{ + if (b.size() > n) { + _buf->append(b.constData(), n); + } else { + int diff = n - b.size(); + _buf->append(b); + if (diff > 0) { + _buf->append(QByteArray(diff, '\0')); + } + } +} + +void Packer::writeUuid(const QUuid &uuid) +{ + writeBytes(16, uuid.toRfc4122()); +} + +void Packer::writeDict(const QMap &d) +{ + int size = d.size(); + if (size > 0xFF) { + logger()->warn() << "Dictionary is too large to encode"; + writeLE(0); + return; + } + + writeLE(size); + + for (QMap::const_iterator it = d.constBegin(); it != d.constEnd(); ++it) { + writeLE(it.key()); + + switch (int(it.value().type())) { + case QMetaType::Char: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(char)); + writeLE(it.value().value()); + break; + case QMetaType::Short: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(short)); + writeLE(it.value().value()); + break; + case QMetaType::Int: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(int)); + writeLE(it.value().value()); + break; + + case QMetaType::UChar: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(char)); + writeLE(it.value().value()); + break; + case QMetaType::UShort: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(short)); + writeLE(it.value().value()); + break; + case QMetaType::UInt: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(int)); + writeLE(it.value().value()); + break; + + case QMetaType::QByteArray: { + QByteArray ba = it.value().toByteArray(); + writeLE(WatchConnector::typeBYTES); + writeLE(ba.size()); + _buf->append(ba); + break; + } + + default: + logger()->warn() << "Unknown dict item type:" << it.value().typeName(); + /* Fallthrough */ + case QMetaType::QString: + case QMetaType::QUrl: + { + QByteArray s = it.value().toString().toUtf8(); + writeLE(WatchConnector::typeSTRING); + writeLE(s.size()); + _buf->append(s); + break; + } + } + } +} diff --git a/daemon/packer.h b/daemon/packer.h new file mode 100644 index 0000000..d22072c --- /dev/null +++ b/daemon/packer.h @@ -0,0 +1,67 @@ +#ifndef PACKER_H +#define PACKER_H + +#include +#include +#include +#include +#include +#include + +class Packer +{ + LOG4QT_DECLARE_STATIC_LOGGER(logger, Packer) + +public: + Packer(QByteArray *buf); + + template + void write(T v); + + template + void writeLE(T v); + + void writeBytes(int n, const QByteArray &b); + + void writeFixedString(int n, const QString &s); + + void writeUuid(const QUuid &uuid); + + void writeDict(const QMap &d); + +private: + char *p(int n); + uchar *up(int n); + QByteArray *_buf; +}; + +inline Packer::Packer(QByteArray *buf) + : _buf(buf) +{ +} + +template +void Packer::write(T v) +{ + qToBigEndian(v, up(sizeof(T))); +} + +template +void Packer::writeLE(T v) +{ + qToLittleEndian(v, up(sizeof(T))); +} + +inline char * Packer::p(int n) +{ + int size = _buf->size(); + _buf->resize(size + n); + return &_buf->data()[size]; +} + +inline uchar * Packer::up(int n) +{ + return reinterpret_cast(p(n)); +} + +#endif // PACKER_H diff --git a/daemon/unpacker.cpp b/daemon/unpacker.cpp index fc38020..e904db8 100644 --- a/daemon/unpacker.cpp +++ b/daemon/unpacker.cpp @@ -29,7 +29,7 @@ QMap Unpacker::readDict() QMap d; if (checkBad(1)) return d; - const int n = read(); + const int n = readLE(); for (int i = 0; i < n; i++) { if (checkBad(4 + 1 + 2)) return d; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index a3ea181..dd95821 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -89,7 +89,7 @@ QString WatchConnector::decodeEndpoint(uint val) return endpoint ? QString(endpoint) : QString("watchUNKNOWN_%1").arg(val); } -void WatchConnector::setEndpointHandler(uint endpoint, EndpointHandlerFunc func) +void WatchConnector::setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func) { if (func) { handlers.insert(endpoint, func); @@ -241,6 +241,7 @@ void WatchConnector::sendData(const QByteArray &data) reconnect(); } else if (is_connected) { logger()->debug() << "Writing" << data.length() << "bytes to socket"; + logger()->debug() << data.toHex(); socket->write(data); } } @@ -251,7 +252,7 @@ void WatchConnector::onBytesWritten(qint64 bytes) logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left"; } -void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) +void WatchConnector::sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback) { logger()->debug() << "sending message to endpoint" << decodeEndpoint(endpoint); QByteArray msg; @@ -268,6 +269,10 @@ void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) msg.append(data); sendData(msg); + + if (callback) { + tmpHandlers[endpoint].append(callback); + } } void WatchConnector::buildData(QByteArray &res, QStringList data) @@ -445,9 +450,8 @@ void WatchConnector::endPhoneCall(uint cookie) void WatchConnector::getAppbankStatus(const std::function& callback) { - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS)); - - tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), + [this, callback](const QByteArray &data) { if (data.at(0) != appmgrGET_APPBANK_STATUS) { return false; } @@ -491,9 +495,8 @@ void WatchConnector::getAppbankStatus(const std::function &)>& callback) { - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS)); - - tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), + [this, callback](const QByteArray &data) { if (data.at(0) != appmgrGET_APPBANK_UUIDS) { return false; } diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 8a7d574..6c7fdd4 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -173,7 +173,7 @@ public: inline bool isConnected() const { return is_connected; } inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } - void setEndpointHandler(uint endpoint, EndpointHandlerFunc func); + void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); void clearEndpointHandler(uint endpoint); static QString timeStamp(); @@ -192,7 +192,7 @@ public slots: void disconnect(); void reconnect(); - void sendMessage(uint endpoint, const QByteArray &data); + void sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback = EndpointHandlerFunc()); void ping(uint val); void time(); -- cgit v1.2.3 From 8d98f990c9ed158d8c65befc154ab58a3c392646 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 01:47:48 +0100 Subject: fix some appmsg issues --- daemon/appmsgmanager.cpp | 15 ++++++++++----- daemon/watchconnector.cpp | 10 +++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) (limited to 'daemon/watchconnector.cpp') diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index afaabee..12971ce 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -71,8 +71,12 @@ AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *p emit messageReceived(uuid, data); break; } + case WatchConnector::appmsgACK: + case WatchConnector::appmsgNACK: + logger()->info() << "appmsg endpoint handler received an unwanted ack/nack"; + break; default: - logger()->warn() << "Unknown application message type:" << data.at(0); + logger()->warn() << "Unknown application message type:" << int(data.at(0)); break; } @@ -88,15 +92,16 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: logger()->debug() << "Sending appmsg" << transaction << "to" << uuid << "with" << dict; +#if 0 /* Try to unpack what we just packed. */ WatchConnector::Dict t_dict; QUuid t_uuid; uint t_trans; if (unpackPushMessage(msg, &t_trans, &t_uuid, &t_dict)) { logger()->debug() << t_trans << t_uuid << t_dict; } else { - logger()->warn() << "not unpack my own"; + logger()->error() << "not able to unpack my own dict"; } - +#endif watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg, [this, ackCallback, nackCallback, transaction](const QByteArray &reply) { @@ -105,10 +110,10 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: quint8 type = reply[0]; quint8 recv_transaction = reply[1]; - logger()->debug() << "Got response to transaction" << transaction; - if (recv_transaction != transaction) return false; + logger()->debug() << "Got response to transaction" << transaction; + switch (type) { case WatchConnector::appmsgACK: logger()->debug() << "Got ACK to transaction" << transaction; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index dd95821..baec52c 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -109,13 +109,17 @@ bool WatchConnector::dispatchMessage(uint endpoint, const QByteArray &data) if (tmp_it != tmpHandlers.end()) { QList& funcs = tmp_it.value(); bool ok = false; - if (!funcs.empty()) { - if (funcs.first()(data)) { + for (int i = 0; i < funcs.size(); i++) { + if (funcs[i](data)) { + // This handler accepted this message ok = true; - funcs.removeFirst(); + // Since it is a temporary handler, remove it. + funcs.removeAt(i); + break; } } if (funcs.empty()) { + // "Garbage collect" the tmpHandlers entry. tmpHandlers.erase(tmp_it); } if (ok) { -- cgit v1.2.3 From 49c20eb7e2933ae6e9e4337fc0fe9b49a39efde8 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Dec 2014 01:31:51 +0100 Subject: add bankmanager and ability to unload apps --- daemon/bankmanager.cpp | 233 ++++++++++++++++++++++++++++++++++++++++++++++ daemon/bankmanager.h | 51 ++++++++++ daemon/daemon.pro | 6 +- daemon/manager.cpp | 12 +++ daemon/manager.h | 4 + daemon/watchconnector.cpp | 90 ------------------ daemon/watchconnector.h | 4 +- org.pebbled.Watch.xml | 5 + 8 files changed, 310 insertions(+), 95 deletions(-) create mode 100644 daemon/bankmanager.cpp create mode 100644 daemon/bankmanager.h (limited to 'daemon/watchconnector.cpp') diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp new file mode 100644 index 0000000..194ec77 --- /dev/null +++ b/daemon/bankmanager.cpp @@ -0,0 +1,233 @@ +#include "unpacker.h" +#include "packer.h" +#include "bankmanager.h" + +BankManager::BankManager(WatchConnector *watch, AppManager *apps, QObject *parent) : + QObject(parent), watch(watch), apps(apps) +{ + connect(watch, &WatchConnector::connectedChanged, this, &BankManager::handleWatchConnected); +} + +int BankManager::numSlots() const +{ + return _slots.size(); +} + +bool BankManager::uploadApp(const QUuid &uuid, int slot) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "uuid" << uuid << "is not installed"; + return false; + } + if (slot == -1) { + slot = findUnusedSlot(); + if (slot == -1) { + logger()->warn() << "no free slots!"; + return false; + } + } + if (slot < 0 || slot > _slots.size()) { + logger()->warn() << "invalid slot index"; + return false; + } + + // TODO + + return false; +} + +bool BankManager::unloadApp(int slot) +{ + if (slot < 0 || slot > _slots.size()) { + logger()->warn() << "invalid slot index"; + return false; + } + if (!_slots[slot].used) { + logger()->warn() << "slot is empty"; + return false; + } + + logger()->debug() << "going to unload app" << _slots[slot].name << "in slot" << slot; + + int installId = _slots[slot].id; + + QByteArray msg; + msg.reserve(2 * sizeof(quint32)); + Packer p(&msg); + p.write(WatchConnector::appmgrREMOVE_APP); + p.write(installId); + p.write(slot); + + watch->sendMessage(WatchConnector::watchAPP_MANAGER, msg, + [this](const QByteArray &data) { + Unpacker u(data); + if (u.read() != WatchConnector::appmgrREMOVE_APP) { + return false; + } + + uint result = u.read(); + switch (result) { + case 1: /* Success */ + logger()->debug() << "sucessfully unloaded app"; + break; + default: + logger()->warn() << "could not unload app. result code:" << result; + break; + } + + QMetaObject::invokeMethod(this, "refresh", Qt::QueuedConnection); + + return true; + }); + + return true; // Operation in progress +} + +void BankManager::refresh() +{ + logger()->debug() << "refreshing bank status"; + + watch->sendMessage(WatchConnector::watchAPP_MANAGER, + QByteArray(1, WatchConnector::appmgrGET_APPBANK_STATUS), + [this](const QByteArray &data) { + if (data.at(0) != WatchConnector::appmgrGET_APPBANK_STATUS) { + return false; + } + + if (data.size() < 9) { + logger()->warn() << "invalid getAppbankStatus response"; + return true; + } + + Unpacker u(data); + + u.skip(sizeof(quint8)); + + unsigned int num_banks = u.read(); + unsigned int apps_installed = u.read(); + + logger()->debug() << "Bank status:" << apps_installed << "/" << num_banks; + + _slots.resize(num_banks); + for (unsigned int i = 0; i < num_banks; i++) { + _slots[i].used = false; + _slots[i].id = 0; + _slots[i].name.clear(); + _slots[i].company.clear(); + _slots[i].flags = 0; + _slots[i].version = 0; + _slots[i].uuid = QUuid(); + } + + for (unsigned int i = 0; i < apps_installed; i++) { + unsigned int id = u.read(); + int index = u.read(); + QString name = u.readFixedString(32); + QString company = u.readFixedString(32); + unsigned int flags = u.read(); + unsigned short version = u.read(); + + if (index < 0 || index >= _slots.size()) { + logger()->warn() << "Invalid slot index" << index; + continue; + } + + if (u.bad()) { + logger()->warn() << "short read"; + return true; + } + + _slots[index].used = true; + _slots[index].id = id; + _slots[index].name = name; + _slots[index].company = company; + _slots[index].flags = flags; + _slots[index].version = version; + + AppInfo info = apps->info(name); + QUuid uuid = info.uuid(); + _slots[index].uuid = uuid; + + logger()->debug() << index << id << name << company << flags << version << uuid; + } + + emit this->slotsChanged(); + + return true; + }); +} + +int BankManager::findUnusedSlot() const +{ + for (int i = 0; i < _slots.size(); ++i) { + if (!_slots[i].used) { + return i; + } + } + + return -1; +} + +void BankManager::handleWatchConnected() +{ + if (watch->isConnected()) { + refresh(); + } +} + +#if 0 + +void WatchConnector::getAppbankStatus(const std::function& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), + [this, callback](const QByteArray &data) { + + }); +} + +void WatchConnector::getAppbankUuids(const function &)>& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), + [this, callback](const QByteArray &data) { + if (data.at(0) != appmgrGET_APPBANK_UUIDS) { + return false; + } + logger()->debug() << "getAppbankUuids response" << data.toHex(); + + if (data.size() < 5) { + logger()->warn() << "invalid getAppbankUuids response"; + return true; + } + + Unpacker u(data); + + u.skip(sizeof(quint8)); + + unsigned int apps_installed = u.read(); + + logger()->debug() << apps_installed; + + QList uuids; + + for (unsigned int i = 0; i < apps_installed; i++) { + QUuid uuid = u.readUuid(); + + logger()->debug() << uuid.toString(); + + if (u.bad()) { + logger()->warn() << "short read"; + return true; + } + + uuids.push_back(uuid); + } + + logger()->debug() << "finished"; + + callback(uuids); + + return true; + }); +} +#endif diff --git a/daemon/bankmanager.h b/daemon/bankmanager.h new file mode 100644 index 0000000..28729b9 --- /dev/null +++ b/daemon/bankmanager.h @@ -0,0 +1,51 @@ +#ifndef BANKMANAGER_H +#define BANKMANAGER_H + +#include "watchconnector.h" +#include "appmanager.h" + +class BankManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit BankManager(WatchConnector *watch, AppManager *apps, QObject *parent = 0); + + int numSlots() const; + + +signals: + void slotsChanged(); + +public slots: + bool uploadApp(const QUuid &uuid, int slot = -1); + bool unloadApp(int slot); + + void refresh(); + +private: + int findUnusedSlot() const; + + +private slots: + void handleWatchConnected(); + +private: + WatchConnector *watch; + AppManager *apps; + + struct SlotInfo { + bool used; + quint32 id; + QString name; + QString company; + quint32 flags; + quint16 version; + QUuid uuid; + }; + + QVector _slots; +}; + +#endif // BANKMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 81570c7..21ebffa 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -28,7 +28,8 @@ SOURCES += \ jskitmanager.cpp \ appinfo.cpp \ jskitobjects.cpp \ - packer.cpp + packer.cpp \ + bankmanager.cpp HEADERS += \ manager.h \ @@ -46,7 +47,8 @@ HEADERS += \ jskitmanager.h \ appinfo.h \ jskitobjects.h \ - packer.h + packer.h \ + bankmanager.h OTHER_FILES += \ ../log4qt-debug.conf \ diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 8a00373..27bb870 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -11,6 +11,7 @@ Manager::Manager(Settings *settings, QObject *parent) : watch(new WatchConnector(this)), dbus(new DBusConnector(this)), apps(new AppManager(this)), + bank(new BankManager(watch, apps, this)), voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), @@ -498,3 +499,14 @@ void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString & manager()->js->handleWebviewClosed(data); } + +void PebbledProxy::UnloadApp(uint slot) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (!manager()->bank->unloadApp(slot)) { + sendErrorReply(msg.interface() + ".Error.CannotUnload", + "Cannot unload application"); + } +} diff --git a/daemon/manager.h b/daemon/manager.h index 9347289..0e55190 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -10,6 +10,7 @@ #include "appmsgmanager.h" #include "jskitmanager.h" #include "appmanager.h" +#include "bankmanager.h" #include "settings.h" #include @@ -45,6 +46,7 @@ class Manager : public QObject, protected QDBusContext WatchConnector *watch; DBusConnector *dbus; AppManager *apps; + BankManager *bank; VoiceCallManager *voice; NotificationManager *notifications; MusicManager *music; @@ -143,6 +145,8 @@ public slots: QString StartAppConfiguration(const QString &uuid); void SendAppConfigurationData(const QString &uuid, const QString &data); + void UnloadApp(uint slot); + signals: void NameChanged(); void AddressChanged(); diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index baec52c..21f5ad5 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -451,93 +451,3 @@ void WatchConnector::endPhoneCall(uint cookie) { phoneControl(callEND, cookie, QStringList()); } - -void WatchConnector::getAppbankStatus(const std::function& callback) -{ - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), - [this, callback](const QByteArray &data) { - if (data.at(0) != appmgrGET_APPBANK_STATUS) { - return false; - } - logger()->debug() << "getAppbankStatus response" << data.toHex(); - - if (data.size() < 9) { - logger()->warn() << "invalid getAppbankStatus response"; - return true; - } - - Unpacker u(data); - - u.skip(sizeof(quint8)); - - unsigned int num_banks = u.read(); - unsigned int apps_installed = u.read(); - - logger()->debug() << num_banks << "/" << apps_installed; - - for (unsigned int i = 0; i < apps_installed; i++) { - unsigned int id = u.read(); - unsigned int index = u.read(); - QString name = u.readFixedString(32); - QString company = u.readFixedString(32); - unsigned int flags = u.read(); - unsigned short version = u.read(); - - logger()->debug() << id << index << name << company << flags << version; - - if (u.bad()) { - logger()->warn() << "short read"; - return true; - } - } - - logger()->debug() << "finished"; - - return true; - }); -} - -void WatchConnector::getAppbankUuids(const function &)>& callback) -{ - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), - [this, callback](const QByteArray &data) { - if (data.at(0) != appmgrGET_APPBANK_UUIDS) { - return false; - } - logger()->debug() << "getAppbankUuids response" << data.toHex(); - - if (data.size() < 5) { - logger()->warn() << "invalid getAppbankUuids response"; - return true; - } - - Unpacker u(data); - - u.skip(sizeof(quint8)); - - unsigned int apps_installed = u.read(); - - logger()->debug() << apps_installed; - - QList uuids; - - for (unsigned int i = 0; i < apps_installed; i++) { - QUuid uuid = u.readUuid(); - - logger()->debug() << uuid.toString(); - - if (u.bad()) { - logger()->warn() << "short read"; - return true; - } - - uuids.push_back(uuid); - } - - logger()->debug() << "finished"; - - callback(uuids); - - return true; - }); -} diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 6c7fdd4..a5fe1ea 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -107,6 +107,7 @@ public: }; enum AppManager { appmgrGET_APPBANK_STATUS = 1, + appmgrREMOVE_APP = 2, appmgrGET_APPBANK_UUIDS = 5 }; enum AppMessage { @@ -179,9 +180,6 @@ public: static QString timeStamp(); static QString decodeEndpoint(uint val); - void getAppbankStatus(const std::function& callback); - void getAppbankUuids(const std::function &uuids)>& callback); - signals: void messageReceived(uint endpoint, const QByteArray &data); void nameChanged(); diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 87a22b9..ce71248 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -49,5 +49,10 @@ + + + + + -- cgit v1.2.3 From 4280a9bda38046f702a4151d7b831a3bf46ef169 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 00:16:06 +0100 Subject: add d-bus API to list slot contents and fix other API issues --- daemon/bankmanager.cpp | 14 ++++++++++++++ daemon/bankmanager.h | 4 +++- daemon/manager.cpp | 25 +++++++++++++++++++++++-- daemon/manager.h | 8 ++++++-- daemon/uploadmanager.cpp | 5 +++-- daemon/watchconnector.cpp | 16 +++++++++------- daemon/watchconnector.h | 1 - org.pebbled.Watch.xml | 6 ++++-- 8 files changed, 62 insertions(+), 17 deletions(-) (limited to 'daemon/watchconnector.cpp') diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp index fe5dc21..8636e95 100644 --- a/daemon/bankmanager.cpp +++ b/daemon/bankmanager.cpp @@ -21,6 +21,16 @@ int BankManager::numSlots() const return _slots.size(); } +bool BankManager::isUsed(int slot) const +{ + return _slots.at(slot).used; +} + +QUuid BankManager::appAt(int slot) const +{ + return _slots.at(slot).uuid; +} + bool BankManager::uploadApp(const QUuid &uuid, int slot) { AppInfo info = apps->info(uuid); @@ -39,6 +49,10 @@ bool BankManager::uploadApp(const QUuid &uuid, int slot) logger()->warn() << "invalid slot index"; return false; } + if (_slots[slot].used) { + logger()->warn() << "slot in use"; + return false; + } QDir appDir(info.path()); diff --git a/daemon/bankmanager.h b/daemon/bankmanager.h index 6abedc8..871db6b 100644 --- a/daemon/bankmanager.h +++ b/daemon/bankmanager.h @@ -15,6 +15,9 @@ public: int numSlots() const; + bool isUsed(int slot) const; + QUuid appAt(int slot) const; + signals: void slotsChanged(); @@ -28,7 +31,6 @@ private: int findUnusedSlot() const; void refreshWatchApp(int slot, std::function successCallback, std::function errorCallback); - private slots: void handleWatchConnected(); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 136d7f3..469e92b 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -70,6 +70,7 @@ Manager::Manager(Settings *settings, QObject *parent) : connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::NameChanged); connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::AddressChanged); connect(watch, &WatchConnector::connectedChanged, proxy, &PebbledProxy::ConnectedChanged); + connect(bank, &BankManager::slotsChanged, proxy, &PebbledProxy::AppSlotsChanged); QString currentProfile = getCurrentProfile(); defaultProfile = currentProfile.isEmpty() ? "ambience" : currentProfile; @@ -414,6 +415,26 @@ void Manager::onAppClosed(const QUuid &uuid) emit proxy->AppUuidChanged(); } +QStringList PebbledProxy::AppSlots() const +{ + const int num_slots = manager()->bank->numSlots(); + QStringList l; + l.reserve(num_slots); + + for (int i = 0; i < num_slots; ++i) { + if (manager()->bank->isUsed(i)) { + QUuid uuid = manager()->bank->appAt(i); + l.append(uuid.toString()); + } else { + l.append(QString()); + } + } + + Q_ASSERT(l.size() == num_slots); + + return l; +} + bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) { Q_ASSERT(calledFromDBus()); @@ -501,7 +522,7 @@ void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString & manager()->js->handleWebviewClosed(data); } -void PebbledProxy::UnloadApp(uint slot) +void PebbledProxy::UnloadApp(int slot) { Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); @@ -512,7 +533,7 @@ void PebbledProxy::UnloadApp(uint slot) } } -void PebbledProxy::UploadApp(const QString &uuid, uint slot) +void PebbledProxy::UploadApp(const QString &uuid, int slot) { Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); diff --git a/daemon/manager.h b/daemon/manager.h index f27da98..0588705 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -122,6 +122,7 @@ class PebbledProxy : public QObject, protected QDBusContext Q_PROPERTY(QString Address READ Address NOTIFY AddressChanged) Q_PROPERTY(bool Connected READ Connected NOTIFY ConnectedChanged) Q_PROPERTY(QString AppUuid READ AppUuid NOTIFY AppUuidChanged) + Q_PROPERTY(QStringList AppSlots READ AppSlots NOTIFY AppSlotsChanged) inline Manager* manager() const { return static_cast(parent()); } inline QVariantMap pebble() const { return manager()->dbus->pebble(); } @@ -134,6 +135,8 @@ public: inline bool Connected() const { return manager()->watch->isConnected(); } inline QString AppUuid() const { return manager()->currentAppUuid.toString(); } + QStringList AppSlots() const; + public slots: inline void Disconnect() { manager()->watch->disconnect(); } inline void Reconnect() { manager()->watch->reconnect(); } @@ -147,14 +150,15 @@ public slots: QString StartAppConfiguration(const QString &uuid); void SendAppConfigurationData(const QString &uuid, const QString &data); - void UnloadApp(uint slot); - void UploadApp(const QString &uuid, uint slot); + void UnloadApp(int slot); + void UploadApp(const QString &uuid, int slot); signals: void NameChanged(); void AddressChanged(); void ConnectedChanged(); void AppUuidChanged(); + void AppSlotsChanged(); void AppOpened(const QString &uuid); void AppClosed(const QString &uuid); }; diff --git a/daemon/uploadmanager.cpp b/daemon/uploadmanager.cpp index 89d70f7..ccbf12a 100644 --- a/daemon/uploadmanager.cpp +++ b/daemon/uploadmanager.cpp @@ -122,8 +122,6 @@ void UploadManager::handleMessage(const QByteArray &msg) Q_ASSERT(!_pending.empty()); PendingUpload &upload = _pending.head(); - logger()->debug() << "get message" << msg.toHex(); - Unpacker u(msg); int status = u.read(); @@ -195,6 +193,9 @@ void UploadManager::handleMessage(const QByteArray &msg) startNextUpload(); } break; + default: + logger()->warn() << "received message in wrong state"; + break; } } diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 21f5ad5..e66ec0f 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -5,6 +5,7 @@ #include "unpacker.h" static const int RECONNECT_TIMEOUT = 500; //ms +static const bool PROTOCOL_DEBUG = false; using std::function; @@ -135,7 +136,7 @@ bool WatchConnector::dispatchMessage(uint endpoint, const QByteArray &data) } logger()->info() << "message to endpoint" << decodeEndpoint(endpoint) << "was not dispatched"; - emit messageReceived(endpoint, data); + logger()->debug() << data.toHex(); return false; } @@ -182,6 +183,7 @@ void WatchConnector::onReadSocket() QByteArray data = socket->read(message_length); logger()->debug() << "received message of length" << message_length << "to endpoint" << decodeEndpoint(endpoint); + if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); dispatchMessage(endpoint, data); } @@ -225,7 +227,7 @@ void WatchConnector::onDisconnected() reconnectTimer.setInterval(reconnectTimer.interval() + RECONNECT_TIMEOUT); } reconnectTimer.start(); - logger()->debug() << "Will reconnect in" << reconnectTimer.interval() << "ms"; + logger()->debug() << "will reconnect in" << reconnectTimer.interval() << "ms"; } void WatchConnector::onError(QBluetoothSocket::SocketError error) @@ -233,7 +235,7 @@ void WatchConnector::onError(QBluetoothSocket::SocketError error) if (error == QBluetoothSocket::UnknownSocketError) { logger()->info() << error << socket->errorString(); } else { - logger()->error() << "Error connecting Pebble:" << error << socket->errorString(); + logger()->error() << "error connecting Pebble:" << error << socket->errorString(); } } @@ -241,11 +243,11 @@ void WatchConnector::sendData(const QByteArray &data) { writeData.append(data); if (socket == nullptr) { - logger()->debug() << "No socket - reconnecting"; + logger()->debug() << "no socket - reconnecting"; reconnect(); } else if (is_connected) { - logger()->debug() << "Writing" << data.length() << "bytes to socket"; - logger()->debug() << data.toHex(); + logger()->debug() << "writing" << data.length() << "bytes to socket"; + if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); socket->write(data); } } @@ -253,7 +255,7 @@ void WatchConnector::sendData(const QByteArray &data) void WatchConnector::onBytesWritten(qint64 bytes) { writeData.remove(0, bytes); - logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left"; + logger()->debug() << "socket written" << bytes << "bytes," << writeData.length() << "left"; } void WatchConnector::sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback) diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 1aaf39d..2e89b8c 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -193,7 +193,6 @@ public: static QString decodeEndpoint(uint val); signals: - void messageReceived(uint endpoint, const QByteArray &data); void nameChanged(); void connectedChanged(); diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 2c5202f..e076d6c 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -51,12 +51,14 @@ + + - + - + -- cgit v1.2.3 From fe08705333690db42ac758e55776f94c667d0194 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 15:46:16 +0100 Subject: cleanup BT message decoding --- daemon/watchconnector.cpp | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) (limited to 'daemon/watchconnector.cpp') diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index e66ec0f..22f4a8a 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -149,41 +149,43 @@ void WatchConnector::onReadSocket() QBluetoothSocket *socket = qobject_cast(sender()); Q_ASSERT(socket && socket == this->socket); + // Keep attempting to read messages as long as at least a header is present while (socket->bytesAvailable() >= header_length) { - // Do nothing if there is no message to read. - if (socket->bytesAvailable() < header_length) { - if (socket->bytesAvailable() > 0) { - logger()->debug() << "incomplete header in read buffer"; - } - return; - } - + // Take a look at the header, but do not remove it from the socket input buffer. + // We will only remove it once we're sure the entire packet is in the buffer. uchar header[header_length]; socket->peek(reinterpret_cast(header), header_length); - quint16 message_length, endpoint; - message_length = qFromBigEndian(&header[0]); - endpoint = qFromBigEndian(&header[sizeof(quint16)]); + quint16 message_length = qFromBigEndian(&header[0]); + quint16 endpoint = qFromBigEndian(&header[2]); - if (message_length > 8 * 1024) { + // Sanity checks on the message_length + if (message_length == 0) { + logger()->warn() << "received empty message"; + socket->read(header_length); // skip this header + continue; // check if there are additional headers. + } else if (message_length > 8 * 1024) { // Protocol does not allow messages more than 8K long, seemingly. logger()->warn() << "received message size too long: " << message_length; - socket->readAll(); // drop input buffer + socket->readAll(); // drop entire input buffer return; } // Now wait for the entire message if (socket->bytesAvailable() < header_length + message_length) { logger()->debug() << "incomplete msg body in read buffer"; - return; + return; // try again once more data comes in } - socket->read(header_length); // Skip the header + // We can now safely remove the header from the input buffer, + // as we know the entire message is in the input buffer. + socket->read(header_length); + // Now read the rest of the message QByteArray data = socket->read(message_length); logger()->debug() << "received message of length" << message_length << "to endpoint" << decodeEndpoint(endpoint); - if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); + if (PROTOCOL_DEBUG) logger()->trace() << data.toHex(); dispatchMessage(endpoint, data); } @@ -247,7 +249,7 @@ void WatchConnector::sendData(const QByteArray &data) reconnect(); } else if (is_connected) { logger()->debug() << "writing" << data.length() << "bytes to socket"; - if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); + if (PROTOCOL_DEBUG) logger()->trace() << data.toHex(); socket->write(data); } } -- cgit v1.2.3 From ab362bf43227552bab209fea792d60d370ce0260 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 06:03:21 +0100 Subject: implement account and device tokens --- daemon/jskitmanager.cpp | 4 +-- daemon/jskitmanager.h | 5 +++- daemon/jskitobjects.cpp | 39 +++++++++++++++++++++++++++++ daemon/jskitobjects.h | 3 +++ daemon/manager.cpp | 2 +- daemon/settings.h | 29 ++++++++++----------- daemon/watchconnector.cpp | 64 +++++++++++++++++++++++++++++++++++++++++------ daemon/watchconnector.h | 2 ++ 8 files changed, 123 insertions(+), 25 deletions(-) (limited to 'daemon/watchconnector.cpp') diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index c73d32e..6d40970 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -4,8 +4,8 @@ #include "jskitmanager.h" #include "jskitobjects.h" -JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent) : - QObject(parent), _apps(apps), _appmsg(appmsg), _engine(0) +JSKitManager::JSKitManager(WatchConnector *watch, AppManager *apps, AppMsgManager *appmsg, Settings *settings, QObject *parent) : + QObject(parent), _watch(watch), _apps(apps), _appmsg(appmsg), _settings(settings), _engine(0) { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 7ffc0ad..871ab8e 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -4,6 +4,7 @@ #include #include "appmanager.h" #include "appmsgmanager.h" +#include "settings.h" class JSKitPebble; class JSKitConsole; @@ -16,7 +17,7 @@ class JSKitManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); + explicit JSKitManager(WatchConnector *watch, AppManager *apps, AppMsgManager *appmsg, Settings *settings, QObject *parent = 0); ~JSKitManager(); QJSEngine * engine(); @@ -44,8 +45,10 @@ private: private: friend class JSKitPebble; + WatchConnector *_watch; AppManager *_apps; AppMsgManager *_appmsg; + Settings *_settings; AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 5f5acaf..fe924e8 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -4,9 +4,12 @@ #include #include #include +#include #include #include "jskitobjects.h" +static const char *token_salt = "0feeb7416d3c4546a19b04bccd8419b1"; + JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) : QObject(mgr), _appInfo(info), _mgr(mgr) { @@ -88,6 +91,42 @@ void JSKitPebble::openURL(const QUrl &url) emit _mgr->appOpenUrl(url); } +QString JSKitPebble::getAccountToken() const +{ + // We do not have any account system, so we just fake something up. + QCryptographicHash hasher(QCryptographicHash::Md5); + + hasher.addData(token_salt, strlen(token_salt)); + hasher.addData(_appInfo.uuid().toByteArray()); + + QString token = _mgr->_settings->property("accountToken").toString(); + if (token.isEmpty()) { + token = QUuid::createUuid().toString(); + logger()->debug() << "created new account token" << token; + _mgr->_settings->setProperty("accountToken", token); + } + hasher.addData(token.toLatin1()); + + QString hash = hasher.result().toHex(); + logger()->debug() << "returning account token" << hash; + + return hash; +} + +QString JSKitPebble::getWatchToken() const +{ + QCryptographicHash hasher(QCryptographicHash::Md5); + + hasher.addData(token_salt, strlen(token_salt)); + hasher.addData(_appInfo.uuid().toByteArray()); + hasher.addData(_mgr->_watch->serialNumber().toLatin1()); + + QString hash = hasher.result().toHex(); + logger()->debug() << "returning watch token" << hash; + + return hash; +} + QJSValue JSKitPebble::createXMLHttpRequest() { JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(_mgr, 0); diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 23dfabe..532ce1b 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -25,6 +25,9 @@ public: Q_INVOKABLE void openURL(const QUrl &url); + Q_INVOKABLE QString getAccountToken() const; + Q_INVOKABLE QString getWatchToken() const; + Q_INVOKABLE QJSValue createXMLHttpRequest(); void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 212a1d7..32b313f 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -18,7 +18,7 @@ Manager::Manager(Settings *settings, QObject *parent) : music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), appmsg(new AppMsgManager(apps, watch, this)), - js(new JSKitManager(apps, appmsg, this)), + js(new JSKitManager(watch, apps, appmsg, settings, this)), notification(MNotification::DeviceEvent) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); diff --git a/daemon/settings.h b/daemon/settings.h index d6db9b6..90e25e2 100644 --- a/daemon/settings.h +++ b/daemon/settings.h @@ -18,6 +18,8 @@ class Settings : public MDConfGroup Q_PROPERTY(bool notificationsFacebook MEMBER notificationsFacebook NOTIFY notificationsFacebookChanged) Q_PROPERTY(bool notificationsOther MEMBER notificationsOther NOTIFY notificationsOtherChanged) Q_PROPERTY(bool notificationsAll MEMBER notificationsAll NOTIFY notificationsAllChanged) + Q_PROPERTY(QString accountToken MEMBER accountToken NOTIFY accountTokenChanged) + bool silentWhenConnected; bool transliterateMessage; bool incomingCallNotification; @@ -29,6 +31,7 @@ class Settings : public MDConfGroup bool notificationsFacebook; bool notificationsOther; bool notificationsAll; + QString accountToken; public: explicit Settings(QObject *parent = 0) : @@ -36,20 +39,18 @@ public: { resolveMetaObject(); } signals: - void silentWhenConnectedChanged(bool); - void transliterateMessageChanged(bool); - void incomingCallNotificationChanged(bool); - void notificationsCommhistorydChanged(bool); - void notificationsMissedCallChanged(bool); - void notificationsEmailsChanged(bool); - void notificationsMitakuuluuChanged(bool); - void notificationsTwitterChanged(bool); - void notificationsFacebookChanged(bool); - void notificationsOtherChanged(bool); - void notificationsAllChanged(bool); - -public slots: - + void silentWhenConnectedChanged(); + void transliterateMessageChanged(); + void incomingCallNotificationChanged(); + void notificationsCommhistorydChanged(); + void notificationsMissedCallChanged(); + void notificationsEmailsChanged(); + void notificationsMitakuuluuChanged(); + void notificationsTwitterChanged(); + void notificationsFacebookChanged(); + void notificationsOtherChanged(); + void notificationsAllChanged(); + void accountTokenChanged(); }; #endif // SETTINGS_H diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 22f4a8a..27a5511 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -1,19 +1,60 @@ #include #include -#include "watchconnector.h" #include "unpacker.h" +#include "watchconnector.h" static const int RECONNECT_TIMEOUT = 500; //ms static const bool PROTOCOL_DEBUG = false; -using std::function; - WatchConnector::WatchConnector(QObject *parent) : QObject(parent), socket(nullptr), is_connected(false) { reconnectTimer.setSingleShot(true); connect(&reconnectTimer, SIGNAL(timeout()), SLOT(reconnect())); + + setEndpointHandler(watchVERSION, [this](const QByteArray &data) { + Unpacker u(data); + + u.skip(1); + + quint32 version = u.read(); + QString version_string = u.readFixedString(32); + QString commit = u.readFixedString(8); + bool is_recovery = u.read(); + quint8 hw_platform = u.read(); + quint8 metadata_version = u.read(); + + quint32 safe_version = u.read(); + QString safe_version_string = u.readFixedString(32); + QString safe_commit = u.readFixedString(8); + bool safe_is_recovery = u.read(); + quint8 safe_hw_platform = u.read(); + quint8 safe_metadata_version = u.read(); + + quint32 bootLoaderTimestamp = u.read(); + QString hardwareRevision = u.readFixedString(9); + QString serialNumber = u.readFixedString(12); + QByteArray address = u.readBytes(6); + + if (u.bad()) { + logger()->warn() << "short read while reading firmware version"; + } + + logger()->debug() << "got version information" + << version << version_string << commit + << is_recovery << hw_platform << metadata_version; + logger()->debug() << "recovery version information" + << safe_version << safe_version_string << safe_commit + << safe_is_recovery << safe_hw_platform << safe_metadata_version; + logger()->debug() << "hardware information" << bootLoaderTimestamp << hardwareRevision; + logger()->debug() << "serial number" << serialNumber.left(3) << "..."; + logger()->debug() << "bt address" << address.toHex(); + + this->_serialNumber = serialNumber; + + return true; + }); } WatchConnector::~WatchConnector() @@ -46,11 +87,11 @@ void WatchConnector::reconnect() void WatchConnector::disconnect() { - logger()->debug() << __FUNCTION__; + logger()->debug() << "disconnecting"; socket->close(); socket->deleteLater(); reconnectTimer.stop(); - logger()->debug() << "Stopped reconnect timer"; + logger()->debug() << "stopped reconnect timer"; } void WatchConnector::handleWatch(const QString &name, const QString &address) @@ -67,6 +108,11 @@ void WatchConnector::handleWatch(const QString &name, const QString &address) _last_address = address; if (emit_name) emit nameChanged(); + if (emit_name) { + // If we've changed names, don't reuse cached serial number! + _serialNumber.clear(); + } + logger()->debug() << "Creating socket"; #if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) socket = new QBluetoothSocket(QBluetoothSocket::RfcommSocket); @@ -198,11 +244,15 @@ void WatchConnector::onConnected() is_connected = true; reconnectTimer.stop(); reconnectTimer.setInterval(0); - if (not was_connected) { - if (not writeData.isEmpty()) { + if (!was_connected) { + if (!writeData.isEmpty()) { logger()->info() << "Found" << writeData.length() << "bytes in write buffer - resending"; sendData(writeData); } + if (_serialNumber.isEmpty()) { + // Ask for version information from the watch + sendMessage(watchVERSION, QByteArray(1, 0)); + } emit connectedChanged(); } } diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 6546302..45fd3c7 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -188,6 +188,7 @@ public: inline bool isConnected() const { return is_connected; } inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } + inline QString serialNumber() const { return _serialNumber; } void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); void clearEndpointHandler(uint endpoint); @@ -245,6 +246,7 @@ private: QTimer reconnectTimer; QString _last_name; QString _last_address; + QString _serialNumber; }; #endif // WATCHCONNECTOR_H -- cgit v1.2.3