// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #include #include #include struct Configuration { QString topic; QByteArray content; quint8 qos; bool retain{false}; bool useEncryption{false}; #ifndef QT_NO_SSL QSslConfiguration sslConfiguration; #endif }; QMqttClient *createClientWithConfiguration(QCoreApplication *app, Configuration *msg, bool publish) { QCommandLineParser parser; if (publish) parser.setApplicationDescription("Qt MQTT publish tool"); else parser.setApplicationDescription("Qt MQTT subscription tool"); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption optionDebug("d", QLatin1String("Enable debug messages / logging categories")); parser.addOption(optionDebug); QCommandLineOption optionFile("f", QLatin1String("Specify the content of a file as message."), QLatin1String("filename")); if (publish) parser.addOption(optionFile); QCommandLineOption optionClientId("i", QLatin1String("Specify a client ID. Defaults to random value."), QLatin1String("clientid")); parser.addOption(optionClientId); QCommandLineOption optionKeepAlive("k", QLatin1String("Specify the keep-alive value in seconds."), QLatin1String("keepAlive")); parser.addOption(optionKeepAlive); QCommandLineOption optionMessageContent("m", QLatin1String("Specify the message content. Defaults to" " a null message."), QLatin1String("messageContent")); if (publish) parser.addOption(optionMessageContent); QCommandLineOption optionPassword("P", QLatin1String("Provide a password."), QLatin1String("password")); parser.addOption(optionPassword); QCommandLineOption optionPort("p", QLatin1String("Network port to connect to. Defaults to 1883."), QLatin1String("hostPort"), QLatin1String("1883")); parser.addOption(optionPort); QCommandLineOption optionQos("q", QLatin1String("Quality of service level to use for all messages." "Defaults to 0."), QLatin1String("qos"), QLatin1String("0")); parser.addOption(optionQos); QCommandLineOption optionRetain("r", QLatin1String("Specify the retain flag for a message.")); if (publish) parser.addOption(optionRetain); QCommandLineOption optionHost("s", QLatin1String("MQTT server to connect to. Defaults to localhost."), QLatin1String("hostName"), QLatin1String("localhost")); parser.addOption(optionHost); QCommandLineOption optionMessageTopic("t", QLatin1String("Specify the message topic."), QLatin1String("messageTopic")); parser.addOption(optionMessageTopic); QCommandLineOption optionUser("u", QLatin1String("Provide a username."), QLatin1String("username")); parser.addOption(optionUser); QCommandLineOption optionVersion("V", QLatin1String("Specify the protocol version. Options are " "mqtt31, mqtt311, mqtt5. Defaults to mqtt311."), QLatin1String("protocolVersion"), QLatin1String("mqtt311")); parser.addOption(optionVersion); QCommandLineOption optionCaFile("cafile", QLatin1String("Specify a file containing trusted CA " "certificates to enable encrypted communication."), QLatin1String("cafile")); parser.addOption(optionCaFile); QCommandLineOption optionCaPath("capath", QLatin1String("Specify a directory containing trusted CA " "certificates to enable encrypted communication."), QLatin1String("capath")); parser.addOption(optionCaPath); parser.process(*app); auto *client = new QMqttClient(app); client->setHostname(parser.value(optionHost)); bool ok = true; quint16 port = static_cast(parser.value(optionPort).toInt(&ok)); if (!ok) { qWarning() << "Invalid port specified:" << parser.value(optionPort); return nullptr; } client->setPort(port); if (parser.isSet(optionUser)) client->setUsername(parser.value(optionUser)); if (parser.isSet(optionPassword)) client->setPassword(parser.value(optionPassword)); if (parser.isSet(optionClientId)) client->setClientId(parser.value(optionClientId)); if (parser.isSet(optionVersion)) { const QString version = parser.value(optionVersion); if (version == QLatin1String("mqtt31")) client->setProtocolVersion(QMqttClient::MQTT_3_1); else if (version == QLatin1String("mqtt311")) client->setProtocolVersion(QMqttClient::MQTT_3_1_1); else if (version == QLatin1String("mqtt5")) client->setProtocolVersion(QMqttClient::MQTT_5_0); else { qWarning() << "Invalid protocol version specified:" << version; return nullptr; } } if (parser.isSet(optionCaFile) || parser.isSet(optionCaPath)) { #ifdef QT_NO_SSL qWarning() << "Qt has not been compiled with SSL support."; return nullptr; #else QList fileNames; if (parser.isSet(optionCaFile)) fileNames.append(parser.value(optionCaFile)); if (parser.isSet(optionCaPath)) { QFileInfo path(parser.value(optionCaPath)); if (!path.isDir()) { qWarning() << "Specified capath is not a directory"; return nullptr; } auto entries = QDir(parser.value(optionCaPath)).entryInfoList(QDir::Files | QDir::NoDotAndDotDot); for (auto entry : entries) fileNames.append(entry.absoluteFilePath()); } if (fileNames.isEmpty()) { qWarning() << "No certificate file found."; return nullptr; } QList defaultCerts; for (auto it : fileNames) { auto certificates = QSslCertificate::fromPath(it); if (certificates.isEmpty() && parser.isSet(optionDebug)) qWarning() << "File " << it << " does not contain any certificates"; defaultCerts.append(certificates); } if (defaultCerts.isEmpty()) { qWarning() << "No certificate could be loaded."; return nullptr; } msg->sslConfiguration.setCaCertificates(defaultCerts); msg->useEncryption = true; #endif } if (parser.isSet(optionDebug)) QLoggingCategory::setFilterRules(QLatin1String("qt.mqtt.*=true")); msg->qos = static_cast(parser.value(optionQos).toInt(&ok)); if (!ok || msg->qos > 2) { qWarning() << "Invalid quality of service for message specified:" << msg->qos; return nullptr; } if (parser.isSet(optionKeepAlive)) { const quint16 keep = static_cast(parser.value(optionKeepAlive).toUInt(&ok)); if (!ok) { qWarning() << "Invalid keep alive value specified"; return nullptr; } client->setKeepAlive(keep); } if (publish) { if (parser.isSet(optionFile) && parser.isSet(optionMessageContent)) { qWarning() << "You cannot specify a file and a text as message."; return nullptr; } if (parser.isSet(optionFile)) { QFile file(parser.value(optionFile)); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Could not open specified file for reading."; return nullptr; } msg->content = file.readAll(); file.close(); } if (parser.isSet(optionMessageContent)) msg->content = parser.value(optionMessageContent).toUtf8(); } if (!parser.isSet(optionMessageTopic)) { qWarning() << "You must specify a topic to publish a message."; return nullptr; } msg->topic = parser.value(optionMessageTopic); if (publish && !QMqttTopicName(msg->topic).isValid()) { qWarning() << "The specified message topic is invalid."; return nullptr; } if (!publish && !QMqttTopicFilter(msg->topic).isValid()) { qWarning() << "The specified subscription topic is invalid."; return nullptr; } if (publish && parser.isSet(optionRetain)) msg->retain = true; // Output: qInfo() << "Client configuration:"; qInfo() << " Host:" << client->hostname() << " Port:" << client->port() << " Protocol:" << client->protocolVersion(); qInfo() << " Username:" << client->username() << " Password:" << !client->password().isEmpty(); qInfo() << " Client ID:" << client->clientId() << "Keep Alive:" << client->keepAlive(); return client; }