├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── readme.md ├── src ├── CMakeLists.txt ├── cmake │ └── config.cmake.in ├── logging_p.h ├── qmqtt_global.h ├── qmqttclient.cpp ├── qmqttclient.h ├── qmqttclient_p.h ├── qmqttcontrolpacket.cpp ├── qmqttcontrolpacket_p.h ├── qmqttnetworkrequest.cpp ├── qmqttnetworkrequest.h ├── qmqttpacketparser.cpp ├── qmqttpacketparser_p.h ├── qmqttprotocol.h ├── qmqttprotocol.qdoc ├── qmqttwill.cpp ├── qmqttwill.h └── qmqttwill_p.h └── tests ├── CMakeLists.txt ├── auto ├── CMakeLists.txt └── mqttclient │ ├── CMakeLists.txt │ ├── tst_qmqttcontrolpacket.cpp │ ├── tst_qmqttnetworkrequest.cpp │ └── tst_qmqttprotocol.cpp └── cmake └── Modules └── AddQtTest.cmake /.appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | 3 | platform: 4 | - x64 5 | 6 | configuration: 7 | - Debug 8 | - Release 9 | 10 | environment: 11 | matrix: 12 | - SHARED: ON 13 | - SHARED: OFF 14 | 15 | VS_VERSION: Visual Studio 15 2017 16 | QT_VERSION: 5.11 17 | QT_COMPILED: msvc2017 18 | 19 | install: 20 | - set VS_FULL=%VS_VERSION% Win64 21 | - set QTDIR=C:\Qt\%QT_VERSION%\%QT_COMPILED%_64 22 | - set PATH=%PATH%;%QTDIR%\bin; 23 | 24 | before_build: 25 | - cd /D %APPVEYOR_BUILD_FOLDER% 26 | - mkdir build 27 | - cd build 28 | - cmake -G "%VS_FULL%" -DCMAKE_BUILD_TYPE=%configuration% -DBUILD_SHARED_LIBS=%SHARED% .. 29 | 30 | build_script: 31 | - cmake --build . --target ALL_BUILD --config %configuration% 32 | 33 | test_script: 34 | - cmake --build . --target RUN_TESTS --config %configuration% 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .qmake.* 2 | CMakeLists.txt.user 3 | build 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c++ 2 | sudo: required 3 | dist: trusty 4 | 5 | os: 6 | - linux 7 | - osx 8 | 9 | compiler: 10 | - gcc 11 | - clang 12 | 13 | env: 14 | global: 15 | - CTEST_OUTPUT_ON_FAILURE=True # Print output of failed unit test. 16 | 17 | matrix: 18 | - BUILD_TYPE=Debug 19 | SHARED_LIBS=ON 20 | - BUILD_TYPE=Release 21 | SHARED_LIBS=ON 22 | - BUILD_TYPE=Debug 23 | SHARED_LIBS=OFF 24 | - BUILD_TYPE=Release 25 | SHARED_LIBS=OFF 26 | 27 | matrix: 28 | exclude: 29 | - os: linux 30 | compiler: clang 31 | - os: osx 32 | compiler: gcc 33 | 34 | install: 35 | - DEPS_DIR="${TRAVIS_BUILD_DIR}/deps" 36 | - mkdir -p ${DEPS_DIR} && cd ${DEPS_DIR} 37 | 38 | ############################################################################ 39 | # Install Qt5.9 40 | ############################################################################ 41 | - | 42 | if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then 43 | sudo apt-add-repository -y ppa:beineri/opt-qt593-trusty 44 | sudo apt-get -qq update 45 | sudo apt-get -qq install qt59tools qt59websockets 46 | 47 | export QTDIR="/opt/qt59" 48 | export PATH="$QTDIR/bin:$PATH" 49 | qt59-env.sh 50 | else 51 | brew update > /dev/null 52 | brew tap homebrew/versions 53 | brew install qt 54 | 55 | export QTDIR="/usr/local/opt/qt5" 56 | export PATH="$QTDIR/bin:$PATH" 57 | fi 58 | 59 | before_script: 60 | - cd ${TRAVIS_BUILD_DIR} 61 | - mkdir build 62 | - cd build 63 | - cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILD_SHARED_LIBS=${SHARED_LIBS} -DPRIVATE_TESTS_ENABLED=ON .. 64 | 65 | script: 66 | - cmake --build . --target all 67 | - cmake --build . --target test 68 | 69 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5.2 FATAL_ERROR) 2 | 3 | ############################### 4 | # Project specific parameters # 5 | # Change as you see fit # 6 | ############################### 7 | set(PROJECT_NAME_PREFIX Qt) 8 | set(PROJECT_BASE_NAME Mqtt) 9 | set(FULL_VERSION 1.0.0) 10 | set(SO_VERSION 1) 11 | set(QT_MAJOR_VERSION_REQUIRED 5) 12 | 13 | ################# 14 | # Build options # 15 | ################# 16 | # When set to OFF, the library will be built as a static library 17 | option(BUILD_SHARED_LIBS "Build as shared library" OFF) 18 | 19 | ##################################################### 20 | # Start of configuration # 21 | # Lines below should not be touched in normal cases # 22 | ##################################################### 23 | 24 | #======================================= 25 | # Set some variables to be used later on 26 | #======================================= 27 | # Point CMake search path to Qt intallation directory 28 | # Either supply QTDIR as -DQTDIR= to cmake or set an environment variable QTDIR pointing to the Qt installation 29 | if ((NOT DEFINED QTDIR) AND DEFINED ENV{QTDIR}) 30 | set(QTDIR $ENV{QTDIR}) 31 | endif ((NOT DEFINED QTDIR) AND DEFINED ENV{QTDIR}) 32 | 33 | set(PROJECT_NAME ${PROJECT_NAME_PREFIX}${PROJECT_BASE_NAME}) 34 | set(TARGET_NAME ${PROJECT_BASE_NAME}) 35 | set(PROJECT_NAMESPACE ${PROJECT_NAME_PREFIX}${QT_MAJOR_VERSION_REQUIRED}) 36 | set(INSTALL_DIRECTORY_NAME ${PROJECT_NAME}/Qt${QT_MAJOR_VERSION_REQUIRED}) 37 | set(CMAKE_DIRECTORY_NAME ${PROJECT_NAME_PREFIX}${QT_MAJOR_VERSION_REQUIRED}${PROJECT_BASE_NAME}) 38 | set(PACKAGE_NAME ${PROJECT_NAME_PREFIX}${QT_MAJOR_VERSION_REQUIRED}${PROJECT_BASE_NAME}) 39 | set(MODULE_NAME ${PROJECT_NAME}) 40 | #base name used for cmake config files: 41 | #Config.cmake 42 | #ConfigVersion.cmake 43 | #Targets.cmake 44 | #Targets_noconfig.cmake 45 | set(CMAKE_CONFIG_FILE_BASE_NAME ${PROJECT_NAME_PREFIX}${QT_MAJOR_VERSION_REQUIRED}${PROJECT_BASE_NAME}) 46 | 47 | set(LIB_INSTALL_DIR lib/${INSTALL_DIRECTORY_NAME}) 48 | set(INCLUDE_INSTALL_DIR include/${INSTALL_DIRECTORY_NAME}) 49 | set(BIN_INSTALL_DIR bin) 50 | set(CMAKE_INSTALL_DIR lib/cmake/${CMAKE_DIRECTORY_NAME}) 51 | 52 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 53 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 54 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 55 | 56 | project(${PROJECT_NAME}) 57 | 58 | #================================ 59 | # Check build pre-requisites 60 | # Fail the build if not fulfilled 61 | #================================ 62 | if (NOT DEFINED QTDIR) 63 | message(FATAL_ERROR "QTDIR has not been set nor supplied as a define parameter to cmake.") 64 | endif (NOT DEFINED QTDIR) 65 | 66 | if (QTDIR) 67 | list (APPEND CMAKE_PREFIX_PATH ${QTDIR}) 68 | endif (QTDIR) 69 | 70 | #include required CMake modules 71 | include(CMakePackageConfigHelpers) 72 | 73 | #include required Qt libraries and include directories 74 | find_package(Qt${QT_MAJOR_VERSION_REQUIRED}Core REQUIRED) 75 | find_package(Qt${QT_MAJOR_VERSION_REQUIRED}Network REQUIRED) 76 | find_package(Qt${QT_MAJOR_VERSION_REQUIRED}WebSockets REQUIRED) 77 | 78 | #============================== 79 | # Add compiler and linker flags 80 | #============================== 81 | 82 | if (${BUILD_SHARED_LIBS}) 83 | add_definitions(-D${PROJECT_NAME}_BUILD_SHARED_LIBS) 84 | endif() 85 | 86 | add_definitions(-Wall -fvisibility=hidden) 87 | set(CMAKE_CXX_STANDARD 11) 88 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 89 | 90 | # Find includes in corresponding build directories 91 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 92 | 93 | # Instruct CMake to run moc automatically when needed. 94 | set(CMAKE_AUTOMOC ON) 95 | 96 | # Compile with @rpath option on Apple 97 | if (APPLE) 98 | set(CMAKE_MACOSX_RPATH 1) 99 | endif (APPLE) 100 | 101 | enable_testing() 102 | 103 | message(STATUS "Libraries will be installed to: ${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}") 104 | message(STATUS "Header files will be installed to: ${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR}") 105 | message(STATUS "Executables will be installed in: ${CMAKE_INSTALL_PREFIX}/${BIN_INSTALL_DIR}") 106 | message(STATUS "CMake config-files will be written to: ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DIR}") 107 | message(STATUS "${PROJECT_NAME} import target: ${PROJECT_NAMESPACE}::${TARGET_NAME}") 108 | message(STATUS "Building ${PROJECT_NAME} ${FULL_VERSION} in ${CMAKE_BUILD_TYPE} mode") 109 | if (PRIVATE_TESTS_ENABLED) 110 | message(STATUS "Private tests are enabled") 111 | endif() 112 | add_subdirectory(src) 113 | add_subdirectory(tests) 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Kurt Pattyn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # QtMqtt 2 | [![License][license-image]][license-url] 3 | [![Linux/OS X Build Status][travis-image]][travis-url] 4 | [![Windows Build Status][appveyor-image]][appveyor-url] 5 | 6 | ##### Author: [Kurt Pattyn](https://github.com/kurtpattyn). 7 | 8 | QtMqtt is an implementation of the [MQTT](http://mqtt.org) protocol for [Qt](https://www.qt.io)-based projects. 9 | It implements version 3.1.1 of the protocol. 10 | QtMqtt only depends on Qt libraries. 11 | 12 | ## Building and installing 13 | 14 | ### Requirements 15 | QtMqtt is created using Qt version 5. 16 | QtMqtt uses CMake to build the sources. The minimum version of CMake is 3.5 17 | 18 | ### Building 19 | ```bash 20 | #Create a directory to hold the sources. 21 | mkdir qtmqtt 22 | cd qtmqtt 23 | 24 | #Check out the sources. 25 | git clone... 26 | 27 | #Create build directory 28 | mkdir build 29 | 30 | #Go into the build directory 31 | cd build 32 | 33 | #Create the build files 34 | cmake -DCMAKE_BUILD_TYPE=debug -DBUILD_SHARED_LIBS=OFF .. 35 | 36 | #To make a release build, change `-DCMAKE_BUILD_TYPE=debug` to `-DCMAKE_BUILD_TYPE=release` 37 | #To make a dynamic library, change `-DBUILD_SHARED_LIBS=OFF` to `-DBUILD_SHARED_LIBS=ON` 38 | 39 | #Build the library 40 | make 41 | ``` 42 | ### Run unit tests 43 | `make test` 44 | 45 | > To enable testing of internal code, add `-DPRIVATE_TESTS_ENABLED` (default: OFF) to the `cmake` command line. 46 | 47 | ### Installing 48 | `make install` 49 | 50 | > This will install the library and the headers to `CMAKE_INSTALL_PREFIX`. 51 | > `CMAKE_INSTALL_PREFIX` defaults to `/usr/local` on UNIX/macOS and `c:/Program Files` on Windows. 52 | > To install in another location, add `-DCMAKE_INSTALL_PREFIX=""` to the `cmake` command line. 53 | 54 | ## Usage 55 | Include the following in your `CMakeLists.txt` file 56 | ```CMake 57 | find_package(Qt5Core) 58 | find_package(Qt5NetWork) 59 | find_package(Qt5WebSockets) 60 | find_package(Qt5Mqtt) 61 | 62 | target_link_libraries( Qt5::Mqtt) 63 | ``` 64 | 65 | In your C++ source file include the QtMqtt module 66 | ```C++ 67 | #include 68 | ``` 69 | 70 | ### Enabling debug output 71 | 72 | To enable debugging information of QtMqtt, the following environment variable can be defined. 73 | `QT_LOGGING_RULES="QtMqtt.*.debug=true"` 74 | 75 | ## License 76 | 77 | [MIT](LICENSE) 78 | 79 | 80 | [license-image]: http://img.shields.io/badge/license-MIT-blue.svg?style=flat 81 | [license-url]: LICENSE 82 | [travis-image]: https://travis-ci.org/KurtPattyn/QtMqtt.svg?branch=develop 83 | [travis-url]: https://travis-ci.org/KurtPattyn/QtMqtt 84 | [appveyor-image]: https://ci.appveyor.com/api/projects/status/4tmm94uvuwscadsv?svg=true 85 | [appveyor-url]: https://ci.appveyor.com/project/KurtPattyn/qtmqtt 86 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(${TARGET_NAME}_SOURCES 3 | qmqttclient.cpp 4 | qmqttcontrolpacket.cpp 5 | qmqttnetworkrequest.cpp 6 | qmqttpacketparser.cpp 7 | qmqttwill.cpp 8 | ) 9 | 10 | set(${TARGET_NAME}_PUBLIC_HEADERS 11 | qmqttclient.h 12 | qmqttprotocol.h 13 | qmqtt_global.h 14 | qmqttnetworkrequest.h 15 | qmqttwill.h 16 | ) 17 | 18 | set(${TARGET_NAME}_PRIVATE_HEADERS 19 | qmqttclient_p.h 20 | qmqttcontrolpacket_p.h 21 | qmqttpacketparser_p.h 22 | qmqttwill_p.h 23 | logging_p.h 24 | ) 25 | 26 | add_definitions(-DQTMQTT_LIBRARY_BUILD) 27 | 28 | # will be a static or dynamic library based on the setting of BUILD_SHARED_LIBS 29 | # see: https://cmake.org/cmake/help/v2.8.12/cmake.html#command%3aadd_library 30 | # The header files are also included in the add_library command, so that the automoc functionality 31 | # will work. 32 | add_library(${TARGET_NAME} 33 | ${${TARGET_NAME}_SOURCES} 34 | ${${TARGET_NAME}_PUBLIC_HEADERS} 35 | ${${TARGET_NAME}_PRIVATE_HEADERS}) 36 | add_library(${PROJECT_NAME_PREFIX}${QT_MAJOR_VERSION_REQUIRED}::${TARGET_NAME} ALIAS ${TARGET_NAME}) 37 | 38 | # add the Qtlibraries to the list of linked libraries 39 | # the Qt libraries are added as PUBLIC, so that projects using this module will transitively 40 | # link to these libraries as well. Include paths and compiler settings will also be promoted. 41 | target_link_libraries(${TARGET_NAME} PUBLIC Qt5::Core Qt5::Network Qt5::WebSockets) 42 | 43 | set_target_properties(${TARGET_NAME} PROPERTIES 44 | VERSION ${FULL_VERSION} 45 | SOVERSION ${SO_VERSION} 46 | PUBLIC_HEADER "${${TARGET_NAME}_PUBLIC_HEADERS}" 47 | PRIVATE_HEADER "${${TARGET_NAME}_PRIVATE_HEADERS}" 48 | ) 49 | 50 | ############################################## 51 | # Create, export and install config packages # 52 | ############################################## 53 | 54 | # Add include directories of own project to include interface 55 | target_include_directories(${TARGET_NAME} PUBLIC $) 56 | 57 | # Create config file 58 | configure_package_config_file(cmake/config.cmake.in 59 | ${CMAKE_BINARY_DIR}/${CMAKE_CONFIG_FILE_BASE_NAME}Config.cmake 60 | INSTALL_DESTINATION ${CMAKE_INSTALL_DIR} 61 | PATH_VARS INCLUDE_INSTALL_DIR 62 | ) 63 | 64 | # Create a config version file 65 | write_basic_package_version_file( 66 | ${CMAKE_BINARY_DIR}/${CMAKE_CONFIG_FILE_BASE_NAME}ConfigVersion.cmake 67 | VERSION ${FULL_VERSION} 68 | COMPATIBILITY SameMajorVersion 69 | ) 70 | 71 | # Create import targets 72 | install(TARGETS ${TARGET_NAME} EXPORT ${TARGET_NAME}Targets 73 | RUNTIME DESTINATION ${BIN_INSTALL_DIR} 74 | LIBRARY DESTINATION ${LIB_INSTALL_DIR} 75 | ARCHIVE DESTINATION ${LIB_INSTALL_DIR} 76 | PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR} 77 | PRIVATE_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/private 78 | ) 79 | 80 | # Export the import targets 81 | install(EXPORT ${TARGET_NAME}Targets 82 | FILE "${CMAKE_CONFIG_FILE_BASE_NAME}Targets.cmake" 83 | NAMESPACE ${PROJECT_NAMESPACE}:: 84 | DESTINATION ${CMAKE_INSTALL_DIR} 85 | ) 86 | 87 | # Now install the 3 config files 88 | install(FILES ${CMAKE_BINARY_DIR}/${CMAKE_CONFIG_FILE_BASE_NAME}Config.cmake 89 | ${CMAKE_BINARY_DIR}/${CMAKE_CONFIG_FILE_BASE_NAME}ConfigVersion.cmake 90 | DESTINATION ${CMAKE_INSTALL_DIR} 91 | ) 92 | 93 | # Create and install a global module include file 94 | # This makes it possible to include all header files of the module by using 95 | # #include <${PROJECT_NAME}> 96 | set(GLOBAL_HEADER_FILE ${CMAKE_BINARY_DIR}/${PROJECT_NAME}) 97 | file(WRITE ${GLOBAL_HEADER_FILE} "//Includes all headers of ${PROJECT_NAME}\n\n") 98 | 99 | foreach(header ${${TARGET_NAME}_PUBLIC_HEADERS}) 100 | file(APPEND ${GLOBAL_HEADER_FILE} "#include \"${header}\"\n") 101 | endforeach() 102 | 103 | install(FILES ${GLOBAL_HEADER_FILE} DESTINATION ${INCLUDE_INSTALL_DIR}) 104 | -------------------------------------------------------------------------------- /src/cmake/config.cmake.in: -------------------------------------------------------------------------------- 1 | # - Config file for the @MODULE_NAME@ module 2 | # To be used as: 3 | # find_package(@PACKAGE_NAME@) 4 | # target_link_libraries(@PROJECT_NAMESPACE@::@PROJECT_BASE_NAME@) 5 | # This will automatically set the correct include directories and link libraries 6 | 7 | #For more information regarding config files see: 8 | #https://cmake.org/cmake/help/v3.5/module/CMakePackageConfigHelpers.html 9 | 10 | @PACKAGE_INIT@ 11 | 12 | include("${CMAKE_CURRENT_LIST_DIR}/@CMAKE_CONFIG_FILE_BASE_NAME@Targets.cmake") 13 | 14 | check_required_components(Qt@QT_MAJOR_VERSION_REQUIRED@Core 15 | Qt@QT_MAJOR_VERSION_REQUIRED@Network 16 | Qt@QT_MAJOR_VERSION_REQUIRED@WebSockets) 17 | 18 | # @PROJECT_NAME@ requires C++11 19 | set(CMAKE_CXX_STANDARD 11) 20 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 21 | 22 | include_directories(@PACKAGE_INCLUDE_INSTALL_DIR@) 23 | 24 | if (@BUILD_SHARED_LIBS@) 25 | add_definitions(-D@PROJECT_NAME@_BUILD_SHARED_LIBS) 26 | endif() 27 | -------------------------------------------------------------------------------- /src/logging_p.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define LoggingModule(moduleName) \ 7 | static const QLoggingCategory module("QtMqtt." moduleName, QtWarningMsg) 8 | -------------------------------------------------------------------------------- /src/qmqtt_global.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef QtMqtt_BUILD_SHARED_LIBS 6 | # if defined(QTMQTT_LIBRARY_BUILD) 7 | # define QTMQTT_EXPORT Q_DECL_EXPORT 8 | # ifdef PRIVATE_TESTS_ENABLED 9 | # define QTMQTT_AUTOTEST_EXPORT Q_DECL_EXPORT 10 | # else 11 | # define QTMQTT_AUTOTEST_EXPORT 12 | # endif 13 | # else 14 | # define QTMQTT_EXPORT Q_DECL_IMPORT 15 | # ifdef PRIVATE_TESTS_ENABLED 16 | # define QTMQTT_AUTOTEST_EXPORT Q_DECL_IMPORT 17 | # else 18 | # define QTMQTT_AUTOTEST_EXPORT 19 | # endif 20 | # endif 21 | #else 22 | # define QTMQTT_EXPORT 23 | # define QTMQTT_AUTOTEST_EXPORT 24 | #endif 25 | -------------------------------------------------------------------------------- /src/qmqttclient.cpp: -------------------------------------------------------------------------------- 1 | #include "qmqttclient.h" 2 | #include "qmqttclient_p.h" 3 | #include "qmqttnetworkrequest.h" 4 | #include "qmqttcontrolpacket_p.h" 5 | #include "qmqttwill.h" 6 | #include "logging_p.h" 7 | 8 | LoggingModule("QMqttClient"); 9 | 10 | /*! 11 | \class QMqttClient 12 | 13 | \inmodule QtMqtt 14 | 15 | \brief Implements a client for the MQTT protocol over WebSockets. 16 | 17 | MQTT stands for Message Queue Telemetry Transport, and is a web technology providing full-duplex 18 | communications channels over a single TCP connection. 19 | The version 3.1.1 of the MQTT protocol was standardized by the OASIS in 2014: 20 | \l {MQTT Standard}{MQTT Version 3.1.1. Edited by Andrew Banks and Rahul Gupta. 29 October 2014. OASIS Standard}. 21 | 22 | Limitations: 23 | ============ 24 | \list 1 25 | \li The QMqttClient class currently does not support sessions and hence does not redeliver 26 | packets. 27 | 28 | From the MQTT specification (4.4 Message delivery retry): 29 | "Historically retransmission of Control Packets was required to overcome data loss on some 30 | older TCP networks. This might remain a concern where MQTT 3.1.1 implementations 31 | are to be deployed in such environments." 32 | We assume that the MQTT client is deployed on state-of-the-art TCP networks. 33 | 34 | \li The QMqttClient currently does not support publishing messages with QoS 2. 35 | \endlist 36 | */ 37 | 38 | /*! 39 | \fn void QMqttClient::stateChanged(QMqttProtocol::State state) 40 | 41 | Emitted when the connectivity status of the connection changes. 42 | The \a state parameter is the new state. 43 | 44 | \note QMqttProtocol::State::CONNECTED is emitted after the client received a connection 45 | acknowledgement from the server. 46 | 47 | \sa connected() 48 | */ 49 | 50 | /*! 51 | \fn void QMqttClient::connected() 52 | 53 | This signal is emitted when a connection is successfully established. 54 | A connection is successfully established if the connection is acknowledged by the server. 55 | 56 | \sa connect(), disconnected() 57 | */ 58 | 59 | /*! 60 | \fn void QMqttClient::disconnected() 61 | 62 | This signal is emitted when the connection is completely closed. 63 | 64 | \sa disconnect(), connected() 65 | */ 66 | 67 | /*! 68 | \fn void QMqttClient::messageReceived(const QString &topicName, const QByteArray &message); 69 | 70 | This signal is emitted when a \a message was received on the topic with the given \a topicName; 71 | */ 72 | 73 | /*! 74 | \fn void QMqttClient::error(MQTTProtocol::Error err, const QString &errorMessage); 75 | 76 | This signal is emitted when an error occurs. The \a err parameter indicates the type of error 77 | that occurred and \a errorMessage contains a textual description of the error. 78 | */ 79 | 80 | /*! 81 | \brief Calls the given function \a f after \a ms milliseconds. 82 | 83 | setTimeout does not block, but uses the Qt event loop to schedule the invocation of the function. 84 | 85 | \code 86 | setTimeout([]() { qDebug() << "Called me after 1 second." }, 1000); 87 | qDebug() << "This will be shown first."; 88 | \endcode 89 | 90 | It is also possible to supply parameters to the callback by binding the function to its 91 | arguments. 92 | 93 | \code 94 | void printSomething(const QString &message) { 95 | qDebug() << message; 96 | } 97 | setTimeout(std::bind(printSomething, message), 1000); 98 | \endcode 99 | 100 | \sa setImmediate() 101 | 102 | \internal 103 | */ 104 | void setTimeout(std::function f, int ms) 105 | { 106 | QTimer::singleShot(ms, f); 107 | } 108 | 109 | /*! 110 | \brief Puts the call to the given function \a f at the end of Qt's event loop. 111 | setImmediate does not block. 112 | 113 | \e setTimeout(f, 0) is the same as calling \e setImmediate(f). 114 | 115 | \sa setTimeout() 116 | 117 | \internal 118 | */ 119 | void setImmediate(std::function f) 120 | { 121 | setTimeout(f, 0); 122 | } 123 | 124 | /*! 125 | \internal 126 | */ 127 | QMqttClientPrivate::QMqttClientPrivate(const QString &clientId, const QSet &allowedSslErrors, QMqttClient * const q) : 128 | QObject(), 129 | q_ptr(q), 130 | m_clientId(clientId), 131 | m_pongReceived(false), 132 | m_pingTimer(), 133 | m_pingIntervalMs(30000), // 30 seconds 134 | m_webSocket(new QWebSocket), 135 | m_state(QMqttProtocol::State::OFFLINE), 136 | m_packetParser(new QMqttPacketParser), 137 | m_packetIdentifier(0), 138 | m_subscribeCallbacks(), 139 | m_will(), 140 | m_signalSlotConnected(false), 141 | m_allowedSslErrors(allowedSslErrors), 142 | m_userName(), 143 | m_password() 144 | { 145 | Q_ASSERT(q); 146 | Q_ASSERT(!clientId.isEmpty()); 147 | } 148 | 149 | /*! 150 | \internal 151 | */ 152 | QMqttClientPrivate::~QMqttClientPrivate() 153 | {} 154 | 155 | 156 | /*! 157 | \internal 158 | */ 159 | void QMqttClientPrivate::connect(const QMqttNetworkRequest &request, const QMqttWill &will, const QString &userName, const QByteArray &password) 160 | { 161 | if (m_state != QMqttProtocol::State::OFFLINE) { 162 | qCWarning(module) << "Already connected."; 163 | return; 164 | } 165 | m_will = will; 166 | m_userName = userName; 167 | m_password = password; 168 | qCDebug(module) << "Connecting to Mqtt backend @ endpoint" << request.url(); 169 | setState(QMqttProtocol::State::CONNECTING); 170 | 171 | makeSignalSlotConnections(); 172 | 173 | m_webSocket->open(request); 174 | } 175 | 176 | /*! 177 | \internal 178 | */ 179 | void QMqttClientPrivate::disconnect() 180 | { 181 | if (m_state != QMqttProtocol::State::OFFLINE) { 182 | m_pingTimer.stop(); 183 | setState(QMqttProtocol::State::DISCONNECTING); 184 | m_webSocket->sendBinaryMessage(QMqttDisconnectControlPacket().encode()); 185 | m_webSocket->close(); 186 | } 187 | } 188 | 189 | /*! 190 | Checks validity of a topic name. 191 | A topic name is valid if it follows the following rules: 192 | - Rule #1: If any part of the topic is not `+` or `#`, then it must not contain `+` and `#` 193 | - Rule #2: Part `#` must be located at the end of the name 194 | 195 | \internal 196 | */ 197 | bool isTopicNameValid(const QString &topicName) 198 | { 199 | const QStringList parts = topicName.split(QStringLiteral("/")); 200 | for (int i = 0; i < parts.length(); ++i) { 201 | const QString part = parts[i]; 202 | if (part == QStringLiteral("+")) { 203 | continue; 204 | } 205 | 206 | if (part == QStringLiteral("#")) { 207 | // for Rule #2 208 | return i == (parts.length() - 1); 209 | } 210 | 211 | if (part.contains('+') || part.contains('#')) { 212 | return false; 213 | } 214 | } 215 | return true; 216 | } 217 | 218 | /*! 219 | \internal 220 | */ 221 | void QMqttClientPrivate::subscribe(const QString &topic, QMqttProtocol::QoS qos, 222 | std::function cb) 223 | { 224 | if (!isTopicNameValid(topic)) { 225 | qCWarning(module) << "Invalid topic name detected:" << topic; 226 | setImmediate(std::bind(cb, false)); 227 | return; 228 | } 229 | qCDebug(module) << "Subscribing to topic" << topic; 230 | QVector> topicFilters 231 | = { { topic, qos } }; 232 | QMqttSubscribeControlPacket subscribePacket(++m_packetIdentifier, topicFilters); 233 | m_subscribeCallbacks.insert(m_packetIdentifier, cb); 234 | sendData(subscribePacket.encode()); 235 | } 236 | 237 | /*! 238 | \internal 239 | */ 240 | void QMqttClientPrivate::unsubscribe(const QString &topic, std::function cb) 241 | { 242 | if (!isTopicNameValid(topic)) { 243 | qCWarning(module) << "Invalid topic name detected:" << topic; 244 | setImmediate(std::bind(cb, false)); 245 | return; 246 | } 247 | QMqttUnsubscribeControlPacket unsubscribePacket(++m_packetIdentifier, {topic}); 248 | m_subscribeCallbacks.insert(m_packetIdentifier, cb); 249 | sendData(unsubscribePacket.encode()); 250 | } 251 | 252 | /*! 253 | \internal 254 | */ 255 | void QMqttClientPrivate::publish(const QString &topic, const QByteArray &message) 256 | { 257 | qCDebug(module) << "Publishing" << message << "to topic" << topic; 258 | QMqttPublishControlPacket packet(topic, message, QMqttProtocol::QoS::AT_MOST_ONCE, false); 259 | sendData(packet.encode()); 260 | } 261 | 262 | /*! 263 | \internal 264 | */ 265 | void QMqttClientPrivate::publish(const QString &topic, const QByteArray &message, 266 | std::function cb) 267 | { 268 | qCDebug(module) << "Publishing" << message << "to topic" << topic; 269 | QMqttPublishControlPacket packet(topic, message, QMqttProtocol::QoS::AT_LEAST_ONCE, 270 | false, ++m_packetIdentifier); 271 | m_subscribeCallbacks.insert(m_packetIdentifier, cb); 272 | sendData(packet.encode()); 273 | } 274 | 275 | /*! 276 | \internal 277 | */ 278 | void QMqttClientPrivate::setState(QMqttProtocol::State newState) 279 | { 280 | if (m_state != newState) { 281 | Q_Q(QMqttClient); 282 | 283 | m_state = newState; 284 | Q_EMIT q->stateChanged(m_state); 285 | } 286 | } 287 | 288 | /*! 289 | \internal 290 | */ 291 | QHostAddress QMqttClientPrivate::localAddress() const 292 | { 293 | qInfo() << "socket info:" << m_webSocket->localAddress() << m_webSocket->peerAddress() << m_webSocket->state(); 294 | return m_webSocket->localAddress(); 295 | } 296 | 297 | /*! 298 | \internal 299 | */ 300 | quint16 QMqttClientPrivate::localPort() const 301 | { 302 | return m_webSocket->localPort(); 303 | } 304 | 305 | /*! 306 | \internal 307 | */ 308 | void QMqttClientPrivate::sendPing() 309 | { 310 | qCDebug(module) << "Sending ping."; 311 | if (m_pongReceived) { 312 | m_pongReceived = false; 313 | QMqttPingReqControlPacket packet; 314 | m_webSocket->sendBinaryMessage(packet.encode()); 315 | } else { 316 | Q_Q(QMqttClient); 317 | 318 | const QString errorMessage = QStringLiteral("Pong not received within expected time."); 319 | 320 | Q_EMIT q->error(QMqttProtocol::Error::TIME_OUT, errorMessage); 321 | 322 | disconnect(); 323 | } 324 | } 325 | 326 | /*! 327 | \internal 328 | */ 329 | void QMqttClientPrivate::onPongReceived() 330 | { 331 | qCDebug(module) << "Received pong."; 332 | m_pongReceived = true; 333 | } 334 | 335 | /*! 336 | \internal 337 | */ 338 | void QMqttClientPrivate::onSocketConnected() 339 | { 340 | qCDebug(module) << "WebSockets successfully connected."; 341 | 342 | QMqttConnectControlPacket packet(m_clientId); 343 | packet.setWill(m_will); 344 | if (!m_userName.isEmpty() && !m_password.isNull()) 345 | { 346 | packet.setCredentials(m_userName, m_password); 347 | } 348 | m_webSocket->sendBinaryMessage(packet.encode()); 349 | 350 | //TODO: initialize connection timeout 351 | } 352 | 353 | /*! 354 | \internal 355 | */ 356 | void QMqttClientPrivate::onConnackReceived(QMqttProtocol::Error err, bool sessionPresent) 357 | { 358 | Q_Q(QMqttClient); 359 | 360 | qCDebug(module) << "Received connack with returncode:" << err << "and session present:" << sessionPresent; 361 | if (m_state != QMqttProtocol::State::CONNECTING) { 362 | const QString errorString = 363 | QStringLiteral("Received a CONNACK packet while the MQTT connection is already connected."); 364 | Q_EMIT q->error(QMqttProtocol::Error::PROTOCOL_VIOLATION, errorString); 365 | m_webSocket->abort(); 366 | return; 367 | } 368 | if (err != QMqttProtocol::Error::CONNECTION_ACCEPTED) { 369 | const QString errorString = QStringLiteral("Connection refused"); 370 | Q_EMIT q->error(err, errorString); 371 | m_webSocket->abort(); 372 | return; 373 | } 374 | 375 | if (m_pingIntervalMs > 0) { 376 | m_pongReceived = true; 377 | m_pingTimer.setInterval(m_pingIntervalMs); 378 | m_pingTimer.setSingleShot(false); 379 | m_pingTimer.start(); 380 | } 381 | 382 | Q_EMIT q->connected(); 383 | } 384 | 385 | /*! 386 | \internal 387 | */ 388 | void QMqttClientPrivate::onSubackReceived(uint16_t packetIdentifier, 389 | QVector qos) 390 | { 391 | qCDebug(module) << "Received suback for packet with id" << packetIdentifier; 392 | if (m_subscribeCallbacks.contains(packetIdentifier)) { 393 | const std::vector qosVector = qos.toStdVector(); 394 | const bool result = std::none_of(qosVector.begin(), qosVector.end(), 395 | [](QMqttProtocol::QoS qos) { return qos == QMqttProtocol::QoS::INVALID; }); 396 | auto cb = m_subscribeCallbacks.value(packetIdentifier); 397 | m_subscribeCallbacks.remove(packetIdentifier); 398 | setImmediate(std::bind(cb, result)); 399 | } 400 | } 401 | 402 | /*! 403 | \internal 404 | */ 405 | void QMqttClientPrivate::onPublishReceived(QMqttProtocol::QoS qos, uint16_t packetIdentifier, 406 | const QString &topicName, const QByteArray &message) 407 | { 408 | Q_Q(QMqttClient); 409 | 410 | qCDebug(module) << "Received publish packet with qos" << qos << "and id" << packetIdentifier; 411 | 412 | Q_EMIT q->messageReceived(topicName, message); 413 | 414 | if (qos == QMqttProtocol::QoS::EXACTLY_ONCE) { 415 | const QMqttPubRecControlPacket packet(packetIdentifier); 416 | sendData(packet.encode()); 417 | } else if (qos == QMqttProtocol::QoS::AT_LEAST_ONCE) { 418 | const QMqttPubAckControlPacket packet(packetIdentifier); 419 | sendData(packet.encode()); 420 | } 421 | } 422 | 423 | /*! 424 | \internal 425 | */ 426 | void QMqttClientPrivate::onPubRelReceived(uint16_t packetIdentifier) 427 | { 428 | qCDebug(module) << "Received PubRel packet with id" << packetIdentifier; 429 | QMqttPubCompControlPacket packet(packetIdentifier); 430 | sendData(packet.encode()); 431 | } 432 | 433 | /*! 434 | \internal 435 | */ 436 | void QMqttClientPrivate::onPubAckReceived(uint16_t packetIdentifier) 437 | { 438 | qCDebug(module) << "Received PubAck packet with id" << packetIdentifier; 439 | if (m_subscribeCallbacks.contains(packetIdentifier)) { 440 | auto cb = m_subscribeCallbacks.value(packetIdentifier); 441 | m_subscribeCallbacks.remove(packetIdentifier); 442 | setImmediate(std::bind(cb, true)); 443 | } 444 | } 445 | 446 | /*! 447 | \internal 448 | */ 449 | void QMqttClientPrivate::onUnsubackReceived(uint16_t packetIdentifier) 450 | { 451 | qCDebug(module) << "Received unsuback for packet with id" << packetIdentifier; 452 | if (m_subscribeCallbacks.contains(packetIdentifier)) { 453 | auto cb = m_subscribeCallbacks.value(packetIdentifier); 454 | m_subscribeCallbacks.remove(packetIdentifier); 455 | setImmediate(std::bind(cb, true)); 456 | } 457 | } 458 | 459 | /*! 460 | \internal 461 | */ 462 | void QMqttClientPrivate::sendData(const QByteArray &data) 463 | { 464 | m_webSocket->sendBinaryMessage(data); 465 | if (m_pingIntervalMs > 0) { 466 | m_pingTimer.start(); //restart the timer 467 | } 468 | } 469 | 470 | /*! 471 | Converts the given list of QSSlErrors to a list of strings. 472 | \internal 473 | */ 474 | QString toString(const QList &sslErrors) { 475 | QString sslErrorString; 476 | for (const QSslError &sslError : sslErrors) { 477 | sslErrorString.append(QStringLiteral("%1 (%2)\n") 478 | .arg(sslError.errorString()).arg(sslError.error())); 479 | } 480 | return sslErrorString; 481 | } 482 | 483 | 484 | /*! 485 | \internal 486 | */ 487 | bool QMqttClientPrivate::sslErrorsAllowed(const QList &errors) const 488 | { 489 | if (!m_allowedSslErrors.isEmpty()) 490 | { 491 | // The QSslErrors received with the QWebSocket::sslErrors signal are probably defined with the QSslError constructor 492 | // with a certificate QSslError::QSslError(QSslError::SslError error, const QSslCertificate &certificate) 493 | // If m_allowedSslErrors is defined using the QSslError constructor without a certificate QSslError::QSslError(QSslError::SslError error), 494 | // e.g. QSet({QSslError::HostNameMismatch, QSslError::SelfSignedCertificate, QSslError::SelfSignedCertificateInChain}), 495 | // the subtraction is never empty. 496 | // This is because the QSslErrors comparison operator compares both the error() and the certificate(). 497 | // The comparison is empty when needed: 498 | // 1. if we construct a QSet errorsSet with only the error() of the errors received with the QWebSocket::sslErrors signal 499 | // 2. and if we construct a QSet allowedErrors with only the error() of m_allowedSslErrors 500 | QSet errorsSet; 501 | for (const auto &error : errors) 502 | { 503 | errorsSet << QSslError(error.error()); 504 | } 505 | QSet allowedErrors; 506 | for (const auto &allowedError : m_allowedSslErrors) 507 | { 508 | allowedErrors << QSslError(allowedError.error()); 509 | } 510 | const QSet subtraction = errorsSet.subtract(allowedErrors); 511 | if (subtraction.isEmpty()) 512 | { 513 | return true; 514 | } 515 | } 516 | 517 | return false; 518 | } 519 | 520 | /*! 521 | \internal 522 | */ 523 | void QMqttClientPrivate::makeSignalSlotConnections() 524 | { 525 | if (m_signalSlotConnected) 526 | { 527 | return; 528 | } 529 | 530 | Q_Q(QMqttClient); 531 | 532 | QObject::connect(m_webSocket.data(), &QWebSocket::connected, 533 | this, &QMqttClientPrivate::onSocketConnected, Qt::QueuedConnection); 534 | QObject::connect(m_webSocket.data(), &QWebSocket::disconnected, 535 | [this, q]() { 536 | qCDebug(module) << "Received QWebSocket::disconnected, close code" << m_webSocket->closeCode() << "close reason" << m_webSocket->closeReason(); 537 | setState(QMqttProtocol::State::OFFLINE); 538 | Q_EMIT q->disconnected(); 539 | }); 540 | 541 | typedef void (QWebSocket::* sslErrorsSignal)(const QList &); 542 | QObject::connect(m_webSocket.data(), static_cast(&QWebSocket::sslErrors), 543 | [this, q](const QList &errors) { 544 | if (sslErrorsAllowed(errors)) 545 | { 546 | qCDebug(module) << "Ignoring SSL errors" << errors; 547 | m_webSocket->ignoreSslErrors(); 548 | } 549 | else 550 | { 551 | const QString errorMessage = QStringLiteral("SSL errors encountered: %1.").arg(toString(errors)); 552 | Q_EMIT q->error(QMqttProtocol::Error::CONNECTION_FAILED, errorMessage); 553 | setState(QMqttProtocol::State::OFFLINE); 554 | } 555 | }); 556 | 557 | typedef void (QWebSocket::* errorSignal)(QAbstractSocket::SocketError); 558 | QObject::connect(m_webSocket.data(), static_cast(&QWebSocket::error), 559 | [this, q](QAbstractSocket::SocketError error) { 560 | const QString errorMessage = QStringLiteral("Error connecting to MQTT server: %1 (%2).") 561 | .arg(error).arg(m_webSocket->errorString()); 562 | Q_EMIT q->error(QMqttProtocol::Error::CONNECTION_FAILED, errorMessage); 563 | setState(QMqttProtocol::State::OFFLINE); 564 | }); 565 | QObject::connect(m_webSocket.data(), &QWebSocket::textMessageReceived, [this, q](const QString &msg) { 566 | const QString errorMessage 567 | = QStringLiteral("Received a text message on the MQTT connection (%1). This should not happen. Connection will be closed.") 568 | .arg(msg); 569 | Q_EMIT q->error(QMqttProtocol::Error::PROTOCOL_VIOLATION, errorMessage); 570 | }); 571 | QObject::connect(m_webSocket.data(), &QWebSocket::binaryMessageReceived, 572 | m_packetParser.data(), &QMqttPacketParser::parse, Qt::QueuedConnection); 573 | 574 | QObject::connect(m_packetParser.data(), &QMqttPacketParser::connack, 575 | this, &QMqttClientPrivate::onConnackReceived, Qt::QueuedConnection); 576 | QObject::connect(m_packetParser.data(), &QMqttPacketParser::suback, 577 | this, &QMqttClientPrivate::onSubackReceived, Qt::QueuedConnection); 578 | QObject::connect(m_packetParser.data(), &QMqttPacketParser::publish, 579 | this, &QMqttClientPrivate::onPublishReceived, Qt::QueuedConnection); 580 | QObject::connect(m_packetParser.data(), &QMqttPacketParser::pubrel, 581 | this, &QMqttClientPrivate::onPubRelReceived, Qt::QueuedConnection); 582 | QObject::connect(m_packetParser.data(), &QMqttPacketParser::unsuback, 583 | this, &QMqttClientPrivate::onUnsubackReceived, Qt::QueuedConnection); 584 | QObject::connect(m_packetParser.data(), &QMqttPacketParser::puback, 585 | this, &QMqttClientPrivate::onPubAckReceived, Qt::QueuedConnection); 586 | 587 | QObject::connect(&m_pingTimer, &QTimer::timeout, 588 | this, &QMqttClientPrivate::sendPing, Qt::QueuedConnection); 589 | QObject::connect(m_packetParser.data(), &QMqttPacketParser::pong, 590 | this, &QMqttClientPrivate::onPongReceived, Qt::QueuedConnection); 591 | 592 | //forward parser errors to user of QMqttClient 593 | QObject::connect(m_packetParser.data(), &QMqttPacketParser::error, 594 | q, &QMqttClient::error, Qt::QueuedConnection); 595 | 596 | m_signalSlotConnected = true; 597 | } 598 | 599 | /*! 600 | Creates a new QMqttClient with the given \a clientId and \a parent. 601 | \a clientId should be a unique id representing the connection. 602 | The length of the \a clientId should be larger than smaller than 24 characters. 603 | If an empty \a clientId is provided, the server will generate a random one. 604 | \a allowedSslErrors: specify any SSL errors you want to allow 605 | */ 606 | QMqttClient::QMqttClient(const QString &clientId, const QSet &allowedSslErrors, QObject *parent) : 607 | QObject(parent), 608 | d_ptr(new QMqttClientPrivate(clientId, allowedSslErrors, this)) 609 | { 610 | qRegisterMetaType("QMqttProtocol::State"); 611 | } 612 | 613 | /*! 614 | Destroys the QMqttClient. If a connection was open, the connection is aborted and the last will 615 | will be executed by the server. 616 | To have an orderly disconnection, disconnect() should be called prior to destroying the 617 | QMqttlient. 618 | 619 | \sa disconnect() 620 | */ 621 | QMqttClient::~QMqttClient() 622 | {} 623 | 624 | /*! 625 | Connects the QMqttClient to the server specified in the \a request. 626 | All HTTP headers present in the \a request will be sent to the server during the WebSocket 627 | handshake request. 628 | When the connection succeeds, a connected() signal will be emitted. 629 | 630 | During setup of the connection, the state of the client will change from DISCONNECTED over 631 | CONNECTING to CONNECTED. 632 | \a userName: specify the userName to be used with connect; connect QMqttPublishControlPacket 633 | does not contain this userName if it is empty 634 | \a password: specify the password to be used with connect; connect QMqttPublishControlPacket 635 | does not contain this password if it is null (use QByteArray() as a null password) 636 | 637 | \sa disconnect(), stateChanged() 638 | */ 639 | void QMqttClient::connect(const QMqttNetworkRequest &request, const QMqttWill &will, const QString &userName, const QByteArray &password) 640 | { 641 | Q_D(QMqttClient); 642 | 643 | d->connect(request, will, userName, password); 644 | } 645 | 646 | /*! 647 | Disconnects the client from the server in an orderly manner. 648 | The client will first send a DISCONNECT packet to indicate to the server that it will close 649 | the connection soon. 650 | 651 | During tear down of the connection, the QMqttClient will change state from CONNECTED over 652 | DISCONNECTING to OFFLINE. 653 | 654 | \sa connect(), stateChanged() 655 | */ 656 | void QMqttClient::disconnect() 657 | { 658 | Q_D(QMqttClient); 659 | 660 | d->disconnect(); 661 | } 662 | 663 | /*! 664 | Subscribes the client to \a topic with the given Quality of Service \a qos. 665 | When subscription has finished, the given callback \a cb is called indicating whether the 666 | subscription succeeded or not. 667 | 668 | The \a topic must follow the following rules: 669 | \list 670 | \li Rule #1: If any part of the topic is not `+` or `#`, then it must not contain `+` and `#` 671 | \li Rule #2: Part `#` must be located at the end of the name 672 | \li Rule #3: The length must be at least 1 673 | \endlist 674 | 675 | If the \a topic is invalid, the callback will be called with false. The connection will not 676 | be dropped, as the check is done before it is sent to the server. 677 | 678 | The \a topic can contain the MQTT supported wildcard characters `#` and `+`. 679 | 680 | Example topic names: 681 | \list 682 | \li \c /: a single forward slash 683 | \li \c +: any single topic name 684 | \li \c {resources/+/weight}: matches resources/table/weight, resources/car/weight, 685 | but not resources/weight nor resources/car/door/weight 686 | \li \c {resources/#}: matches all topics starting with resources/ 687 | \endlist 688 | 689 | \sa unsubscribe() 690 | */ 691 | void QMqttClient::subscribe(const QString &topic, QMqttProtocol::QoS qos, 692 | std::function cb) 693 | { 694 | Q_D(QMqttClient); 695 | 696 | d->subscribe(topic, qos, cb); 697 | } 698 | 699 | /*! 700 | Unsubscribes the client from the given \a topic. When unsubscription has finished, the 701 | callback \a cb will be called with the result. 702 | 703 | The same rules hold for the \a topic as for the subscribe() call. 704 | When an invalid \a topic is detected, the callback will be called with false. The connection 705 | will not be dropped, as the check is done before the request is sent to the server. 706 | 707 | \sa subscribe() 708 | */ 709 | void QMqttClient::unsubscribe(const QString &topic, std::function cb) 710 | { 711 | Q_D(QMqttClient); 712 | 713 | d->unsubscribe(topic, cb); 714 | } 715 | 716 | /*! 717 | Published the given \a message to the given \a topic with a QoS equal to AT_MOST_ONCE (0). 718 | Publishing an empty \a message is allowed, however the topic name should not be empty and 719 | should not contain wildcard characters. 720 | If the topic name is invalid, the connection will be dropped by the server. 721 | 722 | When an error occurs during publising, an error() signal will be emitted and errorString() will 723 | contain a description of the last error. 724 | */ 725 | void QMqttClient::publish(const QString &topic, const QByteArray &message) 726 | { 727 | Q_D(QMqttClient); 728 | 729 | d->publish(topic, message); 730 | } 731 | 732 | /*! 733 | Published the given \a message to the given \a topic with a QoS equal to AT_LEAST_ONCE (1). 734 | When publication finished the given callback \a cb is called indicating success or failure. 735 | Publishing an empty \a message is allowed, however the topic name should not be empty and 736 | should not contain wildcard characters. 737 | If the topic name is invalid, the connection will be dropped by the server. 738 | 739 | When an error occurs during publising, an error() signal will be emitted and errorString() will 740 | contain a description of the last error. 741 | 742 | \note Exactly once delivery (EXACTLY_ONCE) is currently not supported. 743 | 744 | \overload publish() 745 | */ 746 | void QMqttClient::publish(const QString &topic, const QByteArray &message, 747 | std::function cb) 748 | { 749 | Q_D(QMqttClient); 750 | 751 | d->publish(topic, message, cb); 752 | } 753 | 754 | /*! 755 | * Returns the local address 756 | */ 757 | QHostAddress QMqttClient::localAddress() const 758 | { 759 | Q_D(const QMqttClient); 760 | 761 | 762 | return d->localAddress(); 763 | } 764 | 765 | /*! 766 | * Returns the local port 767 | */ 768 | quint16 QMqttClient::localPort() const 769 | { 770 | Q_D(const QMqttClient); 771 | 772 | return d->localPort(); 773 | } 774 | -------------------------------------------------------------------------------- /src/qmqttclient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "qmqttwill.h" 9 | #include "qmqttprotocol.h" 10 | #include "qmqtt_global.h" 11 | 12 | //TODO: add SSL connectivity 13 | //Currently only secure websockets are supported 14 | 15 | class QMqttNetworkRequest; 16 | class QString; 17 | class QByteArray; 18 | class QMqttClientPrivate; 19 | class QTMQTT_EXPORT QMqttClient : public QObject 20 | { 21 | Q_OBJECT 22 | Q_DECLARE_PRIVATE(QMqttClient) 23 | Q_DISABLE_COPY(QMqttClient) 24 | 25 | public: 26 | QMqttClient(const QString &clientId, const QSet &allowedSslErrors = QSet(), QObject *parent = nullptr); 27 | virtual ~QMqttClient(); 28 | 29 | using QObject::connect; 30 | void connect(const QMqttNetworkRequest &request, const QMqttWill &will = QMqttWill(), const QString &userName = QString(), const QByteArray &password = QByteArray()); 31 | using QObject::disconnect; 32 | void disconnect(); 33 | 34 | void subscribe(const QString &topic, QMqttProtocol::QoS qos, std::function cb); 35 | void unsubscribe(const QString &topic, std::function cb); 36 | void publish(const QString &topic, const QByteArray &message); 37 | void publish(const QString &topic, const QByteArray &message, std::function cb); 38 | 39 | QHostAddress localAddress() const; 40 | quint16 localPort() const; 41 | 42 | Q_SIGNALS: 43 | void stateChanged(QMqttProtocol::State); 44 | void connected(); 45 | void disconnected(); 46 | void messageReceived(const QString &topicName, const QByteArray &message); 47 | void error(QMqttProtocol::Error err, const QString &errorMessage); 48 | 49 | private: 50 | QScopedPointer d_ptr; 51 | }; 52 | -------------------------------------------------------------------------------- /src/qmqttclient_p.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "qmqttprotocol.h" 12 | #include "qmqttpacketparser_p.h" 13 | #include "qmqttwill.h" 14 | 15 | class QMqttClient; 16 | class QMqttNetworkRequest; 17 | class QMqttClientPrivate : public QObject 18 | { 19 | Q_OBJECT 20 | Q_DISABLE_COPY(QMqttClientPrivate) 21 | Q_DECLARE_PUBLIC(QMqttClient) 22 | 23 | public: 24 | QMqttClientPrivate(const QString &clientId, const QSet &allowedSslErrors, QMqttClient * const q); 25 | virtual ~QMqttClientPrivate(); 26 | 27 | void connect(const QMqttNetworkRequest &request, const QMqttWill &will, const QString &userName, const QByteArray &password); 28 | void disconnect(); 29 | void subscribe(const QString &topic, QMqttProtocol::QoS qos, std::function cb); 30 | void unsubscribe(const QString &topic, std::function cb); 31 | void publish(const QString &topic, const QByteArray &message); 32 | void publish(const QString &topic, const QByteArray &message, std::function cb); 33 | 34 | void sendPing(); 35 | 36 | void setState(QMqttProtocol::State newState); 37 | 38 | QHostAddress localAddress() const; 39 | quint16 localPort() const; 40 | 41 | private: 42 | QMqttClient * const q_ptr; 43 | const QString m_clientId; 44 | bool m_pongReceived; 45 | QTimer m_pingTimer; 46 | int m_pingIntervalMs; 47 | QScopedPointer m_webSocket; 48 | QMqttProtocol::State m_state; 49 | QScopedPointer m_packetParser; 50 | uint16_t m_packetIdentifier; 51 | QMap> m_subscribeCallbacks; 52 | QMqttWill m_will; 53 | bool m_signalSlotConnected; 54 | const QSet m_allowedSslErrors; 55 | QString m_userName; 56 | QByteArray m_password; 57 | 58 | private Q_SLOTS: 59 | void onSocketConnected(); 60 | void onConnackReceived(QMqttProtocol::Error error, bool sessionPresent); 61 | void onSubackReceived(uint16_t packetIdentifier, QVector qos); 62 | void onPublishReceived(QMqttProtocol::QoS qos, uint16_t packetIdentifier, 63 | const QString &topicName, const QByteArray &message); 64 | void onPubRelReceived(uint16_t packetIdentifier); 65 | void onPubAckReceived(uint16_t packetIdentifier); 66 | void onUnsubackReceived(uint16_t packetIdentifier); 67 | void onPongReceived(); 68 | 69 | private: //helpers 70 | bool sslErrorsAllowed(const QList &sslErrors) const; 71 | void makeSignalSlotConnections(); 72 | 73 | void sendData(const QByteArray &data); 74 | }; 75 | 76 | -------------------------------------------------------------------------------- /src/qmqttcontrolpacket.cpp: -------------------------------------------------------------------------------- 1 | #include "qmqttcontrolpacket_p.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "logging_p.h" 8 | 9 | LoggingModule("QMqttControlPacket"); 10 | 11 | /*! 12 | \enum ControlPacket::PacketType 13 | 14 | \inmodule QtMqtt 15 | 16 | The control packet types supported by MQTT v3.1.1 17 | 18 | \value RESERVED_0 Reserved; should not be used 19 | \value CONNECT Client request to connect to server (client --> server) 20 | \value CONNACK Connect acknowledgement (server --> client) 21 | \value PUBLISH Publish message (client <--> server) 22 | \value PUBACK Publish acknowledgement (client <--> server) 23 | \value PUBREC Publish received (assured delivery part 1) (client <--> server) 24 | \value PUBREL Publish received (assured delivery part 2) (client <--> server) 25 | \value PUBCOMP Publish received (assured delivery part 2) (client <--> server) 26 | \value SUBSCRIBE Client subscribe request (client --> server) 27 | \value SUBACK Subscribe acknowledgement (server --> client) 28 | \value UNSUBSCRIBE Unsubscribe request (client --> server) 29 | \value UNSUBACK Unsubscribe acknlowedgement (server --> client) 30 | \value PINGREQ PING request (client --> server) 31 | \value PINGRESP PING response (server --> client) 32 | \value DISCONNECT Client is disconnecting (client --> server) 33 | \value RESERVED_15 Reserved; should not be used 34 | 35 | \internal 36 | */ 37 | 38 | //helper methods 39 | template 40 | QByteArray encodeNumber(T t) Q_DECL_NOEXCEPT { 41 | QByteArray array; 42 | const T tmp = qToBigEndian(t); 43 | return array.append(static_cast(static_cast(&tmp)), 44 | sizeof(T)); 45 | } 46 | 47 | QByteArray encodeData(const QByteArray &data) Q_DECL_NOEXCEPT 48 | { 49 | const int size = data.size(); 50 | if (size < std::numeric_limits::max()) { 51 | QByteArray encodedData; 52 | encodedData.append(encodeNumber(uint16_t(size))); 53 | if (size > 0) { 54 | encodedData.append(data); 55 | } 56 | return encodedData; 57 | } else { 58 | qCWarning(module) << "Data is too big: size =" << size 59 | << "maximum size=" << std::numeric_limits::max(); 60 | return QByteArray(); 61 | } 62 | } 63 | 64 | inline QByteArray encodeString(const QString &string) Q_DECL_NOEXCEPT { 65 | return encodeData(string.toUtf8()); 66 | } 67 | 68 | inline QByteArray encodeLength(int32_t length) Q_DECL_NOEXCEPT { 69 | QByteArray encodedLength; 70 | do { 71 | uint8_t digit = length % 128; 72 | length = length / 128; 73 | if (length > 0) { 74 | digit = digit | 0x80; 75 | } 76 | encodedLength.append(digit); 77 | } while (length > 0); 78 | return encodedLength; 79 | } 80 | 81 | QMqttControlPacket::QMqttControlPacket(const PacketType &controlPacketType) : 82 | QObject(), 83 | m_type(controlPacketType) 84 | {} 85 | 86 | QMqttControlPacket::PacketType QMqttControlPacket::type() const 87 | { 88 | return m_type; 89 | } 90 | 91 | QByteArray QMqttControlPacket::fixedHeader() const { 92 | QByteArray header; 93 | const uint8_t byte1 = (uint8_t(type()) << 4) | flags(); 94 | return header.append(byte1); 95 | } 96 | 97 | QByteArray QMqttControlPacket::encode() const 98 | { 99 | QByteArray packet; 100 | 101 | const QByteArray fixedHdr = fixedHeader(); 102 | const QByteArray variableHdr = variableHeader(); 103 | const QByteArray payloadData = payload(); 104 | const int remainingLength = variableHdr.size() + payloadData.size(); 105 | if (remainingLength > QMqttControlPacket::MAXIMUM_CONTROL_PACKET_SIZE) { 106 | qCWarning(module) << "Packet size too big:" << remainingLength 107 | << "maximum:" << QMqttControlPacket::MAXIMUM_CONTROL_PACKET_SIZE; 108 | return packet; 109 | } 110 | return packet 111 | .append(fixedHdr) 112 | .append(encodeLength(int32_t(remainingLength))) 113 | .append(variableHdr) 114 | .append(payloadData); 115 | } 116 | 117 | QMqttConnectControlPacket::QMqttConnectControlPacket(const QString &clientIdentifier) : 118 | QMqttControlPacket(PacketType::CONNECT), 119 | m_userName(), 120 | m_password(), 121 | m_will(), 122 | m_clean(true), 123 | m_keepAlive(30), 124 | m_clientIdentifier(clientIdentifier) 125 | { 126 | Q_ASSERT(clientIdentifier.length() < 24); 127 | } 128 | 129 | void QMqttConnectControlPacket::setCredentials(const QString &userName, const QByteArray &password) 130 | { 131 | Q_ASSERT(!userName.isEmpty()); 132 | Q_ASSERT(!password.isNull()); 133 | m_userName = userName; 134 | m_password = password; 135 | } 136 | 137 | void QMqttConnectControlPacket::setWill(const QMqttWill &will) 138 | { 139 | m_will = will; 140 | } 141 | 142 | bool QMqttConnectControlPacket::hasUserName() const 143 | { 144 | return !m_userName.isEmpty(); 145 | } 146 | 147 | bool QMqttConnectControlPacket::hasPassword() const 148 | { 149 | //use isNull iso isEmpty, so that empty password can be supplied 150 | return !m_password.isNull(); 151 | } 152 | 153 | bool QMqttConnectControlPacket::hasWill() const 154 | { 155 | return m_will.isValid(); 156 | } 157 | 158 | bool QMqttConnectControlPacket::isCleanSession() const 159 | { 160 | return m_clean; 161 | } 162 | 163 | uint8_t QMqttConnectControlPacket::flags() const 164 | { 165 | return 0x00; 166 | } 167 | 168 | QByteArray QMqttConnectControlPacket::variableHeader() const 169 | { 170 | QByteArray header; 171 | //protocol name 172 | header.append(encodeString(QStringLiteral("MQTT"))); 173 | //protocol level 174 | header.append(uint8_t(4)); 175 | //connect flags 176 | const uint8_t connectFlags = 177 | ((hasUserName() << 7) | 178 | (hasPassword() << 6) | 179 | (m_will.retain() << 5) | 180 | (uint8_t(m_will.qos()) << 3) | 181 | (hasWill() << 2) | 182 | (isCleanSession() << 1)) & 183 | 0xF7; //set lowest bit to 0 184 | header.append(connectFlags); 185 | //keep alive 186 | header.append(encodeNumber(m_keepAlive)); 187 | return header; 188 | } 189 | 190 | QByteArray QMqttConnectControlPacket::payload() const 191 | { 192 | QByteArray buffer; 193 | buffer.append(encodeString(m_clientIdentifier)); 194 | if (hasWill()) { 195 | buffer.append(encodeString(m_will.topic())); 196 | buffer.append(encodeNumber(uint16_t(m_will.message().size()))); 197 | buffer.append(m_will.message()); 198 | } 199 | if (hasUserName()) { 200 | buffer.append(encodeString(m_userName)); 201 | } 202 | if (hasPassword()) { 203 | buffer.append(encodeData(m_password)); 204 | } 205 | 206 | return buffer; 207 | } 208 | 209 | QMqttPublishControlPacket::QMqttPublishControlPacket(const QString &topicName, const QByteArray &message, 210 | QMqttProtocol::QoS qos, bool retain, 211 | uint16_t packetIdentifier) : 212 | QMqttControlPacket(PacketType::PUBLISH), 213 | m_topicName(topicName), 214 | m_message(message), 215 | m_dup(false), 216 | m_qos(qos), 217 | m_retain(retain), 218 | m_packetIdentifier(packetIdentifier) 219 | { 220 | Q_ASSERT(!topicName.isEmpty()); 221 | } 222 | 223 | uint8_t QMqttPublishControlPacket::flags() const 224 | { 225 | return (uint8_t(m_dup) << 3) | (uint8_t(m_qos) << 1) | uint8_t(m_retain); 226 | } 227 | 228 | QByteArray QMqttPublishControlPacket::variableHeader() const 229 | { 230 | QByteArray header = encodeString(m_topicName); 231 | if ((m_qos == QMqttProtocol::QoS::AT_LEAST_ONCE) || (m_qos == QMqttProtocol::QoS::EXACTLY_ONCE)) 232 | { 233 | header.append(encodeNumber(m_packetIdentifier)); 234 | } 235 | return header; 236 | } 237 | 238 | QByteArray QMqttPublishControlPacket::payload() const 239 | { 240 | return m_message; 241 | } 242 | 243 | QMqttPubAckControlPacket::QMqttPubAckControlPacket(uint16_t packetIdentifier) : 244 | QMqttControlPacket(PacketType::PUBACK), 245 | m_packetIdentifier(packetIdentifier) 246 | {} 247 | 248 | uint8_t QMqttPubAckControlPacket::flags() const 249 | { 250 | return 0x00; 251 | } 252 | 253 | QByteArray QMqttPubAckControlPacket::variableHeader() const 254 | { 255 | return encodeNumber(m_packetIdentifier); 256 | } 257 | 258 | QByteArray QMqttPubAckControlPacket::payload() const 259 | { 260 | return QByteArray(); 261 | } 262 | 263 | QMqttPubRecControlPacket::QMqttPubRecControlPacket(uint16_t packetIdentifier) : 264 | QMqttControlPacket(PacketType::PUBREC), 265 | m_packetIdentifier(packetIdentifier) 266 | {} 267 | 268 | uint8_t QMqttPubRecControlPacket::flags() const 269 | { 270 | return 0x00; 271 | } 272 | 273 | QByteArray QMqttPubRecControlPacket::variableHeader() const 274 | { 275 | return encodeNumber(m_packetIdentifier); 276 | } 277 | 278 | QByteArray QMqttPubRecControlPacket::payload() const 279 | { 280 | return QByteArray(); 281 | } 282 | 283 | QMqttPubCompControlPacket::QMqttPubCompControlPacket(uint16_t packetIdentifier) : 284 | QMqttControlPacket(PacketType::PUBCOMP), 285 | m_packetIdentifier(packetIdentifier) 286 | {} 287 | 288 | uint8_t QMqttPubCompControlPacket::flags() const 289 | { 290 | return 0x00; 291 | } 292 | 293 | QByteArray QMqttPubCompControlPacket::variableHeader() const 294 | { 295 | return encodeNumber(m_packetIdentifier); 296 | } 297 | 298 | QByteArray QMqttPubCompControlPacket::payload() const 299 | { 300 | return QByteArray(); 301 | } 302 | 303 | QMqttSubscribeControlPacket::QMqttSubscribeControlPacket(uint16_t packetIdentifier, 304 | QVector topicFilters) : 305 | QMqttControlPacket(PacketType::SUBSCRIBE), 306 | m_packetIdentifier(packetIdentifier), 307 | m_topicFilters(topicFilters) 308 | { 309 | } 310 | 311 | uint8_t QMqttSubscribeControlPacket::flags() const 312 | { 313 | return 0x02; //QoS = 1 314 | } 315 | 316 | QByteArray QMqttSubscribeControlPacket::variableHeader() const 317 | { 318 | return encodeNumber(m_packetIdentifier); 319 | } 320 | 321 | QByteArray QMqttSubscribeControlPacket::payload() const 322 | { 323 | QByteArray buffer; 324 | for (const TopicFilter &topicFilter : m_topicFilters) { 325 | buffer.append(encodeString(topicFilter.first)); 326 | const uint8_t qos = uint8_t(topicFilter.second); 327 | buffer.append(qos); 328 | } 329 | return buffer; 330 | } 331 | 332 | QMqttUnsubscribeControlPacket::QMqttUnsubscribeControlPacket(uint16_t packetIdentifier, 333 | QVector topics) : 334 | QMqttControlPacket(PacketType::UNSUBSCRIBE), 335 | m_packetIdentifier(packetIdentifier), 336 | m_topics(topics) 337 | {} 338 | 339 | uint8_t QMqttUnsubscribeControlPacket::flags() const 340 | { 341 | return 0x02; 342 | } 343 | 344 | QByteArray QMqttUnsubscribeControlPacket::variableHeader() const 345 | { 346 | return encodeNumber(m_packetIdentifier); 347 | } 348 | 349 | QByteArray QMqttUnsubscribeControlPacket::payload() const 350 | { 351 | QByteArray buffer; 352 | for (const QString &topic : m_topics) { 353 | buffer.append(encodeString(topic)); 354 | } 355 | return buffer; 356 | } 357 | 358 | QMqttPingReqControlPacket::QMqttPingReqControlPacket() : 359 | QMqttControlPacket(PacketType::PINGREQ) 360 | {} 361 | 362 | uint8_t QMqttPingReqControlPacket::flags() const 363 | { 364 | return 0x00; 365 | } 366 | 367 | QByteArray QMqttPingReqControlPacket::variableHeader() const 368 | { 369 | return QByteArray(); 370 | } 371 | 372 | QByteArray QMqttPingReqControlPacket::payload() const 373 | { 374 | return QByteArray(); 375 | } 376 | 377 | QMqttDisconnectControlPacket::QMqttDisconnectControlPacket() : 378 | QMqttControlPacket(PacketType::DISCONNECT) 379 | {} 380 | 381 | uint8_t QMqttDisconnectControlPacket::flags() const 382 | { 383 | return 0x00; 384 | } 385 | 386 | QByteArray QMqttDisconnectControlPacket::variableHeader() const 387 | { 388 | return QByteArray(); 389 | } 390 | 391 | QByteArray QMqttDisconnectControlPacket::payload() const 392 | { 393 | return QByteArray(); 394 | } 395 | -------------------------------------------------------------------------------- /src/qmqttcontrolpacket_p.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "qmqttprotocol.h" 4 | #include "qmqttwill.h" 5 | #include "qmqtt_global.h" 6 | #include 7 | #include 8 | 9 | //MQTT v3.1.1 specification: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html 10 | 11 | //For Control Packets, see 3. MQTT Control Packets in the MQTT v3.1.1 specification 12 | 13 | class QTMQTT_AUTOTEST_EXPORT QMqttControlPacket:public QObject 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | //see 2.1.1 MQTT Control Packet type in MQTT v3.1.1 specification 19 | enum class PacketType : uint8_t 20 | { 21 | RESERVED_0 = 0, //Reserved 22 | CONNECT = 1, //Client request to connect to server (client --> server) 23 | CONNACK = 2, //Connect acknowledgement (server --> client) 24 | PUBLISH = 3, //Publish message (client <--> server) 25 | PUBACK = 4, //Publish acknowledgement (client <--> server) 26 | PUBREC = 5, //Publish received (assured delivery part 1) (client <--> server) 27 | PUBREL = 6, //Publish release (assured delivery part 2) (client <--> server) 28 | PUBCOMP = 7, //Publish complete (assured delivery part 3) (client <--> server) 29 | SUBSCRIBE = 8, //Client subscribe request (client --> server) 30 | SUBACK = 9, //Subscribe acknowledgement (server --> client) 31 | UNSUBSCRIBE = 10, //Unsubscribe request (client --> server) 32 | UNSUBACK = 11, //Unsubscribe acknlowedgement (server --> client) 33 | PINGREQ = 12, //PING request (client --> server) 34 | PINGRESP = 13, //PING response (server --> client) 35 | DISCONNECT = 14, //Client is disconnecting (client --> server) 36 | RESERVED_15 = 15 //Reserved 37 | }; 38 | Q_ENUM(PacketType) 39 | static const int32_t MAXIMUM_CONTROL_PACKET_SIZE = 256 * 1024 * 1024; //256MiB 40 | 41 | QMqttControlPacket(const PacketType &controlPacketType); 42 | 43 | PacketType type() const; 44 | 45 | //fixed header without remaining length field 46 | QByteArray fixedHeader() const; 47 | virtual QByteArray variableHeader() const = 0; 48 | virtual QByteArray payload() const = 0; 49 | 50 | QByteArray encode() const; 51 | 52 | protected: 53 | virtual uint8_t flags() const = 0; 54 | 55 | private: 56 | PacketType m_type; 57 | }; 58 | 59 | class QTMQTT_AUTOTEST_EXPORT QMqttConnectControlPacket: public QMqttControlPacket 60 | { 61 | public: 62 | /** 63 | * @brief ConnectControlPacket 64 | * @param clientIdentifier A string identifying the client. This identifier should be unique 65 | * amongst all clients that connect, as this id is used, a.o., 66 | */ 67 | QMqttConnectControlPacket(const QString &clientIdentifier); 68 | 69 | void setCredentials(const QString &userName, const QByteArray &password = QByteArray()); 70 | void setWill(const QMqttWill &will); 71 | void setCleanSession(bool isClean); 72 | /** 73 | * keepAliveSecs is the maximum time interval in seconds that is permitted to elapse between the 74 | * point at which a client finishes transmitting one control packet and the point it starts 75 | * sending the next. 76 | * A keepAliveSecs value of zero has the effect of turning off the keep alive mechanism. 77 | * If the keepAliveSecs value is non-zero and the server does not receive a control packet from 78 | * the client within one and a half times the keepAliveSecs period, the server will disconnect 79 | * the network connection as if the network has failed. 80 | * The default keepAliveSecs is zero. 81 | */ 82 | void setKeepAlive(uint16_t keepAliveSecs); //if 0, no keep alive; max = 18 hours, 12 minutes and 15 seconds 83 | void setClientIdentifier(const QString &identifier); //length must be > 0 and < 24 84 | 85 | bool hasUserName() const; 86 | bool hasPassword() const; 87 | bool hasWill() const; 88 | bool isCleanSession() const; 89 | 90 | protected: 91 | uint8_t flags() const Q_DECL_OVERRIDE; 92 | QByteArray variableHeader() const Q_DECL_OVERRIDE; 93 | QByteArray payload() const Q_DECL_OVERRIDE; 94 | 95 | private: 96 | QString m_userName; 97 | QByteArray m_password; 98 | QMqttWill m_will; 99 | bool m_clean; 100 | uint16_t m_keepAlive; 101 | QString m_clientIdentifier; 102 | }; 103 | 104 | /** 105 | * @brief The PublishControlPacket class 106 | * Limitations: 107 | * ============ 108 | * The PublishControlPacket currently does not support the dup flag. 109 | * The `dup` flag is used to indicate a redelivered control packet when clean session is set 110 | * to false. The MQTT Client currently does not support sessions and hence does not redeliver 111 | * packets. 112 | * From the MQTT specification (4.4 Message delivery retry): 113 | * "Historically retransmission of Control Packets was required to overcome data loss on some 114 | * older TCP networks. This might remain a concern where MQTT 3.1.1 implementations 115 | * are to be deployed in such environments." 116 | * We assume that the MQTT client is deployed on state-of-the-art TCP networks. 117 | */ 118 | class QTMQTT_AUTOTEST_EXPORT QMqttPublishControlPacket : public QMqttControlPacket 119 | { 120 | public: 121 | /** 122 | * @brief Constructs a publish MQTT control packet with the given topicName and message. 123 | * The topic name must not be empty and must not contain wild cards. The message can be 124 | * empty. The message will be published with the given Quality of Service. 125 | * When retain is true, the message will be stored by the server. When a new client subscribes, 126 | * the last retained message, if any, will be sent to the client for each matching topic. 127 | * The packetIdentifier is a unique number assigned to the packet. It is only required 128 | * when qos != MQTTProtocol::QoS::AT_MOST_ONCE. When qos == MQTTProtocol::QoS::AT_MOST_ONCE, 129 | * the packetIdentifier will be ignored. 130 | */ 131 | QMqttPublishControlPacket(const QString &topicName, const QByteArray &message, 132 | QMqttProtocol::QoS qos, bool retain, uint16_t packetIdentifier = 0); 133 | 134 | private: 135 | const QString m_topicName; 136 | const QByteArray m_message; 137 | const bool m_dup; 138 | QMqttProtocol::QoS m_qos; 139 | const bool m_retain; 140 | uint16_t m_packetIdentifier; 141 | 142 | uint8_t flags() const Q_DECL_OVERRIDE; 143 | QByteArray variableHeader() const Q_DECL_OVERRIDE; 144 | QByteArray payload() const Q_DECL_OVERRIDE; 145 | }; 146 | 147 | class QTMQTT_AUTOTEST_EXPORT QMqttPubAckControlPacket: public QMqttControlPacket 148 | { 149 | public: 150 | QMqttPubAckControlPacket(uint16_t packetIdentifier); 151 | 152 | private: 153 | const uint16_t m_packetIdentifier; 154 | 155 | uint8_t flags() const Q_DECL_OVERRIDE; 156 | QByteArray variableHeader() const Q_DECL_OVERRIDE; 157 | QByteArray payload() const Q_DECL_OVERRIDE; 158 | }; 159 | 160 | class QTMQTT_AUTOTEST_EXPORT QMqttPubRecControlPacket: public QMqttControlPacket 161 | { 162 | public: 163 | QMqttPubRecControlPacket(uint16_t packetIdentifier); 164 | 165 | private: 166 | const uint16_t m_packetIdentifier; 167 | 168 | uint8_t flags() const Q_DECL_OVERRIDE; 169 | QByteArray variableHeader() const Q_DECL_OVERRIDE; 170 | QByteArray payload() const Q_DECL_OVERRIDE; 171 | }; 172 | 173 | class QTMQTT_AUTOTEST_EXPORT QMqttPubCompControlPacket: public QMqttControlPacket 174 | { 175 | public: 176 | QMqttPubCompControlPacket(uint16_t packetIdentifier); 177 | 178 | private: 179 | const uint16_t m_packetIdentifier; 180 | 181 | uint8_t flags() const Q_DECL_OVERRIDE; 182 | QByteArray variableHeader() const Q_DECL_OVERRIDE; 183 | QByteArray payload() const Q_DECL_OVERRIDE; 184 | }; 185 | 186 | typedef QPair TopicFilter; 187 | 188 | class QTMQTT_AUTOTEST_EXPORT QMqttSubscribeControlPacket: public QMqttControlPacket 189 | { 190 | public: 191 | QMqttSubscribeControlPacket(uint16_t packetIdentifier, QVector topicFilters); 192 | 193 | private: 194 | const uint16_t m_packetIdentifier; 195 | const QVector m_topicFilters; 196 | 197 | uint8_t flags() const Q_DECL_OVERRIDE; 198 | QByteArray variableHeader() const Q_DECL_OVERRIDE; 199 | QByteArray payload() const Q_DECL_OVERRIDE; 200 | }; 201 | 202 | class QTMQTT_AUTOTEST_EXPORT QMqttUnsubscribeControlPacket: public QMqttControlPacket 203 | { 204 | public: 205 | QMqttUnsubscribeControlPacket(uint16_t packetIdentifier, QVector topics); 206 | 207 | private: 208 | const uint16_t m_packetIdentifier; 209 | const QVector m_topics; 210 | 211 | uint8_t flags() const Q_DECL_OVERRIDE; 212 | QByteArray variableHeader() const Q_DECL_OVERRIDE; 213 | QByteArray payload() const Q_DECL_OVERRIDE; 214 | }; 215 | 216 | class QTMQTT_AUTOTEST_EXPORT QMqttPingReqControlPacket: public QMqttControlPacket 217 | { 218 | public: 219 | QMqttPingReqControlPacket(); 220 | 221 | private: 222 | uint8_t flags() const Q_DECL_OVERRIDE; 223 | QByteArray variableHeader() const Q_DECL_OVERRIDE; 224 | QByteArray payload() const Q_DECL_OVERRIDE; 225 | }; 226 | 227 | class QTMQTT_AUTOTEST_EXPORT QMqttDisconnectControlPacket: public QMqttControlPacket 228 | { 229 | public: 230 | QMqttDisconnectControlPacket(); 231 | 232 | private: 233 | uint8_t flags() const Q_DECL_OVERRIDE; 234 | QByteArray variableHeader() const Q_DECL_OVERRIDE; 235 | QByteArray payload() const Q_DECL_OVERRIDE; 236 | }; 237 | -------------------------------------------------------------------------------- /src/qmqttnetworkrequest.cpp: -------------------------------------------------------------------------------- 1 | #include "qmqttnetworkrequest.h" 2 | #include 3 | #include 4 | #include 5 | 6 | /*! 7 | \class QMqttNetworkRequest 8 | 9 | \inmodule QtMqtt 10 | 11 | \brief Implements a QNetworkRequest to be used for the MQTT protocol over WebSockets. 12 | 13 | QMqttNetworkRequest inherits from QNetworkRequest and adds the mqtt http header to the 14 | request. 15 | Other headers can be added by calling setRawHeader() or by deriving from this class. 16 | 17 | \code 18 | QMqttNetworkRequest request(QUrl("https://mymqttserver")); 19 | request.setRawHeader("Authorization", "ABCDEFGHIJKL"); 20 | \endcode 21 | 22 | The example above sets an extra "Authorization" header on the request. 23 | 24 | \note QMqttNetworkRequest does not support querystrings. This is due to a limitation in 25 | the implementation of QWebSocket. 26 | */ 27 | 28 | /*! 29 | Constructs a new QMqttNetworkRequest with the given \a url. The WebSocket sub protocol will be 30 | set to mqttv3.1 as required by the standard. 31 | */ 32 | QMqttNetworkRequest::QMqttNetworkRequest(const QUrl &url) : 33 | QNetworkRequest(url) 34 | { 35 | setRawHeader(QByteArrayLiteral("Sec-WebSocket-Protocol"), QByteArrayLiteral("mqttv3.1")); 36 | } 37 | 38 | /*! 39 | Constructs a new default QMqttNetworkRequest. The WebSocket sub protocol will be 40 | set to mqttv3.1 as required by the standard. 41 | The default constructed request has no url and hence cannot be used open a connection, 42 | unless a url has been set through the setUrl() method. 43 | */ 44 | QMqttNetworkRequest::QMqttNetworkRequest() : 45 | QNetworkRequest() 46 | { 47 | setRawHeader(QByteArrayLiteral("Sec-WebSocket-Protocol"), QByteArrayLiteral("mqttv3.1")); 48 | } 49 | -------------------------------------------------------------------------------- /src/qmqttnetworkrequest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "qmqtt_global.h" 5 | 6 | class QUrl; 7 | class QTMQTT_EXPORT QMqttNetworkRequest: public QNetworkRequest 8 | { 9 | public: 10 | QMqttNetworkRequest(const QUrl &url); 11 | QMqttNetworkRequest(); 12 | }; 13 | -------------------------------------------------------------------------------- /src/qmqttpacketparser.cpp: -------------------------------------------------------------------------------- 1 | #include "qmqttprotocol.h" 2 | #include "qmqttcontrolpacket_p.h" 3 | #include "qmqttpacketparser_p.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "logging_p.h" 12 | 13 | LoggingModule("QMqttPacketParser"); 14 | 15 | class MQTTPacket 16 | { 17 | public: 18 | MQTTPacket() 19 | { 20 | clear(); 21 | } 22 | 23 | QMqttProtocol::Error error() const { return m_error; } 24 | QString errorString() const { return m_errorString; } 25 | bool isValid() const { return m_isValid; } 26 | QMqttControlPacket::PacketType packetType() const { return m_packetType; } 27 | bool retain() const { return m_retain; } 28 | bool dup() const { return m_dup; } 29 | QMqttProtocol::QoS qos() const { return m_qos; } 30 | uint8_t flags() const { return m_flags; } 31 | int32_t remainingLength() const { return m_remainingLength; } 32 | QByteArray payload() const { return m_payload; } 33 | 34 | static MQTTPacket readPacket(const QByteArray &data); 35 | 36 | private: 37 | QMqttProtocol::Error m_error; 38 | QString m_errorString; 39 | bool m_isValid; 40 | QMqttControlPacket::PacketType m_packetType; 41 | bool m_retain; 42 | bool m_dup; 43 | QMqttProtocol::QoS m_qos; 44 | uint8_t m_flags; 45 | int32_t m_remainingLength; 46 | QByteArray m_payload; 47 | 48 | void clear() { 49 | m_error = QMqttProtocol::Error::OK; 50 | m_errorString.clear(); 51 | m_isValid = false; 52 | m_packetType = QMqttControlPacket::PacketType::RESERVED_0; 53 | m_retain = false; 54 | m_dup = false; 55 | m_qos = QMqttProtocol::QoS::AT_MOST_ONCE; 56 | m_flags = 0; 57 | m_remainingLength = 0; 58 | m_payload.clear(); 59 | } 60 | 61 | void setError(QMqttProtocol::Error error, const QString &errorString) { 62 | clear(); 63 | m_error = error; 64 | m_errorString = errorString; 65 | m_isValid = false; 66 | } 67 | 68 | static bool parseHeader(QBuffer &buffer, MQTTPacket &packet); 69 | static bool parseRemainingLength(QBuffer &buffer, MQTTPacket &packet); 70 | }; 71 | 72 | MQTTPacket MQTTPacket::readPacket(const QByteArray &data) 73 | { 74 | MQTTPacket packet; 75 | 76 | QByteArray frameData(data); 77 | QBuffer buffer(&frameData); 78 | //TODO: check on open error 79 | buffer.open(QIODevice::ReadOnly); 80 | 81 | if (parseHeader(buffer, packet) && parseRemainingLength(buffer, packet)) { 82 | if (buffer.bytesAvailable() >= packet.remainingLength()) { 83 | packet.m_payload = buffer.read(packet.remainingLength()); 84 | //TODO: check if packet.m_payload.size() == packet.remainingLength() 85 | packet.m_isValid = true; 86 | } else { 87 | packet.setError(QMqttProtocol::Error::INVALID_PACKET, 88 | QStringLiteral("Payload of packet is too small")); 89 | } 90 | } 91 | 92 | buffer.close(); 93 | 94 | return packet; 95 | } 96 | 97 | bool MQTTPacket::parseHeader(QBuffer &buffer, MQTTPacket &packet) 98 | { 99 | if (buffer.bytesAvailable() < 1) { 100 | packet.setError(QMqttProtocol::Error::INVALID_PACKET, 101 | QStringLiteral("Packet is empty")); 102 | return false; 103 | } 104 | 105 | uint8_t header = 0; 106 | //TODO: check on read error 107 | buffer.read((char *)&header, sizeof(header)); 108 | 109 | packet.m_packetType = QMqttControlPacket::PacketType(header >> 4); 110 | if ((packet.m_packetType == QMqttControlPacket::PacketType::RESERVED_0) 111 | || (packet.m_packetType >= QMqttControlPacket::PacketType::RESERVED_15)) { 112 | packet.setError(QMqttProtocol::Error::INVALID_PACKET, 113 | QStringLiteral("Invalid command detected %1").arg(uint8_t(packet.m_packetType))); 114 | return false; 115 | } 116 | packet.m_flags = header & 0x0F; 117 | packet.m_retain = bool(packet.m_flags & 0x01); 118 | const uint8_t qos = (packet.m_flags & 0x06) >> 1; 119 | packet.m_dup = bool((packet.m_flags & 0x08) >> 3); 120 | 121 | if (qos > 2) { //possible values are 0, 1, 2 122 | packet.setError(QMqttProtocol::Error::INVALID_PACKET, 123 | QStringLiteral("Invalid qos value detected %1").arg(qos)); 124 | return false; 125 | } 126 | packet.m_qos = QMqttProtocol::QoS(qos); 127 | 128 | return true; 129 | } 130 | 131 | bool MQTTPacket::parseRemainingLength(QBuffer &buffer, MQTTPacket &packet) 132 | { 133 | uint8_t current = 0; 134 | int count = 0; 135 | int32_t length = 0; 136 | int32_t multiplier = 1; 137 | 138 | while (count < 5) { 139 | if (buffer.bytesAvailable() < 1) { 140 | packet.setError(QMqttProtocol::Error::INVALID_PACKET, 141 | QStringLiteral("Packet does not contain complete length field")); 142 | return false; 143 | } 144 | //TODO: check for read errors 145 | buffer.read((char *)¤t, sizeof(uint8_t)); 146 | length += multiplier * (current & 0x7F); 147 | multiplier *= 0x80; 148 | 149 | if ((current & 0x80) == 0) break; 150 | } 151 | 152 | packet.m_remainingLength = length; 153 | 154 | return true; 155 | } 156 | 157 | template 158 | inline typename QtPrivate::QEnableIf::Value, QString>::Type toString(T e) 159 | { 160 | QMetaEnum me = QMetaEnum::fromType(); 161 | return QString::fromLatin1(me.valueToKey(int(e))); // int cast is necessary to support enum classes 162 | } 163 | 164 | template // Fallback 165 | inline typename QtPrivate::QEnableIf::Value, QString>::Type toString(const T &) 166 | { 167 | return Q_NULLPTR; 168 | } 169 | 170 | QMqttPacketParser::QMqttPacketParser() 171 | { 172 | } 173 | 174 | void QMqttPacketParser::parse(const QByteArray &packet) 175 | { 176 | const MQTTPacket mqttPacket = MQTTPacket::readPacket(packet); 177 | if (Q_UNLIKELY(!mqttPacket.isValid())) { 178 | const QString errorMessage = QStringLiteral("Error reading packet: %1 (%2).") 179 | .arg(toString(mqttPacket.error())) 180 | .arg(mqttPacket.errorString()); 181 | qCWarning(module) << errorMessage; 182 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 183 | return; 184 | } 185 | 186 | switch (mqttPacket.packetType()) { 187 | case QMqttControlPacket::PacketType::CONNACK: { 188 | parseCONNACK(mqttPacket); 189 | break; 190 | } 191 | 192 | case QMqttControlPacket::PacketType::SUBACK: { 193 | parseSUBACK(mqttPacket); 194 | break; 195 | } 196 | 197 | case QMqttControlPacket::PacketType::PUBLISH: { 198 | parsePUBLISH(mqttPacket); 199 | break; 200 | } 201 | 202 | case QMqttControlPacket::PacketType::PUBACK: { 203 | parsePUBACK(mqttPacket); 204 | break; 205 | } 206 | 207 | case QMqttControlPacket::PacketType::PUBREL: { 208 | parsePUBREL(mqttPacket); 209 | break; 210 | } 211 | case QMqttControlPacket::PacketType::UNSUBACK: { 212 | parseUNSUBACK(mqttPacket); 213 | break; 214 | } 215 | 216 | case QMqttControlPacket::PacketType::PINGRESP: { 217 | Q_EMIT pong(); 218 | break; 219 | } 220 | 221 | case QMqttControlPacket::PacketType::PUBREC: 222 | case QMqttControlPacket::PacketType::PUBCOMP: { 223 | qCWarning(module) << "PUBREC and PUBCOMP is not supported currently."; 224 | break; 225 | } 226 | 227 | case QMqttControlPacket::PacketType::RESERVED_0: 228 | case QMqttControlPacket::PacketType::RESERVED_15: 229 | case QMqttControlPacket::PacketType::CONNECT: 230 | case QMqttControlPacket::PacketType::DISCONNECT: 231 | case QMqttControlPacket::PacketType::SUBSCRIBE: 232 | case QMqttControlPacket::PacketType::UNSUBSCRIBE: 233 | case QMqttControlPacket::PacketType::PINGREQ: { 234 | break; 235 | } 236 | 237 | default: { 238 | qCWarning(module) << "Unhandled MQTT Packet type:" << mqttPacket.packetType(); 239 | break; 240 | } 241 | } 242 | } 243 | 244 | void QMqttPacketParser::parseCONNACK(const MQTTPacket &packet) 245 | { 246 | if (packet.remainingLength() != 2) { 247 | const QString errorMessage = QStringLiteral("Invalid CONNACK packet received"); 248 | qCWarning(module) << errorMessage; 249 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 250 | return; 251 | } 252 | const QByteArray payload = packet.payload(); 253 | const uint8_t connectAcknowledgeFlags = payload[0]; 254 | const uint8_t connectReturnCode = payload[1]; 255 | 256 | if ((connectAcknowledgeFlags & 0xFE) != 0) { 257 | const QString errorMessage = 258 | QStringLiteral("Invalid acknowledge flags detected: %1. Upper 7 bits must be zero.") 259 | .arg(connectAcknowledgeFlags); 260 | qCWarning(module) << errorMessage; 261 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 262 | return; 263 | } 264 | const bool sessionPresent = bool(connectAcknowledgeFlags & 0x01); 265 | 266 | if (connectReturnCode > 5) { 267 | const QString errorMessage = 268 | QStringLiteral("Invalid return code detected: %1.").arg(connectReturnCode); 269 | qCWarning(module) << errorMessage; 270 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 271 | return; 272 | } 273 | 274 | Q_EMIT connack(QMqttProtocol::Error(connectReturnCode), sessionPresent); 275 | } 276 | 277 | void QMqttPacketParser::parseSUBACK(const MQTTPacket &packet) 278 | { 279 | if (packet.remainingLength() < 2) { 280 | const QString errorMessage = QStringLiteral("Invalid SUBACK packet received"); 281 | qCWarning(module) << errorMessage; 282 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 283 | return; 284 | } 285 | const QByteArray payload = packet.payload(); 286 | const uint16_t packetIdentifier = (uint16_t(payload[0]) * 256) + uint8_t(payload[1]); //big endian 287 | QVector qos; 288 | 289 | int remainingSize = payload.size() - 2; 290 | while (remainingSize > 0) { 291 | const uint8_t returnCode = payload[--remainingSize + 2]; 292 | if (returnCode == 0x80) { 293 | qos.prepend(QMqttProtocol::QoS::INVALID); 294 | } else { 295 | if (returnCode > 2) { 296 | const QString errorMessage = 297 | QStringLiteral("Invalid return code detected in SUBACK packet: %1") 298 | .arg(returnCode); 299 | qCWarning(module) << errorMessage; 300 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 301 | return; 302 | } 303 | qos.prepend(QMqttProtocol::QoS(returnCode)); 304 | } 305 | } 306 | Q_EMIT suback(packetIdentifier, qos); 307 | } 308 | 309 | void QMqttPacketParser::parsePUBLISH(const MQTTPacket &packet) 310 | { 311 | if (packet.remainingLength() < 2) { 312 | const QString errorMessage = QStringLiteral("Invalid PUBLISH packet received"); 313 | qCWarning(module) << errorMessage; 314 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 315 | return; 316 | } 317 | QByteArray payload = packet.payload(); 318 | QBuffer buffer(&payload); 319 | 320 | if (!buffer.open(QIODevice::ReadOnly)) { 321 | const QString errorMessage 322 | = QStringLiteral("Error opening read buffer: %1") 323 | .arg(buffer.errorString()); 324 | qCWarning(module) << errorMessage; 325 | Q_EMIT error(QMqttProtocol::Error::PARSE_ERROR, errorMessage); 326 | return; 327 | } 328 | 329 | int variableHeaderLength = 0; 330 | 331 | uint16_t topicNameLength; 332 | const qint64 bytesRead = 333 | buffer.read(static_cast(static_cast(&topicNameLength)), 334 | sizeof(uint16_t)); 335 | if (bytesRead != sizeof(uint16_t)) { 336 | const QString errorMessage 337 | = QStringLiteral("Error reading from packet buffer. Bytes read = %1 <> 2") 338 | .arg(bytesRead); 339 | qCWarning(module) << errorMessage; 340 | Q_EMIT error(QMqttProtocol::Error::PARSE_ERROR, errorMessage); 341 | return; 342 | } 343 | topicNameLength = qFromBigEndian(topicNameLength); 344 | variableHeaderLength += 2; 345 | 346 | if (buffer.bytesAvailable() < topicNameLength) { 347 | const QString errorMessage 348 | = QStringLiteral("Invalid PUBLISH packet received. Invalid topic name."); 349 | qCWarning(module) << errorMessage; 350 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 351 | return; 352 | } 353 | variableHeaderLength += topicNameLength; 354 | const QString topicName = QString::fromUtf8(buffer.read(topicNameLength)); 355 | if (topicName.length() != topicNameLength) { 356 | const QString errorMessage 357 | = QStringLiteral("Error reading from packet buffer. Bytes read = %1 <> %2") 358 | .arg(bytesRead).arg(topicNameLength); 359 | qCWarning(module) << errorMessage; 360 | Q_EMIT error(QMqttProtocol::Error::PARSE_ERROR, errorMessage); 361 | return; 362 | } 363 | 364 | uint16_t packetIdentifier = 0; 365 | 366 | if (packet.qos() != QMqttProtocol::QoS::AT_MOST_ONCE) { 367 | if (packet.remainingLength() < 2) { 368 | const QString errorMessage 369 | = QStringLiteral("Invalid PUBLISH packet received. No packet identifier."); 370 | qCWarning(module) << errorMessage; 371 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 372 | return; 373 | } 374 | 375 | const qint64 bytesRead = 376 | buffer.read(static_cast(static_cast(&packetIdentifier)), 377 | sizeof(uint16_t)); 378 | if (bytesRead != sizeof(uint16_t)) { 379 | const QString errorMessage 380 | = QStringLiteral("Error reading from packet buffer. Bytes read = %1 <> 2") 381 | .arg(bytesRead); 382 | qCWarning(module) << errorMessage; 383 | Q_EMIT error(QMqttProtocol::Error::PARSE_ERROR, errorMessage); 384 | return; 385 | } 386 | packetIdentifier = qFromBigEndian(packetIdentifier); 387 | 388 | variableHeaderLength += 2; 389 | } 390 | 391 | const int32_t messageLength = packet.remainingLength() - variableHeaderLength; 392 | if (buffer.bytesAvailable() != messageLength) { 393 | const QString errorMessage 394 | = QStringLiteral("Invalid PUBLISH packet received. Payload too small."); 395 | qCWarning(module) << errorMessage; 396 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 397 | return; 398 | } 399 | const QByteArray message = buffer.read(messageLength); 400 | if (message.size() != messageLength) { 401 | const QString errorMessage = QStringLiteral("Error reading message: %1.").arg(buffer.errorString()); 402 | qCWarning(module) << errorMessage; 403 | Q_EMIT error(QMqttProtocol::Error::PARSE_ERROR, errorMessage); 404 | return; 405 | } 406 | 407 | Q_EMIT publish(packet.qos(), packetIdentifier, topicName, message); 408 | } 409 | 410 | void QMqttPacketParser::parsePUBREL(const MQTTPacket &packet) 411 | { 412 | if (packet.remainingLength() < 2) { 413 | const QString errorMessage = QStringLiteral("Invalid PUBREL packet received"); 414 | qCWarning(module) << errorMessage; 415 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 416 | return; 417 | } 418 | if (packet.flags() != 0x02) { 419 | const QString errorMessage = QStringLiteral("Invalid flags in PUBREL packet."); 420 | qCWarning(module) << errorMessage; 421 | Q_EMIT error(QMqttProtocol::Error::PROTOCOL_VIOLATION, errorMessage); 422 | return; 423 | } 424 | 425 | const QByteArray payload = packet.payload(); 426 | const uint16_t packetIdentifier = uint16_t(uint8_t(payload[0])) * 256 + uint8_t(payload[1]); 427 | 428 | Q_EMIT pubrel(packetIdentifier); 429 | } 430 | 431 | void QMqttPacketParser::parsePUBACK(const MQTTPacket &packet) 432 | { 433 | if (packet.remainingLength() < 2) { 434 | const QString errorMessage = QStringLiteral("Invalid PUBACK packet received"); 435 | qCWarning(module) << errorMessage; 436 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 437 | return; 438 | } 439 | 440 | const QByteArray payload = packet.payload(); 441 | const uint16_t packetIdentifier = uint16_t(uint8_t(payload[0])) * 256 + uint8_t(payload[1]); 442 | 443 | Q_EMIT puback(packetIdentifier); 444 | } 445 | 446 | void QMqttPacketParser::parseUNSUBACK(const MQTTPacket &packet) 447 | { 448 | if (packet.remainingLength() < 2) { 449 | const QString errorMessage = QStringLiteral("Invalid UNSUBACK packet received"); 450 | qCWarning(module) << errorMessage; 451 | Q_EMIT error(QMqttProtocol::Error::INVALID_PACKET, errorMessage); 452 | return; 453 | } 454 | 455 | const QByteArray payload = packet.payload(); 456 | const uint16_t packetIdentifier = uint16_t(uint8_t(payload[0])) * 256 + uint8_t(payload[1]); 457 | 458 | Q_EMIT unsuback(packetIdentifier); 459 | } 460 | -------------------------------------------------------------------------------- /src/qmqttpacketparser_p.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "qmqttprotocol.h" 5 | 6 | class QByteArray; 7 | class QString; 8 | class MQTTPacket; 9 | class QTMQTT_AUTOTEST_EXPORT QMqttPacketParser : public QObject 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | QMqttPacketParser(); 15 | 16 | void parse(const QByteArray &packet); 17 | 18 | Q_SIGNALS: 19 | void error(QMqttProtocol::Error error, const QString &errorMessage); 20 | void connack(QMqttProtocol::Error error, bool sessionPresent); 21 | void publish(QMqttProtocol::QoS qos, uint16_t packetIdentifier, 22 | const QString &topicName, const QByteArray &message); 23 | void puback(uint16_t packetIdentifier); 24 | void pubrel(uint16_t packetIdentifier); 25 | void suback(uint16_t packetIdentifier, QVector qos); 26 | void unsuback(uint16_t packetIdentifier); 27 | void pong(); 28 | 29 | private: 30 | void parseCONNACK(const MQTTPacket &packet); 31 | void parseSUBACK(const MQTTPacket &packet); 32 | void parsePUBLISH(const MQTTPacket &packet); 33 | void parsePUBREL(const MQTTPacket &packet); 34 | void parsePUBACK(const MQTTPacket &packet); 35 | void parseUNSUBACK(const MQTTPacket &packet); 36 | }; 37 | -------------------------------------------------------------------------------- /src/qmqttprotocol.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "qmqtt_global.h" 6 | 7 | //a class is used instead of a namespace so that the enumerations can be marked with 8 | //Q_ENUM. 9 | class QTMQTT_EXPORT QMqttProtocol: public QObject 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | //see 4.3 Quality of Service levels and protocol flows in MQTT v3.1.1 specification 15 | enum class QoS : uint8_t 16 | { 17 | AT_MOST_ONCE = 0, 18 | AT_LEAST_ONCE = 1, 19 | EXACTLY_ONCE = 2, 20 | INVALID = 3 21 | }; 22 | Q_ENUM(QoS) 23 | 24 | enum class Error { 25 | //MQTT specified errors 26 | CONNECTION_ACCEPTED = 0, 27 | CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL = 1, 28 | CONNECTION_REFUSED_IDENTIFIER_REJECTED = 2, 29 | CONNECTION_REFUSED_SERVER_UNAVAILABLE = 3, 30 | CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD = 4, 31 | CONNECTION_REFUSED_NOT_AUTHORIZED = 5, 32 | 33 | //implementation defined errors 34 | OK, 35 | INVALID_PACKET, 36 | PROTOCOL_VIOLATION, 37 | PARSE_ERROR, 38 | TIME_OUT, 39 | CONNECTION_FAILED 40 | }; 41 | Q_ENUM(Error) 42 | 43 | enum State { 44 | OFFLINE, 45 | CONNECTING, 46 | CONNECTED, 47 | DISCONNECTING 48 | }; 49 | Q_ENUM(State) 50 | }; 51 | -------------------------------------------------------------------------------- /src/qmqttprotocol.qdoc: -------------------------------------------------------------------------------- 1 | /*! 2 | \class QMqttProtocol 3 | 4 | \inmodule QtMqtt 5 | 6 | The class QMqttProtocol serves as a namespace for common MQTT constants and defines. 7 | */ 8 | 9 | /*! 10 | \enum QMqttProtocol::QoS 11 | 12 | \inmodule QtMqtt 13 | 14 | \value AT_MOST_ONCE Message will be delivered at most once; message can be lost 15 | \value AT_LEAST_ONCE Message will be delivered at least once; message can be delivered more than once 16 | \value EXACTLY_ONCE Message will be delivered exactly once 17 | \value INVALID Invalid value 18 | */ 19 | 20 | /*! 21 | \enum QMqttProtocol::Error 22 | 23 | \inmodule QtMqtt 24 | 25 | \value CONNECTION_ACCEPTED Connection is accepted by the server 26 | \value CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL Invalid protocol version requested 27 | \value CONNECTION_REFUSED_IDENTIFIER_REJECTED Invalid client identifier 28 | \value CONNECTION_REFUSED_SERVER_UNAVAILABLE The MQTT server is currently unavailable 29 | \value CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD Wrong username and/or password 30 | \value CONNECTION_REFUSED_NOT_AUTHORIZED Not authorized to connect 31 | 32 | \value OK No error 33 | \value INVALID_PACKET An malformed packet was received 34 | \value PROTOCOL_VIOLATION A protocol violation was detected 35 | \value PARSE_ERROR An error occurred during parsing an incoming packet 36 | \value TIME_OUT Operation timed out; e.g. during connection 37 | \value CONNECTION_FAILED TCP connection failed 38 | */ 39 | 40 | /*! 41 | \enum QMqttProtocol::State 42 | 43 | \inmodule QtMqtt 44 | 45 | \value OFFLINE The client is offline 46 | \value CONNECTING The client is connecting to the server; waiting for an acknowledgement 47 | \value CONNECTED The client is connected 48 | \value DISCONNECTING The client is disconnecting 49 | */ 50 | -------------------------------------------------------------------------------- /src/qmqttwill.cpp: -------------------------------------------------------------------------------- 1 | #include "qmqttwill.h" 2 | #include "qmqttwill_p.h" 3 | 4 | QMqttWillPrivate::QMqttWillPrivate() : 5 | m_topic(), m_message(), m_valid(false), m_retain(false), 6 | m_qos(QMqttProtocol::QoS::AT_MOST_ONCE) 7 | {} 8 | 9 | QMqttWillPrivate::QMqttWillPrivate(const QString &topic, const QByteArray &message, 10 | bool retain, QMqttProtocol::QoS qos) : 11 | m_topic(topic), m_message(message), m_valid(true), m_retain(retain), m_qos(qos) 12 | {} 13 | 14 | QMqttWill::QMqttWill() : 15 | d_ptr(new QMqttWillPrivate()) 16 | {} 17 | 18 | QMqttWill::QMqttWill(const QString &topic, const QByteArray &message, 19 | bool retain, QMqttProtocol::QoS qos) : 20 | d_ptr(new QMqttWillPrivate(topic, message, retain, qos)) 21 | {} 22 | 23 | QMqttWill::QMqttWill(const QMqttWill &other) : 24 | d_ptr(new QMqttWillPrivate(other.topic(), other.message(), other.retain(), other.qos())) 25 | { 26 | } 27 | 28 | QMqttWill::QMqttWill(QMqttWill &&other) : 29 | d_ptr(other.d_ptr.take()) 30 | { 31 | } 32 | 33 | QMqttWill::~QMqttWill() 34 | { 35 | } 36 | 37 | QMqttWill &QMqttWill::operator =(const QMqttWill &other) 38 | { 39 | Q_D(QMqttWill); 40 | d->m_message = other.message(); 41 | d->m_qos = other.qos(); 42 | d->m_retain = other.retain(); 43 | d->m_topic = other.topic(); 44 | d->m_valid = other.isValid(); 45 | 46 | return *this; 47 | } 48 | 49 | QMqttWill &QMqttWill::operator =(QMqttWill &&other) 50 | { 51 | swap(other); 52 | return *this; 53 | } 54 | 55 | void QMqttWill::swap(QMqttWill &other) 56 | { 57 | qSwap(d_ptr, other.d_ptr); 58 | } 59 | 60 | bool QMqttWill::isValid() const 61 | { 62 | Q_D(const QMqttWill); 63 | return d->m_valid; 64 | } 65 | 66 | bool QMqttWill::retain() const 67 | { 68 | Q_D(const QMqttWill); 69 | return d->m_retain; 70 | } 71 | 72 | QMqttProtocol::QoS QMqttWill::qos() const 73 | { 74 | Q_D(const QMqttWill); 75 | return d->m_qos; 76 | } 77 | 78 | QString QMqttWill::topic() const 79 | { 80 | Q_D(const QMqttWill); 81 | return d->m_topic; 82 | } 83 | 84 | QByteArray QMqttWill::message() const 85 | { 86 | Q_D(const QMqttWill); 87 | return d->m_message; 88 | } 89 | -------------------------------------------------------------------------------- /src/qmqttwill.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "qmqttprotocol.h" 7 | #include "qmqtt_global.h" 8 | 9 | class QMqttWillPrivate; 10 | class QTMQTT_EXPORT QMqttWill 11 | { 12 | Q_DECLARE_PRIVATE(QMqttWill) 13 | 14 | public: 15 | QMqttWill(); 16 | QMqttWill(const QString &topic, const QByteArray &message, bool retain, QMqttProtocol::QoS qos); 17 | 18 | QMqttWill(const QMqttWill &other); 19 | QMqttWill(QMqttWill &&other); 20 | 21 | ~QMqttWill(); 22 | 23 | QMqttWill &operator =(const QMqttWill &other); 24 | QMqttWill &operator =(QMqttWill &&other); 25 | 26 | void swap(QMqttWill &other); 27 | 28 | bool isValid() const; 29 | bool retain() const; 30 | QMqttProtocol::QoS qos() const; 31 | QString topic() const; 32 | QByteArray message() const; 33 | 34 | private: 35 | QScopedPointer d_ptr; 36 | }; 37 | -------------------------------------------------------------------------------- /src/qmqttwill_p.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "qmqttprotocol.h" 7 | #include "qmqtt_global.h" 8 | 9 | class QMqttWillPrivate 10 | { 11 | public: 12 | QMqttWillPrivate(); 13 | QMqttWillPrivate(const QString &topic, const QByteArray &message, bool retain, QMqttProtocol::QoS qos); 14 | 15 | public: 16 | QString m_topic; 17 | QByteArray m_message; 18 | bool m_valid; 19 | bool m_retain; 20 | QMqttProtocol::QoS m_qos; 21 | }; 22 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/Modules) 2 | 3 | include(AddQtTest) 4 | 5 | add_subdirectory(auto) 6 | -------------------------------------------------------------------------------- /tests/auto/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(mqttclient) 2 | -------------------------------------------------------------------------------- /tests/auto/mqttclient/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # qmqttnetworkrequest 2 | add_qt_test(qmqttnetworkrequest tst_qmqttnetworkrequest.cpp) 3 | target_link_libraries(qmqttnetworkrequest PUBLIC Qt5::Mqtt) 4 | 5 | # qmqttprotocol 6 | add_qt_test(qmqttprotocol tst_qmqttprotocol.cpp) 7 | target_link_libraries(qmqttprotocol PUBLIC Qt5::Mqtt) 8 | 9 | # qmqttcontrolpacket 10 | if(DEFINED PRIVATE_TESTS_ENABLED) 11 | if(${PRIVATE_TESTS_ENABLED}) 12 | add_qt_test(qmqttcontrolpacket tst_qmqttcontrolpacket.cpp) 13 | target_link_libraries(qmqttcontrolpacket PUBLIC Qt5::Mqtt) 14 | endif(${PRIVATE_TESTS_ENABLED}) 15 | endif(DEFINED PRIVATE_TESTS_ENABLED) 16 | -------------------------------------------------------------------------------- /tests/auto/mqttclient/tst_qmqttcontrolpacket.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "qmqttcontrolpacket_p.h" 6 | 7 | class tst_QMqttControlPacket: public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | tst_QMqttControlPacket(); 13 | 14 | private Q_SLOTS: 15 | // void initTestCase(); 16 | // void cleanupTestCase(); 17 | // void init(); 18 | // void cleanup(); 19 | void packetTypes(); 20 | }; 21 | 22 | tst_QMqttControlPacket::tst_QMqttControlPacket() : 23 | QObject() 24 | {} 25 | 26 | void tst_QMqttControlPacket::packetTypes() 27 | { 28 | QCOMPARE(int(QMqttControlPacket::PacketType::RESERVED_0), 0); 29 | QCOMPARE(int(QMqttControlPacket::PacketType::CONNECT), 1); 30 | QCOMPARE(int(QMqttControlPacket::PacketType::CONNACK), 2); 31 | QCOMPARE(int(QMqttControlPacket::PacketType::PUBLISH), 3); 32 | QCOMPARE(int(QMqttControlPacket::PacketType::PUBACK), 4); 33 | QCOMPARE(int(QMqttControlPacket::PacketType::PUBREC), 5); 34 | QCOMPARE(int(QMqttControlPacket::PacketType::PUBREL), 6); 35 | QCOMPARE(int(QMqttControlPacket::PacketType::PUBCOMP), 7); 36 | QCOMPARE(int(QMqttControlPacket::PacketType::SUBSCRIBE), 8); 37 | QCOMPARE(int(QMqttControlPacket::PacketType::SUBACK), 9); 38 | QCOMPARE(int(QMqttControlPacket::PacketType::UNSUBSCRIBE), 10); 39 | QCOMPARE(int(QMqttControlPacket::PacketType::UNSUBACK), 11); 40 | QCOMPARE(int(QMqttControlPacket::PacketType::PINGREQ), 12); 41 | QCOMPARE(int(QMqttControlPacket::PacketType::PINGRESP), 13); 42 | QCOMPARE(int(QMqttControlPacket::PacketType::DISCONNECT), 14); 43 | QCOMPARE(int(QMqttControlPacket::PacketType::RESERVED_15), 15); 44 | } 45 | 46 | QTEST_GUILESS_MAIN(tst_QMqttControlPacket) 47 | 48 | #include "tst_qmqttcontrolpacket.moc" 49 | 50 | -------------------------------------------------------------------------------- /tests/auto/mqttclient/tst_qmqttnetworkrequest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "qmqttnetworkrequest.h" 6 | 7 | class tst_QMqttNetworkRequest: public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | tst_QMqttNetworkRequest(); 13 | 14 | private Q_SLOTS: 15 | // void initTestCase(); 16 | // void cleanupTestCase(); 17 | // void init(); 18 | // void cleanup(); 19 | 20 | void defaultConstructor(); 21 | void constructor(); 22 | void customHeaders(); 23 | }; 24 | 25 | tst_QMqttNetworkRequest::tst_QMqttNetworkRequest() : 26 | QObject() 27 | {} 28 | 29 | void tst_QMqttNetworkRequest::defaultConstructor() 30 | { 31 | QMqttNetworkRequest request; 32 | 33 | QVERIFY(request.url().isEmpty()); 34 | } 35 | 36 | void tst_QMqttNetworkRequest::constructor() 37 | { 38 | QUrl url(QStringLiteral("http://test.mqtt.org")); 39 | QMqttNetworkRequest request(url); 40 | 41 | QCOMPARE(request.url(), url); 42 | } 43 | 44 | void tst_QMqttNetworkRequest::customHeaders() 45 | { 46 | QMqttNetworkRequest request; 47 | 48 | request.setRawHeader(QByteArrayLiteral("X-My-Custom-Header"), QByteArrayLiteral("SomeValue")); 49 | 50 | QCOMPARE(request.rawHeader("X-My-Custom-Header"), QByteArrayLiteral("SomeValue")); 51 | } 52 | 53 | QTEST_GUILESS_MAIN(tst_QMqttNetworkRequest) 54 | 55 | #include "tst_qmqttnetworkrequest.moc" 56 | -------------------------------------------------------------------------------- /tests/auto/mqttclient/tst_qmqttprotocol.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "qmqttprotocol.h" 6 | 7 | class tst_QMqttProtocol: public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | tst_QMqttProtocol(); 13 | 14 | private Q_SLOTS: 15 | // void initTestCase(); 16 | // void cleanupTestCase(); 17 | // void init(); 18 | // void cleanup(); 19 | void qos(); 20 | void standardErrors(); 21 | }; 22 | 23 | tst_QMqttProtocol::tst_QMqttProtocol() : 24 | QObject() 25 | {} 26 | 27 | void tst_QMqttProtocol::qos() 28 | { 29 | QCOMPARE(int(QMqttProtocol::QoS::AT_MOST_ONCE), 0); 30 | QCOMPARE(int(QMqttProtocol::QoS::AT_LEAST_ONCE), 1); 31 | QCOMPARE(int(QMqttProtocol::QoS::EXACTLY_ONCE), 2); 32 | QCOMPARE(int(QMqttProtocol::QoS::INVALID), 3); 33 | } 34 | 35 | void tst_QMqttProtocol::standardErrors() 36 | { 37 | QCOMPARE(int(QMqttProtocol::Error::CONNECTION_ACCEPTED), 0); 38 | QCOMPARE(int(QMqttProtocol::Error::CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL), 1); 39 | QCOMPARE(int(QMqttProtocol::Error::CONNECTION_REFUSED_IDENTIFIER_REJECTED), 2); 40 | QCOMPARE(int(QMqttProtocol::Error::CONNECTION_REFUSED_SERVER_UNAVAILABLE), 3); 41 | QCOMPARE(int(QMqttProtocol::Error::CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD), 4); 42 | QCOMPARE(int(QMqttProtocol::Error::CONNECTION_REFUSED_NOT_AUTHORIZED), 5); 43 | } 44 | 45 | QTEST_GUILESS_MAIN(tst_QMqttProtocol) 46 | 47 | #include "tst_qmqttprotocol.moc" 48 | -------------------------------------------------------------------------------- /tests/cmake/Modules/AddQtTest.cmake: -------------------------------------------------------------------------------- 1 | macro(add_qt_test TEST_NAME SRCS) 2 | find_package(Qt5Test REQUIRED) 3 | 4 | add_executable(${TEST_NAME} ${SRCS}) 5 | 6 | add_test(NAME tst_${TEST_NAME} COMMAND $) 7 | 8 | target_link_libraries(${TEST_NAME} PUBLIC Qt5::Test) 9 | endmacro() 10 | 11 | option(PRIVATE_TESTS_ENABLED "Private tests" OFF) 12 | 13 | macro(add_private_qt_test TEST_NAME SRCS) 14 | if(DEFINED PRIVATE_TESTS_ENABLED) 15 | if(${PRIVATE_TESTS_ENABLED}) 16 | add_qt_test(${TEST_NAME} ${SRCS}) 17 | endif(${PRIVATE_TESTS_ENABLED}) 18 | endif(DEFINED PRIVATE_TESTS_ENABLED) 19 | endmacro() 20 | 21 | --------------------------------------------------------------------------------