diff --git a/libnymea-core/hardware/network/mqtt/mqttproviderimplementation.cpp b/libnymea-core/hardware/network/mqtt/mqttproviderimplementation.cpp index dc6d8646..9bc0ab5b 100644 --- a/libnymea-core/hardware/network/mqtt/mqttproviderimplementation.cpp +++ b/libnymea-core/hardware/network/mqtt/mqttproviderimplementation.cpp @@ -47,17 +47,41 @@ MqttProviderImplementation::MqttProviderImplementation(MqttBroker *broker, QObje connect(broker, &MqttBroker::publishReceived, this, &MqttProviderImplementation::onPublishReceived); } +MqttChannel *MqttProviderImplementation::createChannel(const QHostAddress &clientAddress, const QStringList &topicPrefixList) +{ + QString clientId; + // Generate a clientId that hasn't been used yet. + do { + clientId = QUuid::createUuid().toString().remove(QRegExp("[{}-]")).left(16); + } while (m_createdChannels.contains(clientId)); + + return createChannel(clientId, clientAddress, topicPrefixList); +} + MqttChannel *MqttProviderImplementation::createChannel(const QString &clientId, const QHostAddress &clientAddress, const QStringList &topicPrefixList) +{ + QString username = QUuid::createUuid().toString().remove(QRegExp("[{}-]")).left(16); + QString password = QUuid::createUuid().toString().remove(QRegExp("[{}-]")).left(16); + + return createChannel(clientId, username, password, clientAddress, topicPrefixList); +} + +MqttChannel *MqttProviderImplementation::createChannel(const QString &clientId, const QString &username, const QString &password, const QHostAddress &clientAddress, const QStringList &topicPrefixList) { if (m_broker->configurations().isEmpty()) { qCWarning(dcMqtt) << "MQTT broker not running. Cannot create a channel for thing" << clientId; return nullptr; } + if (m_createdChannels.contains(clientId)) { + qCWarning(dcMqtt()) << "ClientId" << clientId << "already in use. Cannot create channel."; + return nullptr; + } + MqttChannelImplementation* channel = new MqttChannelImplementation(); channel->m_clientId = clientId; - channel->m_username = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - channel->m_password = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + channel->m_username = username; + channel->m_password = password; if (!topicPrefixList.isEmpty()) { channel->m_topicPrefixList = topicPrefixList; } else { diff --git a/libnymea-core/hardware/network/mqtt/mqttproviderimplementation.h b/libnymea-core/hardware/network/mqtt/mqttproviderimplementation.h index 78a6f53d..aad46127 100644 --- a/libnymea-core/hardware/network/mqtt/mqttproviderimplementation.h +++ b/libnymea-core/hardware/network/mqtt/mqttproviderimplementation.h @@ -44,7 +44,9 @@ class MqttProviderImplementation : public MqttProvider public: explicit MqttProviderImplementation(MqttBroker *broker, QObject *parent = nullptr); + MqttChannel* createChannel(const QHostAddress &clientAddress, const QStringList &topicPrefixList = QStringList()) override; MqttChannel* createChannel(const QString &clientId, const QHostAddress &clientAddress, const QStringList &topicPrefixList = QStringList()) override; + MqttChannel* createChannel(const QString &clientId, const QString &username, const QString &password, const QHostAddress &clientAddress, const QStringList &topicPrefixList = QStringList()) override; void releaseChannel(MqttChannel* channel) override; MqttClient* createInternalClient(const QString &clientId) override; diff --git a/libnymea/network/mqtt/mqttprovider.cpp b/libnymea/network/mqtt/mqttprovider.cpp index 49dc15ca..6257705a 100644 --- a/libnymea/network/mqtt/mqttprovider.cpp +++ b/libnymea/network/mqtt/mqttprovider.cpp @@ -154,15 +154,61 @@ \sa HardwareResource, HardwareManager::mqttProvider() */ +/*! \fn MqttChannel *MqttProvider::createChannel(const QHostAddress &clientAddress, const QString &topicPrefix); + Creates a new MQTT channel on the internal broker. The returned channel will have the required details for the + client device to connect to the broker. A temporaray clientId/user/password combination will be created and + clients connecting to the broker with those credentials will have access to subscribe and post to # within the + given \a topicPrefixList. + + The given \a clientAddress will be matched against available MQTT servers in the system and a proper server address + will be returned in the MqttChannel object. The client should be configured to connect to this server address. + + \a topicPrefixList will be used to generate the policy for this MQTT client by appending "/#" to it. This will allow the + client to publish and subscribe to topics within "/#". See The MQTT specification on topic filters for more details. + It is good practice to isolate clients as much as possible the topics should be as restrictive as possible to avoid devices + snooping in on other things on the MQTT broker. If no topicPrefix is provided, a default of "" Id is generated, + resulting in a policy of "/#". At this point it is not allowed for plugins to publish/subscribe to # or $ topics. + + \sa releaseChannel(MqttChannel *channel) +*/ + /*! \fn MqttChannel *MqttProvider::createChannel(const QString &clientId, const QHostAddress &clientAddress, const QString &topicPrefix); Creates a new MQTT channel on the internal broker. The returned channel will have the required details for the client device to connect to the broker. A temporaray user/password combination will be created and clients connecting to the broker with those credentials will have access to subscribe and post to # within the given \a topicPrefixList. + \a clientId must be unique within the system or the channel creation will fail. + + The given \a clientAddress will be matched against available MQTT servers in the system and a proper server address + will be returned in the MqttChannel object. The client should be configured to connect to this server address. + + \a topicPrefixList will be used to generate the policy for this MQTT client by appending "/#" to it. This will allow the + client to publish and subscribe to topics within "/#". See The MQTT specification on topic filters for more details. + It is good practice to isolate clients as much as possible the topics should be as restrictive as possible to avoid devices + snooping in on other things on the MQTT broker. If no topicPrefix is provided, a default of "" Id is generated, + resulting in a policy of "/#". At this point it is not allowed for plugins to publish/subscribe to # or $ topics. + + \sa releaseChannel(MqttChannel *channel) +*/ + +/*! \fn MqttChannel *MqttProvider::createChannel(const QString &clientId, const QString &username, const QString &password, const QHostAddress &clientAddress, const QString &topicPrefix); + Creates a new MQTT channel on the internal broker. The returned channel will have the required details for the + client device to connect to the broker. Clients connecting to the broker with those credentials will have access to + subscribe and post to # within the given \a topicPrefixList. + \a clientId must be unique within the system or the channel creation will fail. If a mqtt client allows to configure the clientId, using it's deviceId is likely a good idea. + The given \a username and \a password will be used to create the policy for the client. Note: While it is technically + possible and allowed to use an empty user/password combination, a plugin developer should not do so unless the client + is not capable of providing login information. In most cases a plugin developer should use the overloaded method + that autogenerates a user and password combination instead. This method might be useful if a client cannot deal with + the auto generated credentials for whatever reason and a plugin requires to override them. + + The given \a clientAddress will be matched against available MQTT servers in the system and a proper server address + will be returned in the MqttChannel object. The client should be configured to connect to this server address. + \a topicPrefixList will be used to generate the policy for this MQTT client by appending "/#" to it. This will allow the client to publish and subscribe to topics within "/#". See The MQTT specification on topic filters for more details. It is good practice to isolate clients as much as possible the topics should be as restrictive as possible to avoid devices diff --git a/libnymea/network/mqtt/mqttprovider.h b/libnymea/network/mqtt/mqttprovider.h index 20c65203..2589cf1d 100644 --- a/libnymea/network/mqtt/mqttprovider.h +++ b/libnymea/network/mqtt/mqttprovider.h @@ -46,7 +46,9 @@ class MqttProvider : public HardwareResource public: explicit MqttProvider(QObject *parent = nullptr); + virtual MqttChannel* createChannel(const QHostAddress &clientAddress, const QStringList &topicPrefixList = QStringList()) = 0; virtual MqttChannel* createChannel(const QString &clientId, const QHostAddress &clientAddress, const QStringList &topicPrefixList = QStringList()) = 0; + virtual MqttChannel* createChannel(const QString &clientId, const QString &username, const QString &password, const QHostAddress &clientAddress, const QStringList &topicPrefixList = QStringList()) = 0; virtual void releaseChannel(MqttChannel *channel) = 0; virtual MqttClient* createInternalClient(const QString &clientId) = 0; diff --git a/nymea.pro b/nymea.pro index d5d719b7..b20daaa7 100644 --- a/nymea.pro +++ b/nymea.pro @@ -8,7 +8,7 @@ JSON_PROTOCOL_VERSION_MAJOR=5 JSON_PROTOCOL_VERSION_MINOR=6 JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" LIBNYMEA_API_VERSION_MAJOR=7 -LIBNYMEA_API_VERSION_MINOR=0 +LIBNYMEA_API_VERSION_MINOR=1 LIBNYMEA_API_VERSION_PATCH=0 LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}"