summaryrefslogtreecommitdiffstats
path: root/tradeshow/knx-demo/3d-Alexa-knx-demo/qupnprootdevice.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tradeshow/knx-demo/3d-Alexa-knx-demo/qupnprootdevice.cpp')
-rw-r--r--tradeshow/knx-demo/3d-Alexa-knx-demo/qupnprootdevice.cpp459
1 files changed, 459 insertions, 0 deletions
diff --git a/tradeshow/knx-demo/3d-Alexa-knx-demo/qupnprootdevice.cpp b/tradeshow/knx-demo/3d-Alexa-knx-demo/qupnprootdevice.cpp
new file mode 100644
index 0000000..933fe40
--- /dev/null
+++ b/tradeshow/knx-demo/3d-Alexa-knx-demo/qupnprootdevice.cpp
@@ -0,0 +1,459 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qupnprootdevice.h"
+#include <QCryptographicHash>
+#include <QNetworkDatagram>
+
+// 239.255.255.250:1900 is the IANA reserved UPnP multicast address.
+const QHostAddress QUPnPRootDevice::multicastAddress("239.255.255.250");
+const quint16 QUPnPRootDevice::multicastPort = 1900;
+
+QList<QUPnPRootDevice*> QUPnPRootDevice::allUPnPRootDevices;
+
+QUPnPRootDevice::QUPnPRootDevice(QObject *parent, const QString &name, const QString &searchTarget)
+ : QMinimalHttpServer(parent)
+{
+ allUPnPRootDevices.push_back(this);
+
+ static bool didRandomize = false;
+ if (!didRandomize)
+ qsrand(QTime::currentTime().msec());
+ didRandomize = true;
+
+ foreach (const QHostAddress &address, QNetworkInterface::allAddresses())
+ if (address.protocol() == QAbstractSocket::IPv4Protocol
+ && address != QHostAddress(QHostAddress::LocalHost))
+ this->localIpAddress = address.toString();
+
+ this->searchTarget = searchTarget;
+ this->product = name;
+ this->uuid = createUuid(name);
+ connect(&udpSocket, &QUdpSocket::readyRead, this, &QUPnPRootDevice::onUdpDataReceived);
+}
+
+QUPnPRootDevice::~QUPnPRootDevice()
+{
+ stopListening();
+ allUPnPRootDevices.removeAll(this);
+}
+
+void QUPnPRootDevice::stopAllUPnPRootDevices()
+{
+ #ifdef QT_DEBUG
+ qDebug() << "QUPnPRootDevice::stopAllUPnPRootDevices";
+ #endif
+ for (QUPnPRootDevice* dev : allUPnPRootDevices)
+ dev->stopListening();
+}
+
+QString QUPnPRootDevice::getUPnPxml()
+{
+ /*
+ <deviceType>urn:schemas-upnp-org:device:DimmableLight:1</deviceType>
+ <modelDescription>UPnP-to-KNX Bridge</modelDescription>
+ <modelName>UPnP-to-KNX Bridge</modelName>
+ <modelNumber>0.1</modelNumber>
+ <modelURL>https://qt.io</modelURL>
+ <serialNumber>0.1</serialNumber>
+ <presentationURL/>
+ */
+
+ QString upnp_xml = QStringLiteral(
+ "<?xml version=\"1.0\"?>"
+ "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
+ "<specVersion>"
+ "<major>1</major>"
+ "<minor>0</minor>"
+ "</specVersion>"
+ "<device>"
+ "<deviceType>upnp:rootdevice</deviceType>"
+ "<friendlyName>") + this->product + "</friendlyName>"
+ "<manufacturer>" + this->manufacturer + "</manufacturer>"
+ "<manufacturerURL>" + this->manufacturerURL + "</manufacturerURL>"
+ "<modelName>" + this->product + "</modelName>"
+ "<modelNumber>0.1</modelNumber>"
+ "<UDN>uuid:" + this->uuid + "</UDN>";
+
+ // icons
+ if (icons.size() > 0) {
+ upnp_xml += "<iconList>";
+ for (QString iconKey : icons.keys()) {
+ QImage icon = icons[iconKey];
+ upnp_xml += QStringLiteral(
+ "<icon>") +
+ "<mimetype>" + httpFileMimeTypes[iconKey] + "</mimetype>"
+ "<width>" + QString::number(icon.width()) + "</width>"
+ "<height>" + QString::number(icon.height()) + "</height>"
+ "<depth>" + QString::number(icon.depth()) + "</depth>"
+ "<url>" + iconKey + "</url>"
+ "</icon>";
+ }
+ upnp_xml += "</iconList>";
+ }
+
+ // services
+ if (services.size() > 0) {
+ upnp_xml += "<serviceList>";
+ for (auto service : services)
+ upnp_xml += service->upnpServiceListEntry();
+ upnp_xml += "</serviceList>";
+ }
+
+ upnp_xml +=
+ "</device>"
+ "</root>";
+
+ return upnp_xml;
+}
+
+QUPnPService *QUPnPRootDevice::getService(const QString &urlUpper)
+{
+ QString serviceKey;
+ if (urlUpper.startsWith("/") && urlUpper.endsWith("/CONTROL"))
+ serviceKey = urlUpper.mid(1, urlUpper.size() - 9);
+ else if (urlUpper.startsWith("/") && urlUpper.endsWith("/EVENTS"))
+ serviceKey = urlUpper.mid(1, urlUpper.size() - 8);
+ else if (urlUpper.startsWith("/") && urlUpper.endsWith("/SCPD.XML"))
+ serviceKey = urlUpper.mid(1, urlUpper.size() - 10);
+
+ if (serviceKey != "" && services.contains(serviceKey))
+ return services[serviceKey];
+ return nullptr;
+}
+
+void QUPnPRootDevice::onUdpDataReceived()
+{
+ QHostAddress senderAddress;
+ quint16 senderPort;
+ while (udpSocket.hasPendingDatagrams()) {
+ //udpSocket.readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
+ QNetworkDatagram datagram = udpSocket.receiveDatagram(int(udpSocket.pendingDatagramSize()));
+ senderAddress = datagram.senderAddress();
+ senderPort = datagram.senderPort();
+#ifdef QT_DEBUG
+ QString sdatagram = QString::fromUtf8(datagram.data());
+ if (!sdatagram.contains("ST: urn:dial-multiscreen-org:")
+ && !sdatagram.contains("ST:urn:schemas-upnp-org:device:InternetGatewayDevice:")
+ && !sdatagram.contains("ST: urn:schemas-upnp-org:device:InternetGatewayDevice:")
+ && !sdatagram.contains("ST:urn:schemas-upnp-org:device:MediaRenderer:")
+ && !sdatagram.contains("ST:urn:schemas-upnp-org:device:MediaServer:")
+ && !sdatagram.contains("NOTIFY * HTTP/")
+ //&& !sdatagram.contains("")
+ ) {
+ qDebug() << "QUPnPRootDevice::onUdpDataReceived " << sdatagram;
+ qDebug() << "QUPnPRootDevice::onUdpDataReceived ip and port:" << senderAddress << senderPort;
+ }
+ #endif
+ processUdpData(QString::fromUtf8(datagram.data()), senderAddress, senderPort);
+ }
+}
+
+void QUPnPRootDevice::processUdpData(const QString &data, QHostAddress senderAddress, quint16 senderPort)
+{
+ if (!data.startsWith("M-SEARCH")
+ || !data.contains("MAN: \"ssdp:discover\"", Qt::CaseInsensitive)
+ || ( !data.contains("ST: ssdp:all", Qt::CaseInsensitive)
+ && !data.contains("ST: upnp:rootdevice", Qt::CaseInsensitive)
+ && !data.contains(QStringLiteral("ST: ") + this->searchTarget, Qt::CaseInsensitive)))
+ return;
+
+ // SSDP Requests should not be answered immediately, because the sender of the
+ // request might get many results and might run on weak hardware.
+ int responseDelay = 1000; // milliseconds
+ QStringList lines = data.split("\r\n");
+ for (QString line : lines) {
+ if (line.startsWith("MX:", Qt::CaseInsensitive))
+ responseDelay = 1000 * line.mid(3).trimmed().toInt();
+ }
+ if (responseDelay > 0)
+ responseDelay = qrand() % responseDelay;
+
+ QTimer::singleShot(responseDelay, [this, senderAddress, senderPort](){
+ QString message = QStringLiteral(
+ "HTTP/1.1 200 OK\r\n"
+ "LOCATION: http://") + localIpAddress + ":" + QString::number(tcpServer.serverPort()) + "/upnp.xml\r\n"
+ "EXT:\r\n"
+ "USN: uuid:" + this->uuid + "::" + this->searchTarget + "\r\n"
+ "SERVER: " + QSysInfo::productType() + "/" + QSysInfo::productVersion() + " UPnP/1.0 " + product + "/" + version + "\r\n"
+ "CACHE-CONTROL: max-age=86400\r\n"
+ "ST: " + this->searchTarget + "\r\n"
+ "DATE: " + DateRfc7231() + "\r\n"
+ "CONTENT-LENGTH: 0\r\n"
+ "\r\n";
+
+ QUdpSocket responseSocket;
+ #ifdef QT_DEBUG
+ qDebug() << "QUPnPRootDevice::processUdpData senderIp:" << senderAddress << message;
+ #endif
+ responseSocket.writeDatagram(message.toUtf8(), senderAddress, senderPort);
+ });
+}
+
+bool QUPnPRootDevice::processHttpGetRequest(QString urlUpper, QString &responseHead, QByteArray &responseBody)
+{
+ if (urlUpper == "/UPNP.XML")
+ {
+ responseBody = this->getUPnPxml().toUtf8();
+ responseHead += "content-type: text/xml; charset=utf-8\r\n";
+ return true;
+ }
+ else
+ {
+ QUPnPService *service = getService(urlUpper);
+ if (service) {
+ responseBody = service->getScpd().toUtf8();
+ responseHead += QStringLiteral(
+ "content-type: text/xml; charset=utf-8\r\n"
+ "server: ") + QSysInfo::productType() + "/" + QSysInfo::productVersion() + " UPnP/1.0 " + product + "/" + version + "\r\n";
+ return true;
+ }
+ }
+ return QMinimalHttpServer::processHttpGetRequest(urlUpper, responseHead, responseBody);
+}
+
+int QUPnPRootDevice::processHttpPostRequest(QString urlUpper, QByteArray &postBody, QString &responseHead, QByteArray &responseBody)
+{
+ #ifdef QT_DEBUG
+ qDebug() << "QUPnPRootDevice::processHttpPostRequest";
+ qDebug() << postBody;
+ #endif
+
+ QUPnPService *service = getService(urlUpper);
+ if (!service) {
+ responseHead = "HTTP/1.0 404 Not Found\r\n";
+ return 404;
+ }
+
+ QDomDocument doc;
+ QString errorMsg; int errorLine; int errorColumn;
+ if (!doc.setContent(postBody, true, &errorMsg, &errorLine, &errorColumn)) {
+ #ifdef QT_DEBUG
+ qDebug() << "doc.setContent failed (" << errorLine << ":" << errorColumn << ") " << errorMsg;
+ #endif
+ responseHead = "HTTP/1.0 400 Bad Request\r\n";
+ return 400;
+ }
+
+ QDomElement envelope = doc.documentElement();
+ QDomNodeList bodies = envelope.elementsByTagNameNS("http://schemas.xmlsoap.org/soap/envelope/", "Body");
+ for (int bodyIdx = 0; bodyIdx < bodies.size(); bodyIdx++) {
+
+ QString actionName;
+
+ // iterate the RPC calls in the envelope
+ QDomNodeList RPCs = bodies.at(bodyIdx).toElement().childNodes();
+ for (int RPCIdx = 0; RPCIdx < RPCs.size(); RPCIdx++) {
+ if (!RPCs.at(RPCIdx).isElement())
+ continue;
+
+ actionName = RPCs.at(RPCIdx).toElement().localName();
+
+ // collect parameters
+ QMap<QString, QString> actionParameters;
+ QDomNodeList params = RPCs.at(RPCIdx).toElement().childNodes();
+ for (int paramIdx = 0; paramIdx < params.size(); paramIdx++) {
+ if (!params.at(paramIdx).isElement())
+ continue;
+
+ QString paramName = params.at(paramIdx).toElement().localName();
+ QString paramValue = "";
+ // get paramValue
+ QDomNodeList paramChildren = params.at(paramIdx).toElement().childNodes();
+ for (int paramChildIdx = 0; paramChildIdx < paramChildren.size(); paramChildIdx++)
+ if (paramChildren.at(paramChildIdx).isText())
+ paramValue = paramChildren.at(paramChildIdx).toText().data();
+
+ actionParameters[paramName] = paramValue;
+ }
+
+ // execute the RPC call
+ return service->executeRemoteProcedureCall(actionName, actionParameters, responseHead, responseBody);
+
+ }
+ }
+
+ return QMinimalHttpServer::processHttpPostRequest(urlUpper, postBody, responseHead, responseBody);
+}
+
+int QUPnPRootDevice::processHttpSubscribeRequest(QString urlUpper, QStringList &requestLines, QString &responseHead, QByteArray &responseBody)
+{
+ #ifdef QT_DEBUG
+ qDebug() << "QUPnPRootDevice::processHttpSubscribeRequest";
+ #endif
+
+ QUPnPService *service = getService(urlUpper);
+ if (!service) {
+ responseHead = "HTTP/1.0 404 Not Found\r\n";
+ return 404;
+ }
+
+ return service->processHttpSubscribeRequest(requestLines, responseHead, responseBody);
+}
+
+int QUPnPRootDevice::processHttpUnsubscribeRequest(QString urlUpper, QStringList &requestLines, QString &responseHead, QByteArray &responseBody)
+{
+ #ifdef QT_DEBUG
+ qDebug() << "QUPnPRootDevice::processHttpUnsubscribeRequest";
+ #endif
+
+ QUPnPService *service = getService(urlUpper);
+ if (!service) {
+ responseHead = "HTTP/1.0 404 Not Found\r\n";
+ return 404;
+ }
+
+ return service->processHttpUnsubscribeRequest(requestLines, responseHead, responseBody);
+}
+
+QString QUPnPRootDevice::createUuid(const QString &base)
+{
+ QString uuid;
+ if (base == QString()) {
+ uuid = QUuid::createUuid().toString(); // random uuid
+ uuid = uuid.mid(1, uuid.length()-2); // remove leading { and trailing }
+ } else {
+ uuid = QString(QCryptographicHash::hash(base.toUtf8(),QCryptographicHash::Sha1).toHex());
+ }
+
+ return uuid;
+}
+
+void QUPnPRootDevice::startListening(quint16 port)
+{
+ if (isListening())
+ return;
+
+ QMinimalHttpServer::startListening(port);
+
+ udpSocket.bind(QHostAddress::AnyIPv4, multicastPort, QUdpSocket::ReuseAddressHint);
+ udpSocket.joinMulticastGroup(multicastAddress);
+
+ // notify network via multicast
+ QString message = QStringLiteral(
+ "NOTIFY * HTTP/1.1\r\n"
+ "HOST: ") + multicastAddress.toString() + ":" + QString::number(multicastPort) + "\r\n"
+ "CACHE-CONTROL: max-age=86400\r\n"
+ "LOCATION: http://" + localIpAddress + ":" + QString::number(tcpServer.serverPort()) + "/upnp.xml\r\n"
+ "SERVER: " + QSysInfo::productType() + "/" + QSysInfo::productVersion() + " UPnP/1.0 " + product + "/" + version + "\r\n"
+ "NTS: ssdp:alive\r\n"
+ "NT: " + this->searchTarget + "\r\n"
+ "USN: uuid:" + this->uuid + "::" + this->searchTarget + "\r\n"
+ "\r\n";
+ QUdpSocket aliveSocket;
+ aliveSocket.setSocketOption(QAbstractSocket::MulticastTtlOption, 4);
+ #ifdef QT_DEBUG
+ qDebug() << "QUPnPRootDevice::startListening " << message;
+ #endif
+ aliveSocket.writeDatagram(message.toUtf8(), multicastAddress, multicastPort);
+}
+
+void QUPnPRootDevice::stopListening()
+{
+ if (!isListening())
+ return;
+
+ // notify network via multicast
+ QString message = QStringLiteral(
+ "NOTIFY * HTTP/1.1\r\n"
+ "HOST: ") + multicastAddress.toString() + ":" + QString::number(multicastPort) + "\r\n"
+ "NT: " + this->searchTarget + "\r\n"
+ "NTS: ssdp:byebye\r\n"
+ "USN: uuid:" + this->uuid + "::" + this->searchTarget + "\r\n"
+ "SERVER: " + QSysInfo::productType() + "/" + QSysInfo::productVersion() + " UPnP/1.0 " + product + "/" + version + "\r\n"
+ "DATE: " + DateRfc7231() + "\r\n"
+ "CONTENT-LENGTH: 0\r\n"
+ "\r\n";
+ QUdpSocket byebyeSocket;
+ byebyeSocket.setSocketOption(QAbstractSocket::MulticastTtlOption, 4);
+ #ifdef QT_DEBUG
+ qDebug() << "QUPnPRootDevice::stopListening " << message;
+ #endif
+ byebyeSocket.writeDatagram(message.toUtf8(), multicastAddress, multicastPort);
+
+ udpSocket.leaveMulticastGroup(multicastAddress);
+ udpSocket.close();
+ QMinimalHttpServer::stopListening();
+}
+
+void QUPnPRootDevice::addIcon(const QString &filename)
+{
+ QFileInfo info(filename);
+ QString nameonly = QString("/") + info.fileName().toUpper();
+
+ QFile file(filename);
+ file.open(QFile::ReadOnly);
+ QByteArray data = file.readAll();
+ file.close();
+ this->addFile(nameonly, data);
+
+ QImage img;
+ img.load(filename);
+ icons[nameonly] = img;
+
+ if (isListening()) {
+ stopListening();
+ startListening();
+ }
+}
+
+void QUPnPRootDevice::addService(QUPnPService *service)
+{
+ QString id = service->identifier.toUpper();
+ if (this->services.contains(id))
+ return;
+ service->rootdevice = this;
+ this->services[id] = service;
+
+ if (isListening()) {
+ stopListening();
+ startListening();
+ }
+}