diff --git a/libmea-core/discovery/discoverydevice.cpp b/libmea-core/discovery/discoverydevice.cpp index 7f9d5e18..000fac6e 100644 --- a/libmea-core/discovery/discoverydevice.cpp +++ b/libmea-core/discovery/discoverydevice.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2018 Michael Zanetti * * * * This file is part of mea. * * * @@ -22,38 +22,19 @@ #include -DiscoveryDevice::DiscoveryDevice() +DiscoveryDevice::DiscoveryDevice(QObject *parent): QObject(parent) { + m_portConfigs = new PortConfigs(this); } -QUrl DiscoveryDevice::location() const +QUuid DiscoveryDevice::uuid() const { - return m_location; + return m_uuid; } -void DiscoveryDevice::setLocation(const QUrl &location) +void DiscoveryDevice::setUuid(const QUuid &uuid) { - m_location = location; -} - -QString DiscoveryDevice::webSocketUrl() const -{ - return m_webSocketUrl; -} - -void DiscoveryDevice::setWebSocketUrl(const QString &webSocketUrl) -{ - m_webSocketUrl = webSocketUrl; -} - -QString DiscoveryDevice::nymeaRpcUrl() const -{ - return m_nymeaRpcUrl; -} - -void DiscoveryDevice::setNymeaRpcUrl(const QString &nymeaRpcUrl) -{ - m_nymeaRpcUrl = nymeaRpcUrl; + m_uuid = uuid; } QHostAddress DiscoveryDevice::hostAddress() const @@ -61,116 +42,156 @@ QHostAddress DiscoveryDevice::hostAddress() const return m_hostAddress; } -void DiscoveryDevice::setHostAddress(const QHostAddress &hostAddress) +QString DiscoveryDevice::hostAddressString() const { - m_hostAddress = hostAddress; + return m_hostAddress.toString(); } -int DiscoveryDevice::port() const +void DiscoveryDevice::setHostAddress(const QHostAddress &hostAddress) +{ + if (m_hostAddress != hostAddress) { + m_hostAddress = hostAddress; + emit hostAddressChanged(); + } +} + +QString DiscoveryDevice::name() const +{ + return m_name; +} + +void DiscoveryDevice::setName(const QString &name) +{ + if (m_name != name) { + m_name = name; + emit nameChanged(); + } +} + +QString DiscoveryDevice::version() const +{ + return m_version; +} + +void DiscoveryDevice::setVersion(const QString &version) +{ + if (m_version != version) { + m_version = version; + emit versionChanged(); + } +} + +PortConfigs* DiscoveryDevice::portConfigs() const +{ + return m_portConfigs; +} + +QString DiscoveryDevice::toUrl(int portConfigIndex) +{ + PortConfig *pc = m_portConfigs->get(portConfigIndex); + if (!pc) { + qWarning() << "No portconfig for index" << portConfigIndex; + } + QString ret = pc->protocol() == PortConfig::ProtocolNymeaRpc ? "nymea" : "ws"; + ret += pc->sslEnabled() ? "s" : ""; + ret += "://"; + ret += m_hostAddress.toString(); + ret += ":"; + ret += QString::number(pc->port()); + return ret; +} + +PortConfigs::PortConfigs(QObject *parent): QAbstractListModel(parent) +{ + +} + +int PortConfigs::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_portConfigs.count(); +} + +QVariant PortConfigs::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RolePort: + return m_portConfigs.at(index.row())->port(); + case RoleProtocol: + return m_portConfigs.at(index.row())->protocol(); + case RoleSSLEnabled: + return m_portConfigs.at(index.row())->sslEnabled(); + } + return QVariant(); +} + +PortConfig *PortConfigs::find(int port) +{ + foreach (PortConfig* pc, m_portConfigs) { + if (pc->port() == port) { + return pc; + } + } + return nullptr; +} + +void PortConfigs::insert(PortConfig *portConfig) +{ + portConfig->setParent(this); + beginInsertRows(QModelIndex(), m_portConfigs.count(), m_portConfigs.count()); + m_portConfigs.append(portConfig); + endInsertRows(); + emit countChanged(); +} + +PortConfig* PortConfigs::get(int index) const +{ + return m_portConfigs.at(index); +} + +QHash PortConfigs::roleNames() const +{ + QHash roles; + roles.insert(RolePort, "port"); + roles.insert(RoleProtocol, "protocol"); + roles.insert(RoleSSLEnabled, "sslEnabled"); + return roles; +} + +PortConfig::PortConfig(int port, QObject *parent): + QObject(parent), + m_port(port) +{ + +} + +int PortConfig::port() const { return m_port; } -void DiscoveryDevice::setPort(const int &port) +PortConfig::Protocol PortConfig::protocol() const { - m_port = port; + return m_protocol; } -QString DiscoveryDevice::friendlyName() const +void PortConfig::setProtocol(PortConfig::Protocol protocol) { - return m_friendlyName; + if (m_protocol != protocol) { + m_protocol = protocol; + emit protocolChanged(); + } } -void DiscoveryDevice::setFriendlyName(const QString &friendlyName) +bool PortConfig::sslEnabled() const { - m_friendlyName = friendlyName; + return m_sslEnabled; } -QString DiscoveryDevice::manufacturer() const +void PortConfig::setSslEnabled(bool sslEnabled) { - return m_manufacturer; -} - -void DiscoveryDevice::setManufacturer(const QString &manufacturer) -{ - m_manufacturer = manufacturer; -} - -QUrl DiscoveryDevice::manufacturerURL() const -{ - return m_manufacturerURL; -} - -void DiscoveryDevice::setManufacturerURL(const QUrl &manufacturerURL) -{ - m_manufacturerURL = manufacturerURL; -} - -QString DiscoveryDevice::modelDescription() const -{ - return m_modelDescription; -} - -void DiscoveryDevice::setModelDescription(const QString &modelDescription) -{ - m_modelDescription = modelDescription; -} - -QString DiscoveryDevice::modelName() const -{ - return m_modelName; -} - -void DiscoveryDevice::setModelName(const QString &modelName) -{ - m_modelName = modelName; -} - -QString DiscoveryDevice::modelNumber() const -{ - return m_modelNumber; -} - -void DiscoveryDevice::setModelNumber(const QString &modelNumber) -{ - m_modelNumber = modelNumber; -} - -QUrl DiscoveryDevice::modelURL() const -{ - return m_modelURL; -} - -void DiscoveryDevice::setModelURL(const QUrl &modelURL) -{ - m_modelURL = modelURL; -} - -QString DiscoveryDevice::uuid() const -{ - return m_uuid; -} - -void DiscoveryDevice::setUuid(const QString &uuid) -{ - m_uuid = uuid; -} - -QDebug operator<<(QDebug debug, const DiscoveryDevice &DiscoveryDevice) -{ - debug << "----------------------------------------------\n"; - debug << "UPnP device on " << QString("%1:%2").arg(DiscoveryDevice.hostAddress().toString()).arg(DiscoveryDevice.port()) << "\n"; - debug << "location | " << DiscoveryDevice.location().toString() << "\n"; - debug << "websocket | " << DiscoveryDevice.webSocketUrl() << "\n"; - debug << "nymearpc | " << DiscoveryDevice.nymeaRpcUrl() << "\n"; - debug << "friendly name | " << DiscoveryDevice.friendlyName() << "\n"; - debug << "manufacturer | " << DiscoveryDevice.manufacturer() << "\n"; - debug << "manufacturer URL | " << DiscoveryDevice.manufacturerURL().toString() << "\n"; - debug << "model name | " << DiscoveryDevice.modelName() << "\n"; - debug << "model number | " << DiscoveryDevice.modelNumber() << "\n"; - debug << "model description | " << DiscoveryDevice.modelDescription() << "\n"; - debug << "model URL | " << DiscoveryDevice.modelURL().toString() << "\n"; - debug << "UUID | " << DiscoveryDevice.uuid() << "\n"; - - return debug; + if (m_sslEnabled != sslEnabled) { + m_sslEnabled = sslEnabled; + emit sslEnabledChanged(); + } } diff --git a/libmea-core/discovery/discoverydevice.h b/libmea-core/discovery/discoverydevice.h index 87582ad4..de6ac57f 100644 --- a/libmea-core/discovery/discoverydevice.h +++ b/libmea-core/discovery/discoverydevice.h @@ -1,20 +1,20 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2018 Michael Zanetti * * * - * This file is part of mea. * + * This file is part of mea. * * * - * mea is free software: you can redistribute it and/or modify * + * mea is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, version 3 of the License. * * * - * mea is distributed in the hope that it will be useful, * + * mea is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * - * along with mea. If not, see . * + * along with mea. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ @@ -22,72 +22,115 @@ #define DISCOVERYDEVICE_H #include +#include #include #include +#include +#include -class DiscoveryDevice +class PortConfig: public QObject { + Q_OBJECT + Q_PROPERTY(int port READ port CONSTANT) + Q_PROPERTY(Protocol protocol READ protocol NOTIFY protocolChanged) + Q_PROPERTY(bool sslEnabled READ sslEnabled NOTIFY sslEnabledChanged) public: - explicit DiscoveryDevice(); - - QUrl location() const; - void setLocation(const QUrl &location); - - QString webSocketUrl() const; - void setWebSocketUrl(const QString &webSocketUrl); - - QString nymeaRpcUrl() const; - void setNymeaRpcUrl(const QString &nymeaRpcUrl); - - QHostAddress hostAddress() const; - void setHostAddress(const QHostAddress &hostAddress); + enum Protocol { + ProtocolNymeaRpc, + ProtocolWebSocket + }; + Q_ENUM(Protocol) + PortConfig(int port, QObject *parent = nullptr); int port() const; - void setPort(const int &port); - QString deviceType() const; - void setDeviceType(const QString & deviceType); + Protocol protocol() const; + void setProtocol(Protocol protocol); - QString friendlyName() const; - void setFriendlyName(const QString &friendlyName); + bool sslEnabled() const; + void setSslEnabled(bool sslEnabled); - QString manufacturer() const; - void setManufacturer(const QString &manufacturer); - - QUrl manufacturerURL() const; - void setManufacturerURL(const QUrl & manufacturerURL); - - QString modelDescription() const; - void setModelDescription(const QString & modelDescription); - - QString modelName() const; - void setModelName(const QString & modelName); - - QString modelNumber() const; - void setModelNumber(const QString &modelNumber); - - QUrl modelURL() const; - void setModelURL(const QUrl &modelURL); - - QString uuid() const; - void setUuid(const QString &uuid); +signals: + void protocolChanged(); + void sslEnabledChanged(); private: - QUrl m_location; - QString m_webSocketUrl; - QString m_nymeaRpcUrl; - QHostAddress m_hostAddress; - int m_port; - QString m_friendlyName; - QString m_manufacturer; - QUrl m_manufacturerURL; - QString m_modelDescription; - QString m_modelName; - QString m_modelNumber; - QUrl m_modelURL; - QString m_uuid; + int m_port = -1; + Protocol m_protocol = ProtocolNymeaRpc; + bool m_sslEnabled = false; }; -QDebug operator<< (QDebug debug, const DiscoveryDevice &discoveryDevice); +class PortConfigs: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) +public: + enum Roles { + RolePort, + RoleProtocol, + RoleSSLEnabled + }; + Q_ENUM(Roles) + PortConfigs(QObject* parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; + + PortConfig* find(int port); + void insert(PortConfig* portConfig); + + Q_INVOKABLE PortConfig *get(int index) const; + +signals: + void countChanged(); + +protected: + QHash roleNames() const override; + +private: + QList m_portConfigs; + +}; + +class DiscoveryDevice: public QObject +{ + Q_OBJECT + Q_PROPERTY(QUuid uuid READ uuid CONSTANT) + Q_PROPERTY(QString hostAddress READ hostAddressString NOTIFY hostAddressChanged) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString version READ version NOTIFY versionChanged) + Q_PROPERTY(PortConfigs* portConfigs READ portConfigs CONSTANT) + +public: + explicit DiscoveryDevice(QObject *parent = nullptr); + + QUuid uuid() const; + void setUuid(const QUuid &uuid); + + QHostAddress hostAddress() const; + QString hostAddressString() const; + void setHostAddress(const QHostAddress &hostAddress); + + QString name() const; + void setName(const QString &name); + + QString version() const; + void setVersion(const QString &version); + + PortConfigs *portConfigs() const; + + Q_INVOKABLE QString toUrl(int portConfigIndex); + +signals: + void nameChanged(); + void hostAddressChanged(); + void versionChanged(); + +private: + QUuid m_uuid; + QHostAddress m_hostAddress; + QString m_name; + QString m_version; + PortConfigs *m_portConfigs = nullptr; +}; #endif // DISCOVERYDEVICE_H diff --git a/libmea-core/discovery/discoverymodel.cpp b/libmea-core/discovery/discoverymodel.cpp index 2b892934..1a2534b1 100644 --- a/libmea-core/discovery/discoverymodel.cpp +++ b/libmea-core/discovery/discoverymodel.cpp @@ -25,11 +25,6 @@ DiscoveryModel::DiscoveryModel(QObject *parent) : { } -QList DiscoveryModel::devices() -{ - return m_devices; -} - int DiscoveryModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) @@ -41,61 +36,54 @@ QVariant DiscoveryModel::data(const QModelIndex &index, int role) const if (index.row() < 0 || index.row() >= m_devices.count()) return QVariant(); - DiscoveryDevice device = m_devices.at(index.row()); - if (role == NameRole) { - return device.friendlyName(); - } else if (role == HostAddressRole) { - return device.hostAddress().toString(); - } else if (role == WebSocketUrlRole) { - return device.webSocketUrl(); - } else if (role == PortRole) { - return device.port(); - } else if (role == VersionRole) { - return device.modelNumber(); - } else if (role == NymeaRpcUrlRole) { - return device.nymeaRpcUrl(); + DiscoveryDevice *device = m_devices.at(index.row()); + switch (role) { + case UuidRole: + return device->uuid(); + case NameRole: + return device->name(); + case HostAddressRole: + return device->hostAddress().toString(); +// case WebSocketUrlRole: +// return device.webSocketUrl(); +// case PortRole: +// return device.port(); + case VersionRole: + return device->version(); +// case NymeaRpcUrlRole: +// return device.nymeaRpcUrl(); } return QVariant(); } -void DiscoveryModel::addDevice(const DiscoveryDevice &device) +void DiscoveryModel::addDevice(DiscoveryDevice *device) { for (int i = 0; i < m_devices.count(); i++) { - if (m_devices.at(i).uuid() == device.uuid()) { - m_devices[i] = device; - emit dataChanged(index(i), index(i)); + if (m_devices.at(i)->uuid() == device->uuid()) { + qWarning() << "Device already added. Update existing device instead."; return; } } + device->setParent(this); beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count()); m_devices.append(device); endInsertRows(); emit countChanged(); } -QString DiscoveryModel::get(int index, DeviceRole role) const +DiscoveryDevice *DiscoveryModel::get(int index) const { - return data(this->index(index), role).toString(); + return m_devices.at(index); } -bool DiscoveryModel::contains(const QString &uuid) const +DiscoveryDevice *DiscoveryModel::find(const QUuid &uuid) { - foreach (const DiscoveryDevice &dev, m_devices) { - if (dev.uuid() == uuid) { - return true; - } - } - return false; -} - -DiscoveryDevice DiscoveryModel::find(const QHostAddress &address) const -{ - foreach (const DiscoveryDevice &dev, m_devices) { - if (dev.hostAddress() == address) { + foreach (DiscoveryDevice *dev, m_devices) { + if (dev->uuid() == uuid) { return dev; } } - return DiscoveryDevice(); + return nullptr; } void DiscoveryModel::clearModel() @@ -109,11 +97,9 @@ void DiscoveryModel::clearModel() QHash DiscoveryModel::roleNames() const { QHash roles; + roles[UuidRole] = "uuid"; roles[NameRole] = "name"; roles[HostAddressRole] = "hostAddress"; - roles[WebSocketUrlRole] = "webSocketUrl"; - roles[NymeaRpcUrlRole] = "nymeaRpcUrl"; - roles[PortRole] = "port"; roles[VersionRole] = "version"; return roles; } diff --git a/libmea-core/discovery/discoverymodel.h b/libmea-core/discovery/discoverymodel.h index b9a434ae..ce830765 100644 --- a/libmea-core/discovery/discoverymodel.h +++ b/libmea-core/discovery/discoverymodel.h @@ -32,27 +32,22 @@ class DiscoveryModel : public QAbstractListModel Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: enum DeviceRole { + UuidRole, NameRole, HostAddressRole, - WebSocketUrlRole, - NymeaRpcUrlRole, - PortRole, VersionRole }; Q_ENUM(DeviceRole) explicit DiscoveryModel(QObject *parent = 0); - QList devices(); - int rowCount(const QModelIndex & parent = QModelIndex()) const; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; - void addDevice(const DiscoveryDevice &device); + void addDevice(DiscoveryDevice *device); - Q_INVOKABLE QString get(int index, DiscoveryModel::DeviceRole role) const; - bool contains(const QString &uuid) const; - DiscoveryDevice find(const QHostAddress &address) const; + Q_INVOKABLE DiscoveryDevice* get(int index) const; + Q_INVOKABLE DiscoveryDevice* find(const QUuid &uuid); void clearModel(); @@ -63,7 +58,7 @@ protected: QHash roleNames() const; private: - QList m_devices; + QList m_devices; }; #endif // DISCOVERYMODEL_H diff --git a/libmea-core/discovery/upnpdiscovery.cpp b/libmea-core/discovery/upnpdiscovery.cpp index 7c95ae6f..d3727a49 100644 --- a/libmea-core/discovery/upnpdiscovery.cpp +++ b/libmea-core/discovery/upnpdiscovery.cpp @@ -114,7 +114,7 @@ void UpnpDiscovery::writeDiscoveryPacket() "MX:2\r\n" "ST: ssdp:all\r\n\r\n"); - qDebug() << "sending discovery packet"; +// qDebug() << "sending discovery packet"; writeDatagram(ssdpSearchMessage, m_host, m_port); } @@ -170,97 +170,104 @@ void UpnpDiscovery::readData() if (!m_foundDevices.contains(location) && isNymea) { m_foundDevices.append(location); - DiscoveryDevice discoveryDevice; - discoveryDevice.setHostAddress(hostAddress); - discoveryDevice.setPort(port); - discoveryDevice.setLocation(location.toString()); - qDebug() << "Getting server data from:" << location; QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(location)); connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList &errors){ reply->ignoreSslErrors(errors); }); - m_runningReplies.insert(reply, discoveryDevice); + m_runningReplies.insert(reply, hostAddress); } } } void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) { + reply->deleteLater(); + QHostAddress discoveredAddress = m_runningReplies.take(reply); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (reply->error() != QNetworkReply::NoError || status != 200) { + qWarning() << "Error fetching UPnP discovery data:" << status << reply->error() << reply->errorString(); + return; + } QByteArray data = reply->readAll(); - DiscoveryDevice discoveryDevice = m_runningReplies.take(reply); - switch (status) { - case(200):{ - // parse XML data - QXmlStreamReader xml(data); - while (!xml.atEnd() && !xml.hasError()) { - xml.readNext(); + QString name; + QString version; + QUuid uuid; + QList portConfigList; - if (xml.isStartDocument()) - continue; + // parse XML data + QXmlStreamReader xml(data); + while (!xml.atEnd() && !xml.hasError()) { + xml.readNext(); - if (xml.isStartElement()) { - if (xml.name().toString() == "websocketURL") { - discoveryDevice.setWebSocketUrl(xml.readElementText()); - } + if (xml.isStartDocument()) + continue; + + if (xml.isStartElement()) { + if (xml.name().toString() == "websocketURL") { + QUrl u(xml.readElementText()); + PortConfig *pc = new PortConfig(u.port()); + pc->setProtocol(PortConfig::ProtocolWebSocket); + pc->setSslEnabled(u.scheme().endsWith('s')); + portConfigList.append(pc); } + } - if (xml.isStartElement()) { - if (xml.name().toString() == "nymeaRpcURL") { - discoveryDevice.setNymeaRpcUrl(xml.readElementText()); - } + if (xml.isStartElement()) { + if (xml.name().toString() == "nymeaRpcURL") { + QUrl u(xml.readElementText()); + qDebug() << "have url" << u << u.scheme(); + PortConfig *pc = new PortConfig(u.port()); + pc->setProtocol(PortConfig::ProtocolNymeaRpc); + pc->setSslEnabled(u.scheme().endsWith('s')); + portConfigList.append(pc); } + } - if (xml.isStartElement()) { - if (xml.name().toString() == "device") { - while (!xml.atEnd()) { - if (xml.name() == "friendlyName" && xml.isStartElement()) { - discoveryDevice.setFriendlyName(xml.readElementText()); - } - if (xml.name() == "manufacturer" && xml.isStartElement()) { - discoveryDevice.setManufacturer(xml.readElementText()); - } - if (xml.name() == "manufacturerURL" && xml.isStartElement()) { - discoveryDevice.setManufacturerURL(QUrl(xml.readElementText())); - } - if (xml.name() == "modelDescription" && xml.isStartElement()) { - discoveryDevice.setModelDescription(xml.readElementText()); - } - if (xml.name() == "modelName" && xml.isStartElement()) { - discoveryDevice.setModelName(xml.readElementText()); - } - if (xml.name() == "modelNumber" && xml.isStartElement()) { - discoveryDevice.setModelNumber(xml.readElementText()); - } - if (xml.name() == "modelURL" && xml.isStartElement()) { - discoveryDevice.setModelURL(QUrl(xml.readElementText())); - } - if (xml.name() == "UDN" && xml.isStartElement()) { - discoveryDevice.setUuid(xml.readElementText()); - } - xml.readNext(); + if (xml.isStartElement()) { + if (xml.name().toString() == "device") { + while (!xml.atEnd()) { + if (xml.name() == "friendlyName" && xml.isStartElement()) { + name = xml.readElementText(); + } + if (xml.name() == "modelNumber" && xml.isStartElement()) { + version = xml.readElementText(); + } + if (xml.name() == "UDN" && xml.isStartElement()) { + uuid = xml.readElementText().split(':').last(); } xml.readNext(); } + xml.readNext(); } } + } - qDebug() << "discovered device" << discoveryDevice.friendlyName() << discoveryDevice.hostAddress(); + qDebug() << "discovered device" << uuid << name << discoveredAddress << version; - if (discoveryDevice.manufacturer().contains("guh")) { - if (!m_discoveryModel->contains(discoveryDevice.uuid())) { - m_discoveryModel->addDevice(discoveryDevice); - } + DiscoveryDevice* device = m_discoveryModel->find(uuid); + if (!device) { + device = new DiscoveryDevice(m_discoveryModel); + device->setUuid(uuid); + qDebug() << "Adding new host to model"; + m_discoveryModel->addDevice(device); + } + device->setHostAddress(discoveredAddress); + device->setName(name); + device->setVersion(version); + foreach (PortConfig *pc, portConfigList) { + PortConfig *portConfig = device->portConfigs()->find(pc->port()); + if (portConfig) { + qDebug() << "Updating port config" << portConfig->port() << portConfig->sslEnabled() << portConfig->protocol(); + portConfig->setProtocol(pc->protocol()); + portConfig->setSslEnabled(pc->sslEnabled()); + pc->deleteLater(); + } else { + qDebug() << "adding new port config" << pc->port() << pc->sslEnabled() << pc->protocol(); + device->portConfigs()->insert(pc); } - - break; } - default: - qWarning() << "HTTP request error " << status; - } - - reply->deleteLater(); } diff --git a/libmea-core/discovery/upnpdiscovery.h b/libmea-core/discovery/upnpdiscovery.h index 4c5c57a6..7ca0ef39 100644 --- a/libmea-core/discovery/upnpdiscovery.h +++ b/libmea-core/discovery/upnpdiscovery.h @@ -55,7 +55,7 @@ private: bool m_discovering; bool m_available; - QHash m_runningReplies; + QHash m_runningReplies; QList m_foundDevices; void setDiscovering(const bool &discovering); diff --git a/libmea-core/discovery/zeroconfdiscovery.cpp b/libmea-core/discovery/zeroconfdiscovery.cpp index 3f33711c..841e345a 100644 --- a/libmea-core/discovery/zeroconfdiscovery.cpp +++ b/libmea-core/discovery/zeroconfdiscovery.cpp @@ -49,6 +49,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) QString uuid; bool sslEnabled = false; QString serverName; + QString version; foreach (const QByteArray &key, entry.txt().keys()) { QPair txtRecord = qMakePair(key, entry.txt().value(key)); if (!sslEnabled && txtRecord.first == "sslEnabled") { @@ -60,34 +61,30 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) if (txtRecord.first == "name") { serverName = txtRecord.second; } + if (txtRecord.first == "serverVersion") { + version = txtRecord.second; + } } qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled; - DiscoveryDevice dev = m_discoveryModel->find(entry.ip()); - if (dev.uuid() == uuid && dev.nymeaRpcUrl().startsWith("nymeas") && !sslEnabled) { - // We already have this host and with a more secure configuration... skip this one... - return; - } - qDebug() << "Adding new found entry:" << entry.name() << entry.ip(); - dev.setUuid(uuid); - dev.setHostAddress(entry.ip()); - dev.setPort(entry.port()); - dev.setFriendlyName(serverName + " on " + entry.ip().toString()); - QHostAddress address = entry.ip(); - QString addressString; - if (address.protocol() == QAbstractSocket::IPv6Protocol) { - addressString = "[" + address.toString() + "]"; - } else { - addressString = address.toString(); - } - if (entry.type() == "_ws._tcp") { - dev.setWebSocketUrl(QString("%1://%2:%3").arg(sslEnabled ? "wss" : "ws").arg(addressString).arg(entry.port())); - } else { - dev.setNymeaRpcUrl(QString("%1://%2:%3").arg(sslEnabled ? "nymeas" : "nymea").arg(addressString).arg(entry.port())); - } - m_discoveryModel->addDevice(dev); -// DiscoveryDevice *dev = new DiscoveryDevice(); -// dev->setFriendlyName(entry.hostName()); + DiscoveryDevice* device = m_discoveryModel->find(uuid); + if (!device) { + device = new DiscoveryDevice(m_discoveryModel); + device->setUuid(uuid); + qDebug() << "Adding new host to model"; + m_discoveryModel->addDevice(device); + } + device->setHostAddress(entry.ip()); + device->setName(serverName); + device->setVersion(version); + PortConfig *portConfig = device->portConfigs()->find(entry.port()); + if (!portConfig) { + qDebug() << "Adding new port config"; + portConfig = new PortConfig(entry.port()); + device->portConfigs()->insert(portConfig); + } + portConfig->setProtocol(entry.type() == "_ws._tcp" ? PortConfig::ProtocolWebSocket : PortConfig::ProtocolNymeaRpc); + portConfig->setSslEnabled(sslEnabled); } #endif diff --git a/libmea-core/jsonrpc/jsonrpcclient.cpp b/libmea-core/jsonrpc/jsonrpcclient.cpp index 9c58b8b7..1221b784 100644 --- a/libmea-core/jsonrpc/jsonrpcclient.cpp +++ b/libmea-core/jsonrpc/jsonrpcclient.cpp @@ -224,7 +224,7 @@ void JsonRpcClient::sendRequest(const QVariantMap &request) { QVariantMap newRequest = request; newRequest.insert("token", m_token); - qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); +// qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson()); } @@ -259,7 +259,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data) // qWarning() << "Could not parse json data from mea" << data << error.errorString(); return; } - qDebug() << "received response" << m_receiveBuffer.left(splitIndex); +// qDebug() << "received response" << m_receiveBuffer.left(splitIndex); m_receiveBuffer = m_receiveBuffer.right(m_receiveBuffer.length() - splitIndex - 1); if (!m_receiveBuffer.isEmpty()) { staticMetaObject.invokeMethod(this, "dataReceived", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray())); diff --git a/libmea-core/libmea-core.h b/libmea-core/libmea-core.h index 52bd4807..fea4f30a 100644 --- a/libmea-core/libmea-core.h +++ b/libmea-core/libmea-core.h @@ -115,6 +115,9 @@ void registerQmlTypes() { qmlRegisterType(uri, 1, 0, "NymeaDiscovery"); qmlRegisterUncreatableType(uri, 1, 0, "DiscoveryModel", "Get it from NymeaDiscovery"); + qmlRegisterUncreatableType(uri, 1, 0, "DiscoveryDevice", "Get it from DiscoveryModel"); + qmlRegisterUncreatableType(uri, 1, 0, "PortConfigs", "Get it from DiscoveryDevice"); + qmlRegisterUncreatableType(uri, 1, 0, "PortConfig", "Get it from DiscoveryDevice"); qmlRegisterType(uri, 1, 0, "EventDescriptorParamsFilterModel"); diff --git a/mea/mea.pro b/mea/mea.pro index 02ff63e2..eb4ee70c 100644 --- a/mea/mea.pro +++ b/mea/mea.pro @@ -13,7 +13,7 @@ win32:Debug:LIBS += -L$$top_builddir/libmea-core/debug \ win32:Release:LIBS += -L$$top_builddir/libmea-core/release \ -L$$top_builddir/libnymea-common/release linux:!android:LIBS += -lavahi-client -lavahi-common - +PRE_TARGETDEPS += ../libmea-core HEADERS += \ stylecontroller.h diff --git a/mea/resources.qrc b/mea/resources.qrc index b344b4a6..8a0bf8e9 100644 --- a/mea/resources.qrc +++ b/mea/resources.qrc @@ -164,5 +164,7 @@ ui/AppSettingsPage.qml ui/images/stock_application.svg ui/delegates/ThingDelegate.qml + ui/images/network-secure.svg + ui/images/lock-broken.svg diff --git a/mea/ui/ConnectPage.qml b/mea/ui/ConnectPage.qml index 82296899..0d7147f9 100644 --- a/mea/ui/ConnectPage.qml +++ b/mea/ui/ConnectPage.qml @@ -1,7 +1,7 @@ -import QtQuick 2.5 -import QtQuick.Controls 2.1 -import QtQuick.Controls.Material 2.1 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Layouts 1.3 import Mea 1.0 import "components" @@ -24,9 +24,8 @@ Page { target: Engine.connection onVerifyConnectionCertificate: { print("verify cert!") - certDialog.issuerInfo = issuerInfo - certDialog.fingerprint = fingerprint - certDialog.open(); + var popup = certDialogComponent.createObject(app, {issuerInfo: issuerInfo, fingerprint: fingerprint}); + popup.open(); } onConnectionError: { pageStack.pop(root) @@ -118,34 +117,81 @@ Page { model: discovery.discoveryModel clip: true - delegate: ItemDelegate { + delegate: SwipeDelegate { width: parent.width height: app.delegateHeight objectName: "discoveryDelegate" + index - property string hostAddress: model.hostAddress - ColumnLayout { - anchors.fill: parent - anchors.margins: app.margins - Label { - text: model.name + contentItem: RowLayout { + ColumnLayout { + Layout.fillWidth: true + Label { + text: model.name + Layout.fillWidth: true + elide: Text.ElideRight + } + Label { + text: model.hostAddress + font.pixelSize: app.smallFont + } } - Label { - text: model.hostAddress - font.pixelSize: app.smallFont + ColorIcon { + Layout.fillHeight: true + Layout.preferredWidth: height + name: "../images/network-secure.svg" + visible: { + var discoveryDevice = discovery.discoveryModel.get(index); + for (var i = 0; i < discoveryDevice.portConfigs.count; i++) { + if (discoveryDevice.portConfigs.get(i).sslEnabled) { + return true; + } + } + return false; + } } } - onClicked: { - var url; - if (model.nymeaRpcUrl) { - url = model.nymeaRpcUrl; - } else { - url = model.webSocketUrl; - } - print("Should connect to", url) - Engine.connection.connect(url) + onClicked: { + var discoveryDevice = discovery.discoveryModel.get(index); + print("discoveryDevice:", discoveryDevice.name, discoveryDevice.uuid, discoveryDevice.hostAddress) + var usedConfigIndex = 0; + for (var i = 1; i < discoveryDevice.portConfigs.count; i++) { + var oldConfig = discoveryDevice.portConfigs.get(usedConfigIndex); + var newConfig = discoveryDevice.portConfigs.get(i); + + // prefer secure over insecure + if (!oldConfig.sslEnabled && newConfig.sslEnabled) { + usedConfigIndex = i; + continue; + } + if (oldConfig.sslEnabled && !newConfig.sslEnabled) { + continue; // discard new one as the one we already have is more secure + } + + // both options are new either secure or insecure, prefer nymearpc over websocket for less overhead + if (oldConfig.protocol === PortConfig.ProtocolWebSocket && newConfig.protocol === PortConfig.ProtocolNymeaRpc) { + usedConfigIndex = i; + } + } + Engine.connection.connect(discoveryDevice.toUrl(usedConfigIndex)) pageStack.push(connectingPage) } + + swipe.right: MouseArea { + height: parent.height + width: height + anchors.right: parent.right + ColorIcon { + anchors.fill: parent + anchors.margins: app.margins + name: "../images/info.svg" + } + onClicked: { + swipe.close() + var popup = infoDialog.createObject(app,{discoveryDevice: discovery.discoveryModel.get(index)}) + popup.open() + } + } + } Column { @@ -317,82 +363,231 @@ Page { } } - Dialog { - id: certDialog - width: Math.min(parent.width * .9, 400) - x: (parent.width - width) / 2 - y: (parent.height - height) / 2 - standardButtons: Dialog.Yes | Dialog.No + Component { + id: certDialogComponent - property var fingerprint - property var issuerInfo + Dialog { + id: certDialog + width: Math.min(parent.width * .9, 400) + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + standardButtons: Dialog.Yes | Dialog.No - ColumnLayout { - anchors { left: parent.left; right: parent.right; top: parent.top } - spacing: app.margins + property var fingerprint + property var issuerInfo - RowLayout { - Layout.fillWidth: true - spacing: app.margins - ColorIcon { - Layout.preferredHeight: app.iconSize * 2 - Layout.preferredWidth: height - name: "../images/dialog-warning-symbolic.svg" - color: app.guhAccent + ColumnLayout { + id: certLayout + anchors.fill: parent +// spacing: app.margins + + RowLayout { + Layout.fillWidth: true + spacing: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize * 2 + Layout.preferredWidth: height + name: "../images/dialog-warning-symbolic.svg" + color: app.guhAccent + } + + Label { + id: titleLabel + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: qsTr("Warning") + color: app.guhAccent + font.pixelSize: app.largeFont + } } Label { - id: titleLabel Layout.fillWidth: true wrapMode: Text.WordWrap - text: qsTr("Warning") - color: app.guhAccent - font.pixelSize: app.largeFont + text: qsTr("The authenticity of this %1 box cannot be verified.").arg(app.systemName) + } + + Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: qsTr("If this is the first time you connect to this box, this is expected. Once you trust a box, you should never see this message again for that one. If you see this message multiple times for the same box, something suspicious is going on!") + } + + ThinDivider {} + Item { + Layout.fillWidth: true + Layout.fillHeight: true + implicitHeight: certGridLayout.implicitHeight + Flickable { + anchors.fill: parent + contentHeight: certGridLayout.implicitHeight + clip: true + + GridLayout { + id: certGridLayout + columns: 2 + width: parent.width + + Repeater { + model: certDialog.issuerInfo + + Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: modelData + } + } + Label { + Layout.fillWidth: true + Layout.columnSpan: 2 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: qsTr("Fingerprint: ") + certDialog.fingerprint + } + } + } + } + + ThinDivider {} + + Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: qsTr("Do you want to trust this device?") + font.bold: true } } - Label { - Layout.fillWidth: true - wrapMode: Text.WordWrap - text: qsTr("The authenticity of this %1 box cannot be verified.").arg(app.systemName) + + onAccepted: { + Engine.connection.acceptCertificate(certDialog.fingerprint) } + } + } - Label { - Layout.fillWidth: true - wrapMode: Text.WordWrap - text: qsTr("If this is the first time you connect to this box, this is expected. Once you trust a box, you should never see this message again for that one. If you see this message multiple times for the same box, something suspicious is going on!") - } - GridLayout { - columns: 2 + Component { + id: infoDialog + Dialog { + id: dialog + width: Math.min(parent.width, contentGrid.implicitWidth) + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + modal: true + title: qsTr("Box information") - Repeater { - model: certDialog.issuerInfo + standardButtons: Dialog.Ok + + property var discoveryDevice: null + + header: Item { + implicitHeight: headerRow.height + app.margins * 2 + implicitWidth: parent.width + RowLayout { + id: headerRow + anchors { left: parent.left; right: parent.right; top: parent.top; margins: app.margins } + spacing: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize * 2 + Layout.preferredWidth: height + name: "../images/info.svg" + color: app.guhAccent + } Label { + id: titleLabel Layout.fillWidth: true - wrapMode: Text.WordWrap - text: modelData + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: dialog.title + color: app.guhAccent + font.pixelSize: app.largeFont } } } - Label { - Layout.fillWidth: true - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - text: qsTr("Fingerprint: ") + certDialog.fingerprint - } + GridLayout { + id: contentGrid + anchors.fill: parent + rowSpacing: app.margins + columns: 2 + Label { + text: "Name:" + } + Label { + text: dialog.discoveryDevice.name + Layout.fillWidth: true + elide: Text.ElideRight + } + Label { + text: "UUID:" + } + Label { + text: dialog.discoveryDevice.uuid + Layout.fillWidth: true + elide: Text.ElideRight + } + Label { + text: "Version:" + } + Label { + text: dialog.discoveryDevice.version + Layout.fillWidth: true + elide: Text.ElideRight + } + Label { + text: "IP Address:" + } + Label { + text: dialog.discoveryDevice.hostAddress + Layout.fillWidth: true + elide: Text.ElideRight + } + ThinDivider { Layout.columnSpan: 2 } + Label { + Layout.columnSpan: 2 + text: qsTr("Available connections") + } - Label { - Layout.fillWidth: true - wrapMode: Text.WordWrap - text: qsTr("Do you want to trust this device?") - font.bold: true - } - } + Flickable { + Layout.columnSpan: 2 + Layout.fillWidth: true + Layout.preferredHeight: 200 + contentHeight: contentColumn.implicitHeight + clip: true + ColumnLayout { + id: contentColumn + width: parent.width + Repeater { + model: dialog.discoveryDevice.portConfigs + ItemDelegate { + Layout.fillWidth: true + contentItem: RowLayout { + ColumnLayout { + Layout.fillWidth: true + Label { + text: qsTr("Port: %1").arg(model.port) + } + Label { + text: model.protocol === PortConfig.ProtocolNymeaRpc ? "nymea-rpc" : "websocket" + Layout.fillWidth: true + font.pixelSize: app.smallFont + } + } - onAccepted: { - Engine.connection.acceptCertificate(certDialog.fingerprint) + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + name: model.sslEnabled ? "../images/network-secure.svg" : "../images/lock-broken.svg" + color: model.sslEnabled ? app.guhAccent : "red" + } + } + onClicked: { + Engine.connection.connect(dialog.discoveryDevice.toUrl(index)) + } + } + } + } + } + } } } } diff --git a/mea/ui/images/lock-broken.svg b/mea/ui/images/lock-broken.svg new file mode 100644 index 00000000..e5804165 --- /dev/null +++ b/mea/ui/images/lock-broken.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/mea/ui/images/network-secure.svg b/mea/ui/images/network-secure.svg new file mode 100644 index 00000000..d7cf5385 --- /dev/null +++ b/mea/ui/images/network-secure.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + +