├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── CMakeModules └── CodeCoverage.cmake ├── LICENSE ├── Readme.md ├── doc ├── .gitkeep └── coverage_linux.png ├── platform ├── esp32.c ├── esp8266.c ├── linux.c ├── platform.h └── windows.c ├── src ├── buffer.h ├── debug.h ├── mqtt.c ├── mqtt.h.in ├── mqtt_internal.h ├── packet.c ├── packet.h ├── protocol.c ├── protocol.h ├── state_queue.c ├── state_queue.h ├── subscriptions.c └── subscriptions.h └── tests ├── connect_publish.c ├── connect_reconnect.c ├── connect_subscribe.c ├── connect_subscribe_qos1.c ├── connect_subscribe_qos2.c ├── decode_packet.c ├── encode_packet.c └── test.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | *.do 4 | *.gcno 5 | *.gcda 6 | *.gcov 7 | coverage/ 8 | *.test 9 | cmake-build-debug/ 10 | cmake-build-release/ 11 | build/ 12 | .idea/ 13 | src/mqtt.h 14 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceRoot}/src", 7 | "${workspaceRoot}/platform", 8 | "${workspaceRoot}/tests", 9 | "/usr/include" 10 | ], 11 | "intelliSenseMode": "clang-x64", 12 | "browse": { 13 | "path": [ 14 | "${workspaceRoot}", 15 | "${workspaceRoot}/src", 16 | "${workspaceRoot}/platform", 17 | "${workspaceRoot}/tests", 18 | "/usr/include" 19 | ] 20 | }, 21 | "limitSymbolsToIncludedHeaders": true, 22 | "databaseFilename": "", 23 | "compilerPath": "/usr/bin/clang", 24 | "cStandard": "c99", 25 | "cppStandard": "c++17", 26 | "configurationProvider": "vector-of-bool.cmake-tools", 27 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 28 | } 29 | ], 30 | "version": 4 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "(gdb) decode_packet.test", 10 | "type": "cppdbg", 11 | "request": "launch", 12 | "program": "${workspaceFolder}/build/decode_packet.test", 13 | "args": [], 14 | "stopAtEntry": false, 15 | "cwd": "${workspaceFolder}", 16 | "environment": [], 17 | "externalConsole": true, 18 | "MIMode": "gdb", 19 | "setupCommands": [ 20 | { 21 | "description": "Enable pretty-printing for gdb", 22 | "text": "-enable-pretty-printing", 23 | "ignoreFailures": true 24 | } 25 | ] 26 | }, 27 | { 28 | "name": "(gdb) encode_packet.test", 29 | "type": "cppdbg", 30 | "request": "launch", 31 | "program": "${workspaceFolder}/build/encode_packet.test", 32 | "args": [], 33 | "stopAtEntry": false, 34 | "cwd": "${workspaceFolder}", 35 | "environment": [], 36 | "externalConsole": true, 37 | "MIMode": "gdb", 38 | "setupCommands": [ 39 | { 40 | "description": "Enable pretty-printing for gdb", 41 | "text": "-enable-pretty-printing", 42 | "ignoreFailures": true 43 | } 44 | ] 45 | }, 46 | { 47 | "name": "(gdb) connect_publish.test", 48 | "type": "cppdbg", 49 | "request": "launch", 50 | "program": "${workspaceFolder}/build/connect_publish.test", 51 | "args": [], 52 | "stopAtEntry": false, 53 | "cwd": "${workspaceFolder}", 54 | "environment": [], 55 | "externalConsole": true, 56 | "MIMode": "gdb", 57 | "setupCommands": [ 58 | { 59 | "description": "Enable pretty-printing for gdb", 60 | "text": "-enable-pretty-printing", 61 | "ignoreFailures": true 62 | } 63 | ] 64 | }, 65 | { 66 | "name": "(gdb) connect_subscribe.test", 67 | "type": "cppdbg", 68 | "request": "launch", 69 | "program": "${workspaceFolder}/build/connect_subscribe.test", 70 | "args": [], 71 | "stopAtEntry": false, 72 | "cwd": "${workspaceFolder}", 73 | "environment": [], 74 | "externalConsole": true, 75 | "MIMode": "gdb", 76 | "setupCommands": [ 77 | { 78 | "description": "Enable pretty-printing for gdb", 79 | "text": "-enable-pretty-printing", 80 | "ignoreFailures": true 81 | } 82 | ] 83 | } 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "mqtt_internal.h": "c", 4 | "platform.h": "c", 5 | "packet.h": "c", 6 | "buffer.h": "c", 7 | "debug.h": "c", 8 | "protocol.h": "c", 9 | "state_queue.h": "c", 10 | "subscriptions.h": "c", 11 | "mqtt.h": "c", 12 | "tidyplatform.h": "c" 13 | }, 14 | "files.exclude": { 15 | "**/.git": true, 16 | "**/.svn": true, 17 | "**/.hg": true, 18 | "**/CVS": true, 19 | "**/.DS_Store": true, 20 | "**/*.o": true, 21 | "**/*.do": true, 22 | "**/*.gcno": true, 23 | "**/*.gcda": true, 24 | "**/*.gcov": true, 25 | "**/*.a": true, 26 | "**/build": true 27 | }, 28 | "coverage-gutters.lcovname": "coverage.info", 29 | "cmake.buildDirectory": "${workspaceRoot}/build" 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build", 8 | "type": "shell", 9 | "command": "make", 10 | "args": [ 11 | "-j", 12 | "8", 13 | "clean", 14 | "all" 15 | ], 16 | "options": { 17 | "cwd": "${workspaceRoot}/build" 18 | }, 19 | "group": { 20 | "kind": "build", 21 | "isDefault": true 22 | }, 23 | "problemMatcher": { 24 | // The problem is owned by the cpp language service. 25 | "owner": "cpp", 26 | // The file name for reported problems is relative to the opened folder. 27 | "fileLocation": "absolute", 28 | // The actual pattern to match problems in the output. 29 | "pattern": { 30 | // The regular expression. Example to match: helloWorld.c:5:3: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] 31 | "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", 32 | // The first match group matches the file name which is relative. 33 | "file": 1, 34 | // The second match group matches the line on which the problem occurred. 35 | "line": 2, 36 | // The third match group matches the column at which the problem occurred. 37 | "column": 3, 38 | // The fourth match group matches the problem's severity. Can be ignored. Then all problems are captured as errors. 39 | "severity": 4, 40 | // The fifth match group matches the message. 41 | "message": 5 42 | } 43 | } 44 | }, 45 | { 46 | "label": "CMake", 47 | "type": "shell", 48 | "command": "cmake", 49 | "args": [ 50 | ".." 51 | ], 52 | "options": { 53 | "cwd": "${workspaceRoot}/build" 54 | }, 55 | "group": "build", 56 | "problemMatcher": [ 57 | "$gcc" 58 | ] 59 | }, 60 | { 61 | "label": "Clean", 62 | "type": "shell", 63 | "command": "make", 64 | "args": [ 65 | "clean" 66 | ], 67 | "options": { 68 | "cwd": "${workspaceRoot}/build" 69 | }, 70 | "problemMatcher": [ 71 | "$gcc" 72 | ] 73 | } 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(LIBMQTT) 3 | 4 | set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules) 5 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 6 | set(CMAKE_C_STANDARD 99) 7 | 8 | # 9 | # Version 10 | # 11 | set (LIBMQTT_VERSION_MAJOR 0) 12 | set (LIBMQTT_VERSION_MINOR 1) 13 | 14 | configure_file ( 15 | "${PROJECT_SOURCE_DIR}/src/mqtt.h.in" 16 | "${PROJECT_SOURCE_DIR}/src/mqtt.h" 17 | ) 18 | 19 | # 20 | # Build flags 21 | # 22 | if (UNIX) 23 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Wno-unused-parameter -fprofile-arcs -ftest-coverage -O0 -pthread -DDEBUG=1") 24 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wall -Wno-unused-parameter -fvisibility=hidden -Os -pthread") 25 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "-lgcov") 26 | endif() # UNIX 27 | 28 | if (MSVC) 29 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DDEBUG=1 -DMSVC -Dstrdup=_strdup /std:c++latest") 30 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DMSVC -Dstrdup=_strdup /std:c++latest") 31 | endif() 32 | 33 | # 34 | # Platform abstraction 35 | # 36 | if (MSVC) 37 | set(PLATFORM_CODE 38 | platform/windows.c 39 | platform/platform.h 40 | ) 41 | find_package(Threads REQUIRED) 42 | set(PLATFORM_LIBS wsock32 ws2_32 ${CMAKE_THREAD_LIBS_INIT}) 43 | endif() 44 | 45 | if (UNIX) 46 | set(PLATFORM_CODE 47 | platform/linux.c 48 | platform/platform.h 49 | ) 50 | find_package(Threads REQUIRED) 51 | set(PLATFORM_LIBS ${CMAKE_THREAD_LIBS_INIT}) 52 | endif() 53 | 54 | # 55 | # Source files 56 | # 57 | set(mqtt-source 58 | src/mqtt.c 59 | src/mqtt_internal.h 60 | src/packet.c 61 | src/packet.h 62 | src/protocol.c 63 | src/protocol.h 64 | src/state_queue.c 65 | src/state_queue.h 66 | src/subscriptions.c 67 | src/subscriptions.h 68 | src/debug.h 69 | src/buffer.h 70 | src/mqtt.h 71 | ${PLATFORM_CODE} 72 | ) 73 | 74 | # full build library for testing 75 | add_library(mqtt-full STATIC ${mqtt-source}) 76 | target_compile_definitions(mqtt-full PRIVATE MQTT_SERVER=1 MQTT_CLIENT=1 KEEPALIVE_INTERVAL=4) 77 | set_target_properties(mqtt-full PROPERTIES PUBLIC_HEADER "src/mqtt.h") 78 | target_include_directories(mqtt-full 79 | PUBLIC 80 | $ 81 | $ 82 | PRIVATE 83 | ${CMAKE_CURRENT_SOURCE_DIR}/platform 84 | ${CMAKE_CURRENT_SOURCE_DIR}/src 85 | ) 86 | 87 | # libraries that could be deployed 88 | add_library(mqtt_static STATIC ${mqtt-source}) 89 | set_target_properties(mqtt_static PROPERTIES PUBLIC_HEADER "src/mqtt.h") 90 | target_include_directories(mqtt_static 91 | PUBLIC 92 | $ 93 | $ 94 | PRIVATE 95 | ${CMAKE_CURRENT_SOURCE_DIR}/platform 96 | ${CMAKE_CURRENT_SOURCE_DIR}/src 97 | ) 98 | 99 | add_library(mqtt SHARED ${mqtt-source}) 100 | set_target_properties(mqtt PROPERTIES 101 | PUBLIC_HEADER "src/mqtt.h" 102 | VERSION "${LIBMQTT_VERSION_MAJOR}.${LIBMQTT_VERSION_MINOR}" 103 | SOVERSION ${LIBMQTT_VERSION_MAJOR} 104 | ) 105 | target_link_libraries(mqtt ${PLATFORM_LIBS}) 106 | target_include_directories(mqtt 107 | PUBLIC 108 | $ 109 | $ 110 | PRIVATE 111 | ${CMAKE_CURRENT_SOURCE_DIR}/platform 112 | ${CMAKE_CURRENT_SOURCE_DIR}/src 113 | ) 114 | 115 | # Test executables 116 | add_executable (connect_publish.test tests/connect_publish.c) 117 | target_link_libraries (connect_publish.test mqtt-full ${PLATFORM_LIBS}) 118 | target_include_directories(connect_publish.test 119 | PRIVATE 120 | ${PROJECT_BINARY_DIR} 121 | ${CMAKE_CURRENT_SOURCE_DIR}/src 122 | ${CMAKE_CURRENT_SOURCE_DIR}/platform 123 | ) 124 | add_test(NAME ConnectPublish COMMAND ${PROJECT_BINARY_DIR}/connect_publish.test) 125 | 126 | add_executable (connect_subscribe.test tests/connect_subscribe.c) 127 | target_link_libraries (connect_subscribe.test mqtt-full ${PLATFORM_LIBS}) 128 | target_include_directories(connect_subscribe.test 129 | PRIVATE 130 | ${PROJECT_BINARY_DIR} 131 | ${CMAKE_CURRENT_SOURCE_DIR}/src 132 | ${CMAKE_CURRENT_SOURCE_DIR}/platform 133 | ) 134 | add_test(NAME ConnectSubscribe COMMAND ${PROJECT_BINARY_DIR}/connect_subscribe.test) 135 | 136 | add_executable (connect_subscribe_qos1.test tests/connect_subscribe_qos1.c) 137 | target_link_libraries (connect_subscribe_qos1.test mqtt-full ${PLATFORM_LIBS}) 138 | target_include_directories(connect_subscribe_qos1.test 139 | PRIVATE 140 | ${PROJECT_BINARY_DIR} 141 | ${CMAKE_CURRENT_SOURCE_DIR}/src 142 | ${CMAKE_CURRENT_SOURCE_DIR}/platform 143 | ) 144 | add_test(NAME ConnectSubscribeQos1 COMMAND ${PROJECT_BINARY_DIR}/connect_subscribe_qos1.test) 145 | 146 | add_executable (connect_subscribe_qos2.test tests/connect_subscribe_qos2.c) 147 | target_link_libraries (connect_subscribe_qos2.test mqtt-full ${PLATFORM_LIBS}) 148 | target_include_directories(connect_subscribe_qos2.test 149 | PRIVATE 150 | ${PROJECT_BINARY_DIR} 151 | ${CMAKE_CURRENT_SOURCE_DIR}/src 152 | ${CMAKE_CURRENT_SOURCE_DIR}/platform 153 | ) 154 | add_test(NAME ConnectSubscribeQos2 COMMAND ${PROJECT_BINARY_DIR}/connect_subscribe_qos2.test) 155 | 156 | add_executable (connect_reconnect.test tests/connect_reconnect.c) 157 | target_link_libraries (connect_reconnect.test mqtt-full ${PLATFORM_LIBS}) 158 | target_include_directories(connect_reconnect.test 159 | PRIVATE 160 | ${PROJECT_BINARY_DIR} 161 | ${CMAKE_CURRENT_SOURCE_DIR}/src 162 | ${CMAKE_CURRENT_SOURCE_DIR}/platform 163 | ) 164 | add_test(NAME ConnectReconnect COMMAND ${PROJECT_BINARY_DIR}/connect_reconnect.test) 165 | 166 | add_executable (decode_packet.test tests/decode_packet.c) 167 | target_link_libraries (decode_packet.test mqtt-full ${PLATFORM_LIBS}) 168 | target_include_directories(decode_packet.test 169 | PRIVATE 170 | ${PROJECT_BINARY_DIR} 171 | ${CMAKE_CURRENT_SOURCE_DIR}/src 172 | ) 173 | add_test(NAME DecodePacket COMMAND ${PROJECT_BINARY_DIR}/decode_packet.test) 174 | 175 | add_executable (encode_packet.test tests/encode_packet.c) 176 | target_link_libraries (encode_packet.test mqtt-full ${PLATFORM_LIBS}) 177 | target_include_directories(encode_packet.test 178 | PRIVATE 179 | ${PROJECT_BINARY_DIR} 180 | ${CMAKE_CURRENT_SOURCE_DIR}/src 181 | ) 182 | add_test(NAME EncodePacket COMMAND ${PROJECT_BINARY_DIR}/encode_packet.test) 183 | 184 | include(CTest) 185 | enable_testing() 186 | 187 | # check target, builds and runs all tests as the 'test' target is not able to build 188 | add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS 189 | connect_publish.test 190 | connect_subscribe.test 191 | decode_packet.test 192 | encode_packet.test 193 | ) 194 | 195 | # code coverage target 196 | if(CMAKE_COMPILER_IS_GNUCXX) 197 | include(CodeCoverage) 198 | setup_target_for_coverage(coverage check coverage) 199 | endif() 200 | 201 | # 202 | # Install 203 | # 204 | 205 | install(TARGETS mqtt mqtt_static 206 | RUNTIME DESTINATION bin 207 | PUBLIC_HEADER DESTINATION include/mqtt 208 | LIBRARY DESTINATION lib CONFIGURATIONS Release 209 | ARCHIVE DESTINATION lib CONFIGURATIONS Release) 210 | -------------------------------------------------------------------------------- /CMakeModules/CodeCoverage.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 - 2015, Lars Bilke 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without modification, 5 | # are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its contributors 15 | # may be used to endorse or promote products derived from this software without 16 | # specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # 30 | # 31 | # 2012-01-31, Lars Bilke 32 | # - Enable Code Coverage 33 | # 34 | # 2013-09-17, Joakim Söderberg 35 | # - Added support for Clang. 36 | # - Some additional usage instructions. 37 | # 38 | # USAGE: 39 | 40 | # 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: 41 | # http://stackoverflow.com/a/22404544/80480 42 | # 43 | # 1. Copy this file into your cmake modules path. 44 | # 45 | # 2. Add the following line to your CMakeLists.txt: 46 | # INCLUDE(CodeCoverage) 47 | # 48 | # 3. Set compiler flags to turn off optimization and enable coverage: 49 | # SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 50 | # SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 51 | # 52 | # 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target 53 | # which runs your test executable and produces a lcov code coverage report: 54 | # Example: 55 | # SETUP_TARGET_FOR_COVERAGE( 56 | # my_coverage_target # Name for custom target. 57 | # test_driver # Name of the test driver target that runs the tests. 58 | # coverage # Name of output directory. 59 | # ) 60 | # 61 | # 4. Build a Debug build: 62 | # cmake -DCMAKE_BUILD_TYPE=Debug .. 63 | # make 64 | # make my_coverage_target 65 | # 66 | # 67 | 68 | # Check prereqs 69 | FIND_PROGRAM( GCOV_PATH gcov ) 70 | FIND_PROGRAM( LCOV_PATH lcov ) 71 | FIND_PROGRAM( GENHTML_PATH genhtml ) 72 | 73 | IF(NOT GCOV_PATH) 74 | MESSAGE(FATAL_ERROR "gcov not found! Aborting...") 75 | ENDIF() # NOT GCOV_PATH 76 | 77 | IF("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") 78 | IF("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) 79 | MESSAGE(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") 80 | ENDIF() 81 | ELSEIF(NOT CMAKE_COMPILER_IS_GNUCXX) 82 | MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") 83 | ENDIF() # CHECK VALID COMPILER 84 | 85 | SET(CMAKE_CXX_FLAGS_COVERAGE 86 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 87 | CACHE STRING "Flags used by the C++ compiler during coverage builds." 88 | FORCE ) 89 | SET(CMAKE_C_FLAGS_COVERAGE 90 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 91 | CACHE STRING "Flags used by the C compiler during coverage builds." 92 | FORCE ) 93 | SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE 94 | "" 95 | CACHE STRING "Flags used for linking binaries during coverage builds." 96 | FORCE ) 97 | SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE 98 | "" 99 | CACHE STRING "Flags used by the shared libraries linker during coverage builds." 100 | FORCE ) 101 | MARK_AS_ADVANCED( 102 | CMAKE_CXX_FLAGS_COVERAGE 103 | CMAKE_C_FLAGS_COVERAGE 104 | CMAKE_EXE_LINKER_FLAGS_COVERAGE 105 | CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) 106 | 107 | IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) 108 | MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) 109 | ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" 110 | 111 | 112 | # Param _targetname The name of new the custom make target 113 | # Param _testtarget The name of the target which runs the tests. 114 | # MUST return ZERO always, even on errors. 115 | # If not, no coverage report will be created! 116 | # Param _outputname lcov output is generated as _outputname.info 117 | # HTML report is generated in _outputname/index.html 118 | FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testtarget _outputname) 119 | 120 | IF(NOT LCOV_PATH) 121 | MESSAGE(FATAL_ERROR "lcov not found! Aborting...") 122 | ENDIF() # NOT LCOV_PATH 123 | 124 | IF(NOT GENHTML_PATH) 125 | MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") 126 | ENDIF() # NOT GENHTML_PATH 127 | 128 | SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info") 129 | SET(coverage_cleaned "${coverage_info}.cleaned") 130 | 131 | # Setup target 132 | ADD_CUSTOM_TARGET(${_targetname}_clean 133 | 134 | # Cleanup lcov 135 | ${LCOV_PATH} --directory . --zerocounters 136 | 137 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 138 | COMMENT "Resetting code coverage counters to zero." 139 | ) 140 | 141 | ADD_CUSTOM_TARGET(${_targetname} 142 | 143 | # Capturing lcov counters and generating report 144 | COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info} 145 | COMMAND ${LCOV_PATH} --remove ${coverage_info} '${PROJECT_SOURCE_DIR}/tests/*' '/usr/*' --output-file ${coverage_cleaned} 146 | COMMAND ${GENHTML_PATH} -o ${_outputname} ${coverage_cleaned} 147 | COMMAND ${CMAKE_COMMAND} -E remove ${coverage_cleaned} 148 | 149 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 150 | COMMENT "Processing code coverage counters and generating report." 151 | ) 152 | 153 | add_dependencies(${_testtarget} ${_targetname}_clean) 154 | add_dependencies(${_targetname} ${_testtarget}) 155 | 156 | # Show info where to find the report 157 | ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD 158 | COMMAND ; 159 | COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." 160 | ) 161 | 162 | ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Johannes Schriewer 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | MQTT library for multiple platforms including embedded targets. 2 | 3 | ## Goals for this library 4 | 5 | 1. Readable code, modern C 6 | 2. Simple and fool-proof API 7 | 3. High test coverage (aim is >85% of all lines tested) 8 | 4. Suitable for embedded or bigger targets (no static allocation though) 9 | 10 | ## Current state 11 | 12 | - MQTT connection as a client is working 13 | - All packet types are implemented 14 | - Supports MQTT 3.1.1 (aka. protocol level 4) 15 | - Test have a coverage of about 86.5% (lines) and 100% (functions), untested code is just bail-out error handling for fatal errors (usually programming errors or network failure) 16 | - Platform support for Linux and Windows is working 17 | - Builds on Linux (GCC and Clang) and Windows (MSVC and Clang/c2) 18 | 19 | ![Coverage report Linux](doc/coverage_linux.png) 20 | 21 | ## TODO 22 | 23 | - [ ] Running in MQTT Broker mode (very low prio) 24 | - [ ] Implement Protocol level 3 (low prio) 25 | - [ ] Implement Draft Protocol level 5 26 | - [ ] Support ESP8266 (RTOS) 27 | - [ ] Support ESP32 (IDF) 28 | 29 | And no, I will not do an Arduino port. 30 | 31 | ## How to use 32 | 33 | ### Building on Linux 34 | 35 | Requirements: 36 | 37 | - CMake 38 | - GCC 39 | 40 | 41 | 1. Checkout the repo 42 | 2. Run cmake: 43 | ```bash 44 | mkdir build 45 | cd build 46 | cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/ .. 47 | ``` 48 | 3. Build the library 49 | ```bash 50 | cd build 51 | make all 52 | ``` 53 | 4. Install/Make a Distribution (Only works in `RELEASE` mode) 54 | ```bash 55 | make DESTDIR=/path/to/put/it install 56 | ``` 57 | The dynamic and static libs will be placed in the `lib/` sub-dir of that folder and the include in `include/mqtt/mqtt.h` 58 | 59 | ### Testing on Linux 60 | 61 | Requirements: 62 | 63 | - Local MQTT broker, simplest is mosquitto (can be run by calling `mosquitto -v` in a terminal for debug output) 64 | - `Gcov` and `Lcov` for coverage reports, be aware you'll need a git version of lcov for newer GCC versions (> 6.0.0) 65 | 66 | #### Running the tests: 67 | 68 | ```bash 69 | cd build 70 | make check 71 | ``` 72 | 73 | #### Building a coverage report 74 | 75 | **Attention:** Only works in `DEBUG` mode, switch like this: 76 | 77 | ```bash 78 | cd build 79 | cmake -DCMAKE_BUILD_TYPE=Debug .. 80 | ``` 81 | 82 | Building the report: 83 | 84 | ```bash 85 | cd build 86 | make coverage 87 | ``` 88 | 89 | Be aware to create the coverage report the tests must run, so you'll need the MQTT broker. 90 | 91 | ### Building on Windows 92 | 93 | Requirements: 94 | 95 | - `cmake` in path 96 | - Visual Studio (Community Edition is sufficient) 97 | - Checked out repo 98 | 99 | #### With Visual Studio (MSVC) 100 | 101 | 1. Run cmake: 102 | ```powershell 103 | mkdir build 104 | cd build 105 | cmake -DCMAKE_BUILD_TYPE=Release .. -G "Visual Studio 15 2017 Win64" 106 | ``` 107 | 2. Build the library 108 | ```powershell 109 | cd build 110 | cmake --build c:/absolute/path/to/build --config Debug --target ALL_BUILD -- /m /property:GenerateFullPaths=true 111 | ``` 112 | 113 | The dynamic and static libs will be placed in the `/` sub-dir of that folder and the include is in `src/mqtt.h` 114 | Alternatively open the Visual Studio solution in the `build` dir in Visual Studio. 115 | 116 | **Attention:** As MSVC is not C99 compliant the tests for encode and decode packet will not work. Use `clang` for that. 117 | 118 | #### With Visual Studio (clang) 119 | 120 | Requirements: 121 | 122 | - Clang/C2 installed (experimental option in Visual Studio Installer) 123 | 124 | 1. Run cmake: 125 | ```powershell 126 | mkdir build 127 | cd build 128 | cmake -DCMAKE_BUILD_TYPE=Release .. -G "Visual Studio 15 2017 Win64" -T v141_clang_c2 129 | ``` 130 | 2. Build the library 131 | ```powershell 132 | cd build 133 | cmake --build c:/absolute/path/to/build --config Debug --target ALL_BUILD -- /m /property:GenerateFullPaths=true 134 | ``` 135 | 136 | The dynamic and static libs will be placed in the `/` sub-dir of that folder and the include is in `src/mqtt.h` 137 | Alternatively open the Visual Studio solution in the `build` dir in Visual Studio. 138 | 139 | ### Testing on Windows 140 | 141 | Requirements: 142 | 143 | - Local MQTT broker, simplest is mosquitto (can be run by calling `mosquitto -v` in a terminal for debug output) 144 | 145 | **Attention:** Coverage report is not possible on Windows currently 146 | 147 | 1. Build everything 148 | ```powershell 149 | cd build 150 | cmake --build c:/absolute/path/to/build --config Debug --target ALL_BUILD -- /m /property:GenerateFullPaths=true 151 | ``` 152 | 2. Run tests 153 | ```powershell 154 | cd build 155 | ctest -C Debug -T test --output-on-failure 156 | ``` 157 | 158 | ### API 159 | 160 | #### Configuration 161 | 162 | Create an instance of the config struct as following: 163 | 164 | ```c 165 | MQTTConfig config = { 0 }; // IMPORTANT: zero out the struct before usage! (this is C99 style) 166 | 167 | config.client_id = "my_client"; 168 | config.hostname = "localhost"; 169 | ``` 170 | 171 | The snippet above is the absolute minimum you'll need, but there's more to configure if you want: 172 | 173 | ``` 174 | typedef struct { 175 | char *hostname; /**< Hostname to connect to, will do DNS resolution */ 176 | uint16_t port; /**< Port the broker listens on, set to 0 for 1883 default */ 177 | 178 | char *client_id; /**< Client identification max 23 chars */ 179 | bool clean_session; /**< Set to true to reset the session on reconnect */ 180 | 181 | char *username; /**< User name, set to NULL to connect anonymously */ 182 | char *password; /**< Password, set to NULL to connect without password */ 183 | 184 | char *last_will_topic; /**< last will topic that is automatically published on connection loss */ 185 | char *last_will_message; /**< last will message */ 186 | bool last_will_retain; /**< tell server to retain last will message */ 187 | } MQTTConfig; 188 | ``` 189 | 190 | #### Connect to MQTT broker 191 | 192 | ```c 193 | MQTTHandle *mqtt_connect(MQTTConfig *config, MQTTEventHandler callback, void *callback_context, MQTTErrorHandler error_callback); 194 | ``` 195 | - `config`: MQTT configuration 196 | - `callback`: Callback function to call on successful connect 197 | - `callback_context`: Just a void pointer that is stored and given to the callback on connection 198 | - `error_callback`: Error handler callback 199 | - Returns handle to mqtt connection or NULL on error 200 | 201 | If the error handler is called with Host not found or Connection refused, 202 | the handler is in charge of freeing the handle by returning true 203 | or re-trying by changing settings and calling mqtt_reconnect() and returning false 204 | 205 | The error handler should be defined like this: 206 | 207 | ```c 208 | bool error_handler(MQTTHandle *handle, MQTTConfig *config, MQTTErrorCode code) { 209 | 210 | return true; // kill the handle, return false to keep it 211 | } 212 | ``` 213 | 214 | The event handler looks like this: 215 | 216 | ```c 217 | void event_handler(MQTTHandle *handle, void *context) { 218 | // The context pointer is the same as the time when you called `mqtt_connect` 219 | } 220 | ``` 221 | 222 | #### Re-Connect to MQTT broker 223 | 224 | ```c 225 | MQTTStatus mqtt_reconnect(MQTTHandle *handle, MQTTEventHandler callback, void *callback_context); 226 | ``` 227 | 228 | - `handle`: MQTT Handle from `mqtt_connect` 229 | - `callback`: Callback to call when reconnect was successful 230 | - `callback_context`: Pointer to give the callback 231 | - Returns status code 232 | 233 | Usually called in the MQTTErrorHandler callback, if called on a working 234 | connection the connection will be disconnected before reconnecting. 235 | 236 | If there were registered subscriptions they will be re-instated after 237 | a successful reconnect. 238 | 239 | #### Subscribe to a topic 240 | 241 | ```c 242 | MQTTStatus mqtt_subscribe(MQTTHandle *handle, char *topic, MQTTQosLevel qos_level, MQTTPublishEventHandler callback); 243 | ``` 244 | - `handle`: MQTT Handle from `mqtt_connect` 245 | - `topic`: Topic to subscribe 246 | - `qos_level`: Maximum QoS level to subscribe for 247 | - `callback`: Callback function to call when receiving something for that topic 248 | - Returns status code 249 | 250 | You may use the same callback function for multiple topics. 251 | The callback function looks like this: 252 | 253 | ```c 254 | void publish_handler(MQTTHandle *handle, char *topic, char *payload) { 255 | // the payload is zero terminated by the library, so it can be used as 256 | // a standard c-string immediately. 257 | } 258 | ``` 259 | 260 | #### Un-Subscribe from a topic 261 | 262 | ```c 263 | MQTTStatus mqtt_unsubscribe(MQTTHandle *handle, char *topic); 264 | ``` 265 | 266 | - `handle`: MQTT Handle from `mqtt_connect` 267 | - `topic`: Topic to unsubscribe 268 | - Returns status code 269 | 270 | #### Publish something to the broker 271 | 272 | ```c 273 | MQTTStatus mqtt_publish(MQTTHandle *handle, char *topic, char *payload, MQTTQosLevel qos_level, MQTTPublishEventCallback callback); 274 | ``` 275 | 276 | - `handle`: MQTT Handle from `mqtt_connect` 277 | - `topic`: Topic to publish to 278 | - `payload`: Message payload to publish 279 | - `qos_level`: QoS Level for the publish (0 = Fire and forget, 1 = At least once, 2 = One time for sure) 280 | - `callback`: Callback function that is called when publish cleared the QoS handlers 281 | - Returns status code 282 | 283 | This uses a c-string as the payload, theoretically the protocol would allow for binary payloads, but this is currently 284 | not supported. 285 | 286 | #### Disconnect from MQTT broker 287 | 288 | ```c 289 | MQTTStatus mqtt_disconnect(MQTTHandle *handle, MQTTEventHandler callback, void *callback_context); 290 | ``` 291 | 292 | - `handle`: MQTT Handle from `mqtt_connect` 293 | - Returns status code 294 | 295 | Attention: do not use the handle after calling this function, 296 | all resources will be freed, this handle is now invalid! 297 | -------------------------------------------------------------------------------- /doc/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunkelstern/libmqtt/a644c1c4d413825637f068ba6690dcdc953b8c05/doc/.gitkeep -------------------------------------------------------------------------------- /doc/coverage_linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunkelstern/libmqtt/a644c1c4d413825637f068ba6690dcdc953b8c05/doc/coverage_linux.png -------------------------------------------------------------------------------- /platform/esp32.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunkelstern/libmqtt/a644c1c4d413825637f068ba6690dcdc953b8c05/platform/esp32.c -------------------------------------------------------------------------------- /platform/esp8266.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunkelstern/libmqtt/a644c1c4d413825637f068ba6690dcdc953b8c05/platform/esp8266.c -------------------------------------------------------------------------------- /platform/linux.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "debug.h" 15 | #include "mqtt_internal.h" 16 | #include "platform.h" 17 | 18 | const size_t max_receive_buffer_size = 4 * 4096; // 16 KB 19 | 20 | #define MAX_TASKS 16 21 | #define MAX_TIMERS 5 22 | 23 | typedef struct { 24 | PlatformTimerCallback callback; 25 | int status; 26 | int interval; 27 | 28 | } PlatformTimer; 29 | 30 | struct _PlatformData { 31 | pthread_t tasks[MAX_TASKS]; 32 | PlatformTimer timers[MAX_TIMERS]; 33 | int timer_task; 34 | int sock; 35 | }; 36 | 37 | PlatformTaskFunc(timer_task) { 38 | MQTTHandle *handle = (MQTTHandle *)context; 39 | while (1) { 40 | platform_sleep(1000); 41 | 42 | bool active = false; 43 | for (uint8_t i = 0; i < MAX_TIMERS; i++) { 44 | PlatformTimer *timer = &handle->platform->timers[i]; 45 | 46 | if (timer->callback != NULL) { 47 | timer->status--; 48 | 49 | if (timer->status == 0) { 50 | timer->callback(handle, i); 51 | timer->status = timer->interval; 52 | } 53 | 54 | active = true; 55 | } 56 | } 57 | 58 | if (!active) { 59 | return NULL; 60 | } 61 | } 62 | } 63 | 64 | PlatformStatusCode platform_init(MQTTHandle *handle) { 65 | handle->platform = (PlatformData *)calloc(1, sizeof(struct _PlatformData)); 66 | handle->platform->sock = -1; 67 | handle->platform->timer_task = -1; 68 | if (handle->platform) { 69 | return PlatformStatusOk; 70 | } 71 | 72 | return PlatformStatusError; 73 | } 74 | 75 | PlatformStatusCode platform_release(MQTTHandle *handle) { 76 | PlatformData *p = handle->platform; 77 | 78 | // shut down all timers 79 | if (p->timer_task >= 0) { 80 | for (uint8_t free_timer = 0; free_timer < MAX_TIMERS; free_timer++) { 81 | PlatformStatusCode ret = platform_destroy_timer(handle, free_timer); 82 | if (ret != PlatformStatusOk) { 83 | DEBUG_LOG("Could not shut down all timers"); 84 | return PlatformStatusError; 85 | } 86 | } 87 | } 88 | 89 | // check if there are tasks running 90 | for (uint8_t free_task = 0; free_task < MAX_TASKS; free_task++) { 91 | if (p->tasks[free_task] != 0) { 92 | DEBUG_LOG("Cannot free platform handle, there are tasks running!"); 93 | return PlatformStatusError; 94 | } 95 | } 96 | 97 | free(handle->platform); 98 | return PlatformStatusOk; 99 | } 100 | 101 | PlatformStatusCode platform_run_task(MQTTHandle *handle, int *task_handle, PlatformTask callback) { 102 | PlatformData *p = handle->platform; 103 | uint8_t free_task = 0; 104 | 105 | for (free_task = 0; free_task < MAX_TASKS; free_task++) { 106 | if (p->tasks[free_task] == 0) { 107 | break; 108 | } 109 | } 110 | if (free_task == MAX_TASKS) { 111 | return PlatformStatusError; 112 | } 113 | 114 | if (pthread_create(&p->tasks[free_task], NULL, (void *(*)(void *))callback, (void *)handle)) { 115 | return PlatformStatusError; 116 | } 117 | 118 | *task_handle = free_task; 119 | return PlatformStatusOk; 120 | } 121 | 122 | PlatformStatusCode platform_cleanup_task(MQTTHandle *handle, int task_handle) { 123 | PlatformData *p = handle->platform; 124 | 125 | if ((task_handle < 0) || (task_handle >= MAX_TASKS)) { 126 | return PlatformStatusError; 127 | } 128 | 129 | if (p->tasks[task_handle]) { 130 | pthread_join(p->tasks[task_handle], NULL); 131 | p->tasks[task_handle] = 0; 132 | } 133 | return PlatformStatusOk; 134 | } 135 | 136 | PlatformStatusCode platform_resolve_host(char *hostname , char *ip) { 137 | struct addrinfo hints, *servinfo; 138 | struct sockaddr_in *h; 139 | 140 | memset(&hints, 0, sizeof(struct addrinfo)); 141 | hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6 142 | hints.ai_socktype = SOCK_STREAM; 143 | 144 | int ret = getaddrinfo(hostname, NULL, &hints, &servinfo); 145 | if (ret != 0) { 146 | DEBUG_LOG("Resolving host failed: %s", gai_strerror(ret)); 147 | return PlatformStatusError; 148 | } 149 | 150 | // FIXME: we do not try to connect here, perhaps return a list or just the first 151 | // loop through all the results and connect to the first we can 152 | for(struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) { 153 | h = (struct sockaddr_in *)p->ai_addr; 154 | strcpy(ip , inet_ntoa( h->sin_addr ) ); 155 | } 156 | 157 | freeaddrinfo(servinfo); // all done with this structure 158 | return PlatformStatusOk; 159 | } 160 | 161 | PlatformStatusCode platform_connect(MQTTHandle *handle) { 162 | PlatformData *p = handle->platform; 163 | 164 | int ret; 165 | struct sockaddr_in servaddr; 166 | memset(&servaddr, 0, sizeof(servaddr)); 167 | 168 | p->sock = socket(AF_INET, SOCK_STREAM, 0); 169 | servaddr.sin_family = AF_INET; 170 | servaddr.sin_port = htons(handle->config->port); 171 | 172 | char ip[40]; 173 | if (platform_resolve_host(handle->config->hostname, ip) != PlatformStatusOk) { 174 | bool free_handle = handle->error_handler(handle, handle->config, MQTT_Error_Host_Not_Found); 175 | if (free_handle) { 176 | mqtt_free(handle); 177 | } 178 | DEBUG_LOG("Resolving hostname failed: %s", strerror(errno)); 179 | close(p->sock); 180 | return PlatformStatusError; 181 | } 182 | ret = inet_pton(AF_INET, ip, &(servaddr.sin_addr)); 183 | if (ret == 0) { 184 | bool free_handle = handle->error_handler(handle, handle->config, MQTT_Error_Host_Not_Found); 185 | if (free_handle) { 186 | mqtt_free(handle); 187 | } 188 | DEBUG_LOG("Converting to servaddr failed: %s", strerror(errno)); 189 | close(p->sock); 190 | return PlatformStatusError; 191 | } 192 | 193 | ret = connect(p->sock, (struct sockaddr *)&servaddr, sizeof(servaddr)); 194 | if (ret != 0) { 195 | bool free_handle = handle->error_handler(handle, handle->config, MQTT_Error_Connection_Refused); 196 | if (free_handle) { 197 | mqtt_free(handle); 198 | } 199 | DEBUG_LOG("Connection failed: %s", strerror(errno)); 200 | close(p->sock); 201 | return PlatformStatusError; 202 | } 203 | 204 | return PlatformStatusOk; 205 | } 206 | 207 | PlatformStatusCode platform_read(MQTTHandle *handle, Buffer *buffer) { 208 | PlatformData *p = handle->platform; 209 | 210 | while (1) { 211 | ssize_t num_bytes = read(p->sock, &buffer->data[buffer->position], buffer_free_space(buffer)); 212 | if (num_bytes == 0) { 213 | /* Socket closed, coordinated shutdown */ 214 | DEBUG_LOG("Socket closed"); 215 | return PlatformStatusError; 216 | } else if (num_bytes < 0) { 217 | if ((errno == EINTR) || (errno == EAGAIN)) { 218 | continue; 219 | } 220 | 221 | /* Some error occured */ 222 | return PlatformStatusError; 223 | } 224 | 225 | buffer->position += num_bytes; 226 | return PlatformStatusOk; 227 | } 228 | } 229 | 230 | PlatformStatusCode platform_write(MQTTHandle *handle, Buffer *buffer) { 231 | PlatformData *p = handle->platform; 232 | 233 | while (!buffer_eof(buffer)) { 234 | ssize_t bytes = write(p->sock, buffer->data + buffer->position, buffer_free_space(buffer)); 235 | if (bytes <= 0) { 236 | return PlatformStatusError; 237 | } 238 | buffer->position += bytes; 239 | } 240 | 241 | return PlatformStatusOk; 242 | } 243 | 244 | PlatformStatusCode platform_disconnect(MQTTHandle *handle) { 245 | PlatformData *p = handle->platform; 246 | if (p->sock >= 0) { 247 | close(p->sock); 248 | p->sock = -1; 249 | } 250 | 251 | return PlatformStatusOk; 252 | } 253 | 254 | PlatformStatusCode platform_create_timer(MQTTHandle *handle, int interval, int *timer_handle, PlatformTimerCallback callback) { 255 | PlatformData *p = handle->platform; 256 | uint8_t free_timer = 0; 257 | 258 | for (free_timer = 0; free_timer < MAX_TIMERS; free_timer++) { 259 | // DEBUG_LOG("Timer %d: %s", free_timer, p->timers[free_timer].callback ? "Occupied" : "Free"); 260 | if (p->timers[free_timer].callback == NULL) { 261 | break; 262 | } 263 | } 264 | if (free_timer == MAX_TASKS) { 265 | return PlatformStatusError; 266 | } 267 | 268 | PlatformTimer *timer = &p->timers[free_timer]; 269 | 270 | timer->callback = callback; 271 | timer->status = interval; 272 | timer->interval = interval; 273 | 274 | if (p->timer_task < 0) { 275 | PlatformStatusCode ret = platform_run_task(handle, &p->timer_task, timer_task); 276 | if (ret != PlatformStatusOk) { 277 | DEBUG_LOG("Could not start timer task"); 278 | return PlatformStatusError; 279 | } 280 | } 281 | 282 | *timer_handle = free_timer; 283 | return PlatformStatusOk; 284 | } 285 | 286 | PlatformStatusCode platform_destroy_timer(MQTTHandle *handle, int timer_handle) { 287 | PlatformData *p = handle->platform; 288 | 289 | if ((timer_handle < 0) || (timer_handle >= MAX_TIMERS)) { 290 | DEBUG_LOG("Invalid timer handle"); 291 | return PlatformStatusError; 292 | } 293 | 294 | p->timers[timer_handle].callback = NULL; 295 | 296 | 297 | // check if there is a timer running 298 | uint8_t free_timer = 0; 299 | 300 | for (free_timer = 0; free_timer < MAX_TIMERS; free_timer++) { 301 | if (p->timers[free_timer].callback != NULL) { 302 | break; 303 | } 304 | } 305 | if ((free_timer == MAX_TIMERS) && (p->timer_task >= 0)) { 306 | // if we get here we have no running timers, so we destroy the timer task 307 | PlatformStatusCode ret = platform_cleanup_task(handle, p->timer_task); 308 | if (ret == PlatformStatusOk) { 309 | p->timer_task = -1; 310 | } else { 311 | DEBUG_LOG("Could not finish timer task"); 312 | return PlatformStatusError; 313 | } 314 | } 315 | 316 | return PlatformStatusOk; 317 | } 318 | 319 | 320 | PlatformStatusCode platform_sleep(int milliseconds) { 321 | usleep(milliseconds * 1000); 322 | return PlatformStatusOk; 323 | } -------------------------------------------------------------------------------- /platform/platform.h: -------------------------------------------------------------------------------- 1 | #ifndef platform_h__included 2 | #define platform_h__included 3 | 4 | #include "mqtt_internal.h" 5 | 6 | /* Sadly we have to have a Windows-ism here */ 7 | #if MSVC 8 | typedef unsigned long (__stdcall *PlatformTask)(void *context); 9 | 10 | // Use this to define the PlatformTask callback functions to be cross platform 11 | #define PlatformTaskFunc(_name) unsigned long __stdcall _name(void *context) 12 | #else 13 | typedef void *(*PlatformTask)(void *context); 14 | 15 | // Use this to define the PlatformTask callback functions to be cross platform 16 | #define PlatformTaskFunc(_name) void *_name(void *context) 17 | #endif 18 | 19 | typedef void (*PlatformTimerCallback)(MQTTHandle *handle, int timer_handle); 20 | 21 | /** maximum receiver buffer size, defined by platform */ 22 | extern const size_t max_receive_buffer_size; 23 | 24 | typedef enum { 25 | PlatformStatusOk, /**< Everything ok */ 26 | PlatformStatusError, /**< Non-recoverable error */ 27 | PlatformStatusRetry /**< Recoverable error */ 28 | } PlatformStatusCode; 29 | 30 | 31 | /** 32 | * Initialize platform specific data 33 | * 34 | * @param handle: The handle to initialize 35 | * @return Platform status code 36 | */ 37 | PlatformStatusCode platform_init(MQTTHandle *handle); 38 | 39 | /** 40 | * Platform specific function to release resources associated with a MQTTHandle 41 | * 42 | * @param handle: The handle to clean up 43 | * @return Platform status code 44 | */ 45 | PlatformStatusCode platform_release(MQTTHandle *handle); 46 | 47 | /** 48 | * Platform specific function to start a reading thread 49 | * 50 | * @param handle: The broker connection handle 51 | * @param task_handle: Task handle output 52 | * @param callback: callback to run in the thread 53 | * @return Platform status code 54 | */ 55 | PlatformStatusCode platform_run_task(MQTTHandle *handle, int *task_handle, PlatformTask callback); 56 | 57 | /** 58 | * Platform specific function to clean up the reading thread 59 | * 60 | * @param handle: State handle 61 | * @param task_handle: Task handle to clean up 62 | * @return Platform status code 63 | */ 64 | PlatformStatusCode platform_cleanup_task(MQTTHandle *handle, int task_handle); 65 | 66 | /** 67 | * Resolve host 68 | * 69 | * @param hostname: Hostname to resolve 70 | * @param ip_out: resulting IP address if no error occured 71 | * @return Platform status code 72 | */ 73 | PlatformStatusCode platform_resolve_host(char *hostname, char *ip_out); 74 | 75 | /** 76 | * Connect to host from configuration 77 | * 78 | * @param handle: The configuration 79 | * @return Platform status code 80 | */ 81 | PlatformStatusCode platform_connect(MQTTHandle *handle); 82 | 83 | /** 84 | * Read from the "socket" in the handle 85 | * 86 | * @param handle: State handle 87 | * @param buffer: Read target 88 | * @return Platform status code 89 | */ 90 | PlatformStatusCode platform_read(MQTTHandle *handle, Buffer *buffer); 91 | 92 | /** 93 | * Write to the "socket" in the handle 94 | * 95 | * @param handle: State handle 96 | * @param buffer: Write source 97 | * @return Platform status code 98 | */ 99 | PlatformStatusCode platform_write(MQTTHandle *handle, Buffer *buffer); 100 | 101 | /** 102 | * Disconnect the "socket" in the handle 103 | * 104 | * @param handle: State handle 105 | * @return Platform status code 106 | */ 107 | PlatformStatusCode platform_disconnect(MQTTHandle *handle); 108 | 109 | 110 | /** 111 | * Set a recurring timer 112 | * 113 | * @param handle: State handle 114 | * @param interval: Number of seconds to call the callback in 115 | * @param timer_handle: Timer handle out 116 | * @param callback: Callback to call 117 | * @return Platform status code 118 | */ 119 | PlatformStatusCode platform_create_timer(MQTTHandle *handle, int interval, int *timer_handle, PlatformTimerCallback callback); 120 | 121 | /** 122 | * Destroy a recurring timer 123 | * @param handle 124 | * @param timer_handle 125 | * @return Platform status code 126 | */ 127 | PlatformStatusCode platform_destroy_timer(MQTTHandle *handle, int timer_handle); 128 | 129 | 130 | /** 131 | * Sleep for some milliseconds, may yield the task, so the sleep time could be longer 132 | * 133 | * @param milliseconds: minimum number of milliseconds to sleep 134 | * @return Platform status code 135 | */ 136 | PlatformStatusCode platform_sleep(int milliseconds); 137 | 138 | #endif /* platform_h__included */ 139 | -------------------------------------------------------------------------------- /platform/windows.c: -------------------------------------------------------------------------------- 1 | #include 2 | //#include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "debug.h" 12 | #include "mqtt_internal.h" 13 | #include "platform.h" 14 | 15 | const size_t max_receive_buffer_size = 4 * 4096; // 16 KB 16 | 17 | #define MAX_TASKS 16 18 | #define MAX_TIMERS 5 19 | 20 | typedef struct { 21 | PlatformTimerCallback callback; 22 | int status; 23 | int interval; 24 | 25 | } PlatformTimer; 26 | 27 | struct _PlatformData { 28 | HANDLE tasks[MAX_TASKS]; 29 | PlatformTimer timers[MAX_TIMERS]; 30 | WSADATA wsa; 31 | SOCKET sock; 32 | int timer_task; 33 | }; 34 | 35 | PlatformTaskFunc(timer_task) { 36 | MQTTHandle *handle = (MQTTHandle *)context; 37 | while (1) { 38 | platform_sleep(1000); 39 | 40 | bool active = false; 41 | for (uint8_t i = 0; i < MAX_TIMERS; i++) { 42 | PlatformTimer *timer = &handle->platform->timers[i]; 43 | 44 | if (timer->callback != NULL) { 45 | timer->status--; 46 | 47 | if (timer->status == 0) { 48 | timer->callback(handle, i); 49 | timer->status = timer->interval; 50 | } 51 | 52 | active = true; 53 | } 54 | } 55 | 56 | if (!active) { 57 | return 0; 58 | } 59 | } 60 | } 61 | 62 | PlatformStatusCode platform_init(MQTTHandle *handle) { 63 | handle->platform = (PlatformData *)calloc(1, sizeof(struct _PlatformData)); 64 | handle->platform->sock = INVALID_SOCKET; 65 | handle->platform->timer_task = -1; 66 | if (!handle->platform) { 67 | return PlatformStatusError; 68 | } 69 | 70 | return PlatformStatusOk; 71 | } 72 | 73 | PlatformStatusCode platform_release(MQTTHandle *handle) { 74 | PlatformData *p = handle->platform; 75 | 76 | // shut down all timers 77 | if (p->timer_task >= 0) { 78 | for (uint8_t free_timer = 0; free_timer < MAX_TIMERS; free_timer++) { 79 | PlatformStatusCode ret = platform_destroy_timer(handle, free_timer); 80 | if (ret != PlatformStatusOk) { 81 | DEBUG_LOG("Could not shut down all timers"); 82 | return PlatformStatusError; 83 | } 84 | } 85 | } 86 | 87 | // check if there are tasks running 88 | for (uint8_t free_task = 0; free_task < MAX_TASKS; free_task++) { 89 | if (p->tasks[free_task] != 0) { 90 | DEBUG_LOG("Cannot free platform handle, there are tasks running! (id: %d)", free_task); 91 | return PlatformStatusError; 92 | } 93 | } 94 | 95 | free(handle->platform); 96 | return PlatformStatusOk; 97 | } 98 | 99 | PlatformStatusCode platform_run_task(MQTTHandle *handle, int *task_handle, PlatformTask callback) { 100 | PlatformData *p = handle->platform; 101 | uint8_t free_task = 0; 102 | 103 | for (free_task = 0; free_task < MAX_TASKS; free_task++) { 104 | if (p->tasks[free_task] == 0) { 105 | break; 106 | } 107 | } 108 | if (free_task == MAX_TASKS) { 109 | return PlatformStatusError; 110 | } 111 | 112 | DWORD thread_id; 113 | p->tasks[free_task] = CreateThread( 114 | NULL, // default security attributes 115 | 0, // use default stack size 116 | callback, // thread function name 117 | handle, // argument to thread function 118 | 0, // use default creation flags 119 | &thread_id); // returns the thread identifier 120 | 121 | if (p->tasks[free_task] == NULL) { 122 | return PlatformStatusError; 123 | } 124 | 125 | *task_handle = free_task; 126 | return PlatformStatusOk; 127 | } 128 | 129 | PlatformStatusCode platform_cleanup_task(MQTTHandle *handle, int task_handle) { 130 | PlatformData *p = handle->platform; 131 | 132 | if ((task_handle < 0) || (task_handle >= MAX_TASKS)) { 133 | return PlatformStatusError; 134 | } 135 | 136 | if (p->tasks[task_handle]) { 137 | HANDLE wait[] = { p->tasks[task_handle] }; 138 | WaitForMultipleObjects(1, wait, TRUE, INFINITE); 139 | CloseHandle(p->tasks[task_handle]); 140 | p->tasks[task_handle] = 0; 141 | } 142 | return PlatformStatusOk; 143 | } 144 | 145 | PlatformStatusCode platform_resolve_host(char *hostname , char *ip) { 146 | struct in_addr **addr_list; 147 | int i; 148 | 149 | struct hostent *he = gethostbyname(hostname); 150 | if (he == NULL) { 151 | DEBUG_LOG("Resolving host failed: %d", WSAGetLastError()); 152 | return PlatformStatusError; 153 | } 154 | 155 | addr_list = (struct in_addr **) he->h_addr_list; 156 | 157 | for(i = 0; addr_list[i] != NULL; i++) { 158 | strncpy_s(ip, 40, inet_ntoa(*addr_list[i]), 40); 159 | break; 160 | } 161 | 162 | return PlatformStatusOk; 163 | } 164 | 165 | PlatformStatusCode platform_connect(MQTTHandle *handle) { 166 | PlatformData *p = handle->platform; 167 | 168 | if (WSAStartup(MAKEWORD(2,2),&handle->platform->wsa) != 0) { 169 | DEBUG_LOG("Winsock init failed. Error Code : %d", WSAGetLastError()); 170 | return PlatformStatusError; 171 | } 172 | 173 | int ret; 174 | struct sockaddr_in servaddr; 175 | memset(&servaddr, 0, sizeof(servaddr)); 176 | 177 | p->sock = socket(AF_INET, SOCK_STREAM, 0); 178 | if (p->sock == INVALID_SOCKET) { 179 | bool free_handle = handle->error_handler(handle, handle->config, MQTT_Error_Internal); 180 | if (free_handle) { 181 | mqtt_free(handle); 182 | } 183 | char err_msg[200]; 184 | strerror_s(err_msg, 200, errno); 185 | DEBUG_LOG("Resolving hostname failed: %s", err_msg); 186 | return PlatformStatusError; 187 | } 188 | servaddr.sin_family = AF_INET; 189 | servaddr.sin_port = htons(handle->config->port); 190 | 191 | char ip[40]; 192 | if (platform_resolve_host(handle->config->hostname, ip) != PlatformStatusOk) { 193 | bool free_handle = handle->error_handler(handle, handle->config, MQTT_Error_Host_Not_Found); 194 | if (free_handle) { 195 | mqtt_free(handle); 196 | } 197 | char err_msg[200]; 198 | strerror_s(err_msg, 200, errno); 199 | DEBUG_LOG("Resolving hostname failed: %s", err_msg); 200 | closesocket(p->sock); 201 | return PlatformStatusError; 202 | } 203 | servaddr.sin_addr.s_addr = inet_addr(ip); 204 | 205 | ret = connect(p->sock, (struct sockaddr *)&servaddr, sizeof(servaddr)); 206 | if (ret != 0) { 207 | bool free_handle = handle->error_handler(handle, handle->config, MQTT_Error_Connection_Refused); 208 | if (free_handle) { 209 | mqtt_free(handle); 210 | } 211 | char err_msg[200]; 212 | strerror_s(err_msg, 200, errno); 213 | DEBUG_LOG("Connection failed: %s", err_msg); 214 | closesocket(p->sock); 215 | return PlatformStatusError; 216 | } 217 | 218 | return PlatformStatusOk; 219 | } 220 | 221 | PlatformStatusCode platform_read(MQTTHandle *handle, Buffer *buffer) { 222 | PlatformData *p = handle->platform; 223 | 224 | while (1) { 225 | int num_bytes = recv(p->sock, &buffer->data[buffer->position], (int)buffer_free_space(buffer), 0); 226 | if (num_bytes == 0) { 227 | /* Socket closed, coordinated shutdown */ 228 | DEBUG_LOG("Socket closed"); 229 | return PlatformStatusError; 230 | } else if (num_bytes < 0) { 231 | if ((errno == EINTR) || (errno == EAGAIN)) { 232 | continue; 233 | } 234 | 235 | /* Some error occured */ 236 | return PlatformStatusError; 237 | } 238 | 239 | buffer->position += num_bytes; 240 | return PlatformStatusOk; 241 | } 242 | } 243 | 244 | PlatformStatusCode platform_write(MQTTHandle *handle, Buffer *buffer) { 245 | PlatformData *p = handle->platform; 246 | 247 | while (!buffer_eof(buffer)) { 248 | int bytes = send(p->sock, buffer->data + buffer->position, (int)buffer_free_space(buffer), 0); 249 | if (bytes <= 0) { 250 | return PlatformStatusError; 251 | } 252 | buffer->position += bytes; 253 | } 254 | 255 | return PlatformStatusOk; 256 | } 257 | 258 | PlatformStatusCode platform_disconnect(MQTTHandle *handle) { 259 | PlatformData *p = handle->platform; 260 | if (p->sock != INVALID_SOCKET) { 261 | // u_long mode = 1; 262 | // DEBUG_LOG("Unblocking socket"); 263 | // ioctlsocket(p->sock, FIONBIO, &mode); // unblock socket 264 | 265 | DEBUG_LOG("Closing socket"); 266 | closesocket(p->sock); 267 | WSACleanup(); 268 | p->sock = INVALID_SOCKET; 269 | } 270 | 271 | return PlatformStatusOk; 272 | } 273 | 274 | PlatformStatusCode platform_create_timer(MQTTHandle *handle, int interval, int *timer_handle, PlatformTimerCallback callback) { 275 | PlatformData *p = handle->platform; 276 | uint8_t free_timer = 0; 277 | 278 | for (free_timer = 0; free_timer < MAX_TIMERS; free_timer++) { 279 | // DEBUG_LOG("Timer %d: %s", free_timer, p->timers[free_timer].callback ? "Occupied" : "Free"); 280 | if (p->timers[free_timer].callback == NULL) { 281 | break; 282 | } 283 | } 284 | if (free_timer == MAX_TASKS) { 285 | return PlatformStatusError; 286 | } 287 | 288 | PlatformTimer *timer = &p->timers[free_timer]; 289 | 290 | timer->callback = callback; 291 | timer->status = interval; 292 | timer->interval = interval; 293 | 294 | if (p->timer_task < 0) { 295 | PlatformStatusCode ret = platform_run_task(handle, &p->timer_task, timer_task); 296 | if (ret != PlatformStatusOk) { 297 | DEBUG_LOG("Could not start timer task"); 298 | return PlatformStatusError; 299 | } 300 | } 301 | 302 | *timer_handle = free_timer; 303 | return PlatformStatusOk; 304 | } 305 | 306 | PlatformStatusCode platform_destroy_timer(MQTTHandle *handle, int timer_handle) { 307 | PlatformData *p = handle->platform; 308 | 309 | if ((timer_handle < 0) || (timer_handle >= MAX_TIMERS)) { 310 | DEBUG_LOG("Invalid timer handle"); 311 | return PlatformStatusError; 312 | } 313 | 314 | p->timers[timer_handle].callback = NULL; 315 | 316 | 317 | // check if there is a timer running 318 | uint8_t free_timer = 0; 319 | 320 | for (free_timer = 0; free_timer < MAX_TIMERS; free_timer++) { 321 | if (p->timers[free_timer].callback != NULL) { 322 | break; 323 | } 324 | } 325 | if ((free_timer == MAX_TIMERS) && (p->timer_task >= 0)) { 326 | // if we get here we have no running timers, so we destroy the timer task 327 | PlatformStatusCode ret = platform_cleanup_task(handle, p->timer_task); 328 | if (ret == PlatformStatusOk) { 329 | p->timer_task = -1; 330 | } else { 331 | DEBUG_LOG("Could not finish timer task"); 332 | return PlatformStatusError; 333 | } 334 | } 335 | 336 | return PlatformStatusOk; 337 | } 338 | 339 | 340 | PlatformStatusCode platform_sleep(int milliseconds) { 341 | HANDLE timer; 342 | LARGE_INTEGER ft; 343 | 344 | // Convert to 100 nanosecond interval, negative value indicates relative time 345 | ft.QuadPart = -(10 * milliseconds * 1000); 346 | 347 | timer = CreateWaitableTimer(NULL, TRUE, NULL); 348 | SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); 349 | WaitForSingleObject(timer, INFINITE); 350 | CloseHandle(timer); 351 | 352 | return PlatformStatusOk; 353 | } -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef buffer_h__included 2 | #define buffer_h__included 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "mqtt.h" 10 | 11 | typedef struct { 12 | char *data; /**< Pointer to data */ 13 | size_t len; /**< Allocated space in data */ 14 | size_t position; /**< current cursor position in buffer */ 15 | } Buffer; 16 | 17 | /** 18 | * Copy data from current buffer position into dest 19 | * 20 | * This advances the internal buffer position 21 | * 22 | * @param src: Source buffer 23 | * @param dest: Destination memory area 24 | * @param len: Number of bytes to copy 25 | * @returns: Actual number of bytes copied 26 | */ 27 | static inline size_t buffer_copy_out(Buffer *src, char *dest, size_t len) { 28 | size_t sz = (len > src->len - src->position) ? src->len - src->position : len; 29 | memcpy(dest, src->data + src->position, sz); 30 | src->position += sz; 31 | return sz; 32 | } 33 | 34 | /** 35 | * Copy data into the buffer 36 | * 37 | * This advances the internal buffer position 38 | * 39 | * @param src: Source memory area 40 | * @param dest: Destination buffer 41 | * @param len: Number of bytes to copy 42 | * @returns: Actual number of bytes copied 43 | */ 44 | static inline size_t buffer_copy_in(char *src, Buffer *dest, size_t len) { 45 | size_t sz = (len > dest->len - dest->position) ? dest->len - dest->position : len; 46 | memcpy(dest->data + dest->position, src, sz); 47 | dest->position += sz; 48 | return sz; 49 | } 50 | 51 | /** 52 | * Get free space in buffer 53 | * 54 | * @param buffer: Buffer to check 55 | * @returns: Number of free bytes in buffer 56 | */ 57 | static inline size_t buffer_free_space(Buffer *buffer) { 58 | return buffer->len - buffer->position; 59 | } 60 | 61 | /** 62 | * Check if the internal position is at the end of the buffer 63 | * 64 | * @param buffer; Buffer to check 65 | * @returns: True if end of buffer reached 66 | */ 67 | static inline bool buffer_eof(Buffer *buffer) { 68 | return buffer->position == buffer->len; 69 | } 70 | 71 | /** 72 | * Reset internal position of buffer 73 | * 74 | * @param buffer: Buffer to reset 75 | */ 76 | static inline void buffer_reset(Buffer *buffer) { 77 | buffer->position = 0; 78 | } 79 | 80 | /** 81 | * Allocate a new buffer 82 | * 83 | * @param len: Size of new buffer 84 | * @returns: New buffer or NULL if out of memory 85 | */ 86 | static inline Buffer *buffer_allocate(size_t len) { 87 | Buffer *buffer = (Buffer *)malloc(sizeof(Buffer)); 88 | if (buffer == NULL) { 89 | return NULL; 90 | } 91 | buffer->len = len; 92 | buffer->position = 0; 93 | buffer->data = (char *)calloc(1, len); 94 | if (buffer->data == NULL) { 95 | free(buffer); 96 | return NULL; 97 | } 98 | 99 | return buffer; 100 | } 101 | 102 | /** 103 | * Re-allocate buffer size 104 | * 105 | * @param buffer: Buffer to modify 106 | * @param len: Size of new buffer 107 | * @returns: Modified buffer if realloc did work, check if buffer->len == len to verify 108 | */ 109 | static inline Buffer *buffer_reallocate(Buffer *buffer, size_t len) { 110 | char *new_buffer = (char *)realloc(buffer->data, len); 111 | if (new_buffer != NULL) { 112 | buffer->data = new_buffer; 113 | buffer->len = len; 114 | } 115 | return buffer; 116 | } 117 | 118 | /** 119 | * Create a new buffer from a memory area and a size 120 | * 121 | * @param data: Memory area 122 | * @param len: Length of memory area 123 | * @returns: New Buffer 124 | * 125 | * @attention: the data pointer will be owned by the buffer and freed with it! 126 | */ 127 | static inline Buffer *buffer_from_data_no_copy(char *data, size_t len) { 128 | Buffer *buffer = (Buffer *)malloc(sizeof(Buffer)); 129 | buffer->len = len; 130 | buffer->data = data; 131 | buffer->position = 0; 132 | 133 | return buffer; 134 | } 135 | 136 | /** 137 | * Create a new buffer and copy the data 138 | * 139 | * @param data: Data to copy into the buffer 140 | * @param len: Number of bytes to copy 141 | * @returns: New Buffer 142 | */ 143 | static inline Buffer *buffer_from_data_copy(char *data, size_t len) { 144 | Buffer *buffer = buffer_allocate(len); 145 | (void)buffer_copy_in(data, buffer, len); 146 | buffer->position = 0; 147 | return buffer; 148 | } 149 | 150 | /** 151 | * Release a buffer 152 | * 153 | * @param buffer: Buffer to release 154 | */ 155 | static inline void buffer_release(Buffer *buffer) { 156 | free(buffer->data); 157 | buffer->data = NULL; 158 | free(buffer); 159 | } 160 | 161 | /** 162 | * Append data to a buffer 163 | * 164 | * @param buffer: Buffer to append data to 165 | * @param data: Memory area to copy to the end of the buffer 166 | * @param len: Number of bytes to copy 167 | * @returns: Numbr of bytes copied 168 | * 169 | * @attention: May come up short if the destination buffer has to be reallocated and 170 | * that reallocation fails 171 | */ 172 | static inline size_t buffer_append_data(Buffer *buffer, char *data, size_t len) { 173 | size_t num_bytes = buffer_copy_in(data, buffer, len); 174 | if (num_bytes != len) { 175 | // reallocate 176 | (void)buffer_reallocate(buffer, buffer->len + (len - num_bytes)); 177 | if (buffer_eof(buffer)) { 178 | // reallocation failed 179 | return num_bytes; 180 | } 181 | (void)buffer_copy_in(data + num_bytes, buffer, (len - num_bytes)); 182 | } 183 | 184 | return len; 185 | } 186 | 187 | /** 188 | * Append a buffer to another buffer 189 | * 190 | * @param dest: Destination buffer 191 | * @param src: Source buffer to append 192 | * @returns: Number of bytes copied 193 | * 194 | * @attention: May come up short if the destination buffer has to be reallocated and 195 | * that reallocation fails 196 | */ 197 | static inline size_t buffer_append_buffer(Buffer *dest, Buffer *src) { 198 | return buffer_append_data(dest, src->data, src->len); 199 | } 200 | 201 | #if DEBUG_HEXDUMP 202 | #include "debug.h" 203 | 204 | static inline void buffer_hexdump(Buffer *buffer, int indent) { 205 | hexdump(buffer->data, buffer->len, indent); 206 | } 207 | #else 208 | #define buffer_hexdump(_buffer, _indent) /* */ 209 | #endif 210 | 211 | #endif /* buffer_h__included */ 212 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef debug_h__included 2 | #define debug_h__included 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #if DEBUG_HEXDUMP 11 | static inline void hexdump(char *data, size_t len, int indent) { 12 | for (int i = 0; i < len;) { 13 | 14 | // indent 15 | for (int col = 0; col < indent; col++) { 16 | fprintf(stdout, " "); 17 | } 18 | 19 | // address 20 | fprintf(stdout, "0x%04x: ", i); 21 | 22 | // hex field 23 | for (int col = 0; col < 16; col++) { 24 | if (i + col < len) { 25 | fprintf(stdout, "%02x ", (uint8_t)data[i + col]); 26 | } else { 27 | fprintf(stdout, " "); 28 | } 29 | } 30 | 31 | // separator 32 | fprintf(stdout, " | "); 33 | 34 | // ascii field 35 | for (int col = 0; col < 16; col++) { 36 | if (i + col < len) { 37 | char c = data[i + col]; 38 | if ((c > 127) || (c < 32)) c = '.'; 39 | fprintf(stdout, "%c", (uint8_t)c); 40 | } else { 41 | fprintf(stdout, " "); 42 | } 43 | } 44 | 45 | fprintf(stdout, "\n"); 46 | i += 16; 47 | } 48 | } 49 | #else 50 | #define hexdump(_data, _len, _indent) /* */ 51 | #endif /* DEBUG_HEXDUMP */ 52 | 53 | #if DEBUG 54 | # define DEBUG_LOG(fmt, ...) fprintf(stderr, "%s:%d: " fmt "\n", __FILE__, __LINE__, ## __VA_ARGS__); 55 | #else /* DEBUG */ 56 | # define DEBUG_LOG(fmt, ...) /* */ 57 | #endif /* DEBUG */ 58 | 59 | 60 | #endif /* debug_h__included */ 61 | -------------------------------------------------------------------------------- /src/mqtt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mqtt.h" 8 | #include "mqtt_internal.h" 9 | #include "packet.h" 10 | #include "platform.h" 11 | #include "protocol.h" 12 | #include "debug.h" 13 | 14 | void mqtt_free(MQTTHandle *handle) { 15 | platform_release(handle); 16 | remove_all_subscriptions(handle); 17 | free(handle); 18 | } 19 | 20 | /* 21 | * State handling 22 | */ 23 | 24 | void cleanup_session(MQTTHandle *handle) { 25 | // Remove all waiting packets 26 | clear_packet_queue(handle); 27 | } 28 | 29 | static MQTTStatus resubscribe(MQTTHandle *handle) { 30 | // re-subscribe to all topics 31 | SubscriptionItem *item = handle->subscriptions.items; 32 | while (item != NULL) { 33 | if (!send_subscribe_packet(handle, item->topic, item->qos)) { 34 | DEBUG_LOG("Error sending subscribe packet"); 35 | return MQTT_STATUS_ERROR; 36 | } 37 | item = item->next; 38 | } 39 | 40 | return MQTT_STATUS_OK; 41 | } 42 | 43 | /* 44 | * Keepalive 45 | */ 46 | 47 | static void _keepalive_callback(MQTTHandle *handle, int timer_handle) { 48 | bool result = send_ping_packet(handle); 49 | if (!result) { 50 | DEBUG_LOG("Sending PINGREQ packet failed!"); 51 | } 52 | } 53 | 54 | /* 55 | * Packet parser 56 | */ 57 | 58 | static inline void parse_packet(MQTTHandle *handle, MQTTPacket *packet) { 59 | // DEBUG_LOG("Packet, type: %s, packet_id: %d", get_packet_name(packet), get_packet_id(packet)); 60 | 61 | switch (packet->packet_type) { 62 | case PacketTypeConnAck: 63 | if (!dispatch_packet(handle, packet)) { 64 | DEBUG_LOG("Unexpected packet! (type: CONNACK)"); 65 | (void)platform_destroy_timer(handle, handle->keepalive_timer); 66 | handle->keepalive_timer = -1; 67 | (void)platform_disconnect(handle); 68 | } else { 69 | ConnAckPayload *payload = (ConnAckPayload *)packet->payload; 70 | if ((!payload->session_present) && (handle->reconnecting)) { 71 | cleanup_session(handle); 72 | if (resubscribe(handle) != MQTT_STATUS_OK) { 73 | DEBUG_LOG("Could not re-subscribe to all topics!"); 74 | (void)platform_destroy_timer(handle, handle->keepalive_timer); 75 | handle->keepalive_timer = -1; 76 | (void)platform_disconnect(handle); 77 | } 78 | } 79 | if (platform_create_timer(handle, KEEPALIVE_INTERVAL, &handle->keepalive_timer, _keepalive_callback) != PlatformStatusOk) { 80 | DEBUG_LOG("Could not create keepalive timer!"); 81 | (void)platform_destroy_timer(handle, handle->keepalive_timer); 82 | handle->keepalive_timer = -1; 83 | (void)platform_disconnect(handle); 84 | } 85 | } 86 | break; 87 | 88 | case PacketTypePubAck: 89 | case PacketTypePubRec: 90 | case PacketTypePubRel: 91 | case PacketTypePubComp: 92 | case PacketTypeSubAck: 93 | case PacketTypeUnsubAck: 94 | if (!dispatch_packet(handle, packet)) { 95 | DEBUG_LOG("Unexpected packet! (type: %s, packet_id: %d)", get_packet_name(packet), get_packet_id(packet)); 96 | (void)platform_destroy_timer(handle, handle->keepalive_timer); 97 | handle->keepalive_timer = -1; 98 | (void)platform_disconnect(handle); 99 | } 100 | break; 101 | 102 | case PacketTypePublish: { 103 | // DEBUG_LOG("Publish on %s -> %s", ((PublishPayload *)packet->payload)->topic, ((PublishPayload *)packet->payload)->message); 104 | PublishPayload *payload = (PublishPayload *)packet->payload; 105 | switch (payload->qos) { 106 | case MQTT_QOS_0: 107 | dispatch_subscription(handle, payload); 108 | break; 109 | case MQTT_QOS_1: 110 | if (send_puback_packet(handle, payload->packet_id)) { 111 | dispatch_subscription(handle, payload); 112 | } 113 | break; 114 | case MQTT_QOS_2: 115 | send_pubrec_packet(handle, payload->packet_id, dispatch_subscription_direct, payload); 116 | break; 117 | default: 118 | DEBUG_LOG("Invalid QoS! (packet_id: %d)", payload->packet_id); 119 | (void)platform_destroy_timer(handle, handle->keepalive_timer); 120 | handle->keepalive_timer = -1; 121 | (void)platform_disconnect(handle); 122 | break; 123 | } 124 | break; 125 | } 126 | 127 | // just for keepalive, do not handle 128 | case PacketTypePingResp: 129 | break; 130 | 131 | // client -> server, will not be handled in client 132 | case PacketTypeConnect: 133 | case PacketTypeSubscribe: 134 | case PacketTypeUnsubscribe: 135 | case PacketTypePingReq: 136 | case PacketTypeDisconnect: 137 | DEBUG_LOG("Server packet on client connection? What's up with the broker?"); 138 | (void)platform_destroy_timer(handle, handle->keepalive_timer); 139 | handle->keepalive_timer = -1; 140 | (void)platform_disconnect(handle); 141 | break; 142 | } 143 | } 144 | 145 | /* 146 | * Reading loop 147 | */ 148 | 149 | PlatformTaskFunc(_reader) { 150 | MQTTHandle *handle = (MQTTHandle *)context; 151 | Buffer *buffer = buffer_allocate(max_receive_buffer_size); 152 | 153 | handle->reader_alive = true; 154 | 155 | while (1) { 156 | PlatformStatusCode ret = platform_read(handle, buffer); 157 | if (ret == PlatformStatusError) { 158 | handle->reader_alive = false; 159 | buffer_release(buffer); 160 | return 0; 161 | } 162 | 163 | while (1) { 164 | buffer->len = buffer->position; 165 | buffer->position = 0; 166 | 167 | MQTTPacket *packet = mqtt_packet_decode(buffer); 168 | if (packet == NULL) { 169 | // invalid packet 170 | if (buffer_free_space(buffer) > 0) { 171 | // half packet, fetch more 172 | buffer->position = buffer->len; 173 | buffer->len = max_receive_buffer_size; 174 | break; 175 | } else { 176 | // no space in buffer, bail and reconnect 177 | DEBUG_LOG("Buffer overflow!"); 178 | (void)platform_destroy_timer(handle, handle->keepalive_timer); 179 | handle->keepalive_timer = -1; 180 | (void)platform_disconnect(handle); 181 | handle->reader_alive = false; 182 | buffer_release(buffer); 183 | return 0; 184 | } 185 | } else { 186 | hexdump(buffer->data, num_bytes, 2); 187 | parse_packet(handle, packet); 188 | free_MQTTPacket(packet); 189 | 190 | if (!buffer_eof(buffer)) { 191 | buffer->len = max_receive_buffer_size; 192 | 193 | // Not complete recv buffer was consumed, so we have more than one packet in there 194 | size_t remaining = max_receive_buffer_size - buffer->position; 195 | memmove(buffer->data, buffer->data + buffer->position, remaining); 196 | buffer->position = 0; 197 | } else { 198 | // buffer consumed completely, read another chunk 199 | buffer->position = 0; 200 | buffer->len = max_receive_buffer_size; 201 | break; 202 | } 203 | } 204 | } 205 | } 206 | } 207 | 208 | static void _mqtt_connect(MQTTHandle *handle, MQTTEventHandler callback, void *context) { 209 | PlatformStatusCode ret = platform_connect(handle); 210 | 211 | if (ret == PlatformStatusError) { 212 | DEBUG_LOG("Could not connect"); 213 | return; 214 | } 215 | 216 | expect_packet(handle, PacketTypeConnAck, 0, callback, context); 217 | 218 | if (!handle->reader_alive) { 219 | if (handle->read_task_handle >= 0) { 220 | platform_cleanup_task(handle, handle->read_task_handle); 221 | handle->read_task_handle = -1; 222 | } 223 | ret = platform_run_task(handle, &handle->read_task_handle, _reader); 224 | if (ret == PlatformStatusError) { 225 | DEBUG_LOG("Could not start read task"); 226 | return; 227 | } 228 | } 229 | 230 | bool result = send_connect_packet(handle); 231 | if (result == false) { 232 | DEBUG_LOG("Sending connect packet failed, running error handler"); 233 | bool free_handle = handle->error_handler(handle, handle->config, MQTT_Error_Broker_Disconnected); 234 | platform_disconnect(handle); 235 | if (free_handle) { 236 | platform_cleanup_task(handle, handle->read_task_handle); 237 | handle->read_task_handle = -1; 238 | mqtt_free(handle); 239 | } 240 | } 241 | } 242 | 243 | /* 244 | * API 245 | */ 246 | 247 | MQTTHandle *mqtt_connect(MQTTConfig *config, MQTTEventHandler callback, void *context, MQTTErrorHandler error_callback) { 248 | // sanity check 249 | if ((config->client_id != NULL) && (strlen(config->client_id) > 23)) { 250 | DEBUG_LOG("Client ID has to be shorter than 24 characters"); 251 | return NULL; 252 | } 253 | 254 | MQTTHandle *handle = (MQTTHandle *)calloc(sizeof(struct _MQTTHandle), 1); 255 | PlatformStatusCode ret = platform_init(handle); 256 | if (ret == PlatformStatusError) { 257 | free(handle); 258 | return NULL; 259 | } 260 | 261 | if (config->port == 0) { 262 | config->port = 1883; 263 | } 264 | 265 | handle->config = config; 266 | handle->error_handler = error_callback; 267 | handle->read_task_handle = -1; 268 | handle->keepalive_timer = -1; 269 | 270 | _mqtt_connect(handle, callback, context); 271 | 272 | return handle; 273 | } 274 | 275 | 276 | MQTTStatus mqtt_reconnect(MQTTHandle *handle, MQTTEventHandler callback, void *context) { 277 | if (handle->reader_alive) { 278 | (void)platform_destroy_timer(handle, handle->keepalive_timer); 279 | handle->keepalive_timer = -1; 280 | (void)platform_disconnect(handle); 281 | // DEBUG_LOG("Waiting for reader to exit"); 282 | // platform_cleanup_task(handle, handle->read_task_handle); 283 | } 284 | 285 | handle->config->clean_session = false; 286 | handle->reconnecting = true; 287 | _mqtt_connect(handle, callback, context); 288 | 289 | return MQTT_STATUS_OK; 290 | } 291 | 292 | MQTTStatus mqtt_subscribe(MQTTHandle *handle, char *topic, MQTTQosLevel qos_level, MQTTPublishEventHandler callback) { 293 | if (!handle->reader_alive) { 294 | handle->error_handler(handle, handle->config, MQTT_Error_Connection_Reset); 295 | return MQTT_STATUS_ERROR; 296 | } 297 | add_subscription(handle, topic, qos_level, callback); 298 | return (send_subscribe_packet(handle, topic, qos_level) ? MQTT_STATUS_OK : MQTT_STATUS_ERROR); 299 | } 300 | 301 | MQTTStatus mqtt_unsubscribe(MQTTHandle *handle, char *topic) { 302 | if (!handle->reader_alive) { 303 | handle->error_handler(handle, handle->config, MQTT_Error_Connection_Reset); 304 | return MQTT_STATUS_ERROR; 305 | } 306 | remove_subscription(handle, topic); 307 | return (send_unsubscribe_packet(handle, topic) ? MQTT_STATUS_OK : MQTT_STATUS_ERROR); 308 | } 309 | 310 | MQTTStatus mqtt_publish(MQTTHandle *handle, char *topic, char *payload, MQTTQosLevel qos_level, MQTTPublishEventHandler callback) { 311 | if (!handle->reader_alive) { 312 | handle->error_handler(handle, handle->config, MQTT_Error_Connection_Reset); 313 | return MQTT_STATUS_ERROR; 314 | } 315 | return (send_publish_packet(handle, topic, payload, qos_level, callback) ? MQTT_STATUS_OK : MQTT_STATUS_ERROR); 316 | } 317 | 318 | MQTTStatus mqtt_disconnect(MQTTHandle *handle, MQTTEventHandler callback, void *callback_context) { 319 | (void)send_disconnect_packet(handle); 320 | (void)platform_destroy_timer(handle, handle->keepalive_timer); 321 | handle->keepalive_timer = -1; 322 | (void)platform_disconnect(handle); 323 | (void)platform_cleanup_task(handle, handle->read_task_handle); 324 | handle->read_task_handle = -1; 325 | mqtt_free(handle); 326 | 327 | if (callback) { 328 | callback(NULL, callback_context); 329 | } 330 | return MQTT_STATUS_OK; 331 | } 332 | -------------------------------------------------------------------------------- /src/mqtt.h.in: -------------------------------------------------------------------------------- 1 | #ifndef mqtt_h__included 2 | #define mqtt_h__included 3 | 4 | // re-define to 1 to enable server functionality 5 | #ifndef MQTT_SERVER 6 | # define MQTT_SERVER 0 7 | #endif 8 | 9 | // re-rdefine to 0 to disable client functionality 10 | #ifndef MQTT_CLIENT 11 | # define MQTT_CLIENT 1 12 | #endif 13 | 14 | #define LIBMQTT_VERSION_MAJOR @LIBMQTT_VERSION_MAJOR@ 15 | #define LIBMQTT_VERSION_MINOR @LIBMQTT_VERSION_MINOR@ 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif /* __cplusplus */ 20 | 21 | #if MSVC 22 | #define API __declspec(dllexport) 23 | #else 24 | #define API __attribute__((visibility("default"))) 25 | #endif 26 | 27 | #include 28 | #include 29 | 30 | 31 | typedef struct _MQTTHandle MQTTHandle; 32 | 33 | typedef struct { 34 | char *hostname; /**< Hostname to connect to, will do DNS resolution */ 35 | uint16_t port; /**< Port the broker listens on, set to 0 for 1883 default */ 36 | 37 | char *client_id; /**< Client identification */ 38 | bool clean_session; /**< Set to true to reset the session on reconnect */ 39 | 40 | char *username; /**< User name, set to NULL to connect anonymously */ 41 | char *password; /**< Password, set to NULL to connect without password */ 42 | 43 | char *last_will_topic; /**< last will topic that is automatically published on connection loss */ 44 | char *last_will_message; /**< last will message */ 45 | bool last_will_retain; /**< tell server to retain last will message */ 46 | } MQTTConfig; 47 | 48 | typedef enum { 49 | MQTT_STATUS_OK = 0, /**< All ok, no error */ 50 | MQTT_STATUS_ERROR /**< Error, action did not succeed, error handler will be called */ 51 | } MQTTStatus; 52 | 53 | typedef enum { 54 | MQTT_QOS_0 = 0, /**< At most once, drop message if nobody is listening, no ACK */ 55 | MQTT_QOS_1, /**< At least once, wait for ACK from broker */ 56 | MQTT_QOS_2, /**< Exactly once, do triple way handshake with broker */ 57 | } MQTTQosLevel; 58 | 59 | typedef enum { 60 | MQTT_Error_Internal, /**< Internal error */ 61 | MQTT_Error_Host_Not_Found, /**< Host could not be resolved */ 62 | MQTT_Error_Connection_Refused, /**< Connection was refused, wrong port? */ 63 | MQTT_Error_Broker_Disconnected, /**< Broker went down, perhaps restart? */ 64 | MQTT_Error_Authentication, /**< Authentication error, wrong or missing username/password? */ 65 | MQTT_Error_Protocol_Not_Supported, /**< Broker does not speak MQTT protocol 3.1.1 (aka version 4) */ 66 | MQTT_Error_Connection_Reset /**< Network connection reset, perhaps network went down? */ 67 | } MQTTErrorCode; 68 | 69 | /** Error handler callback 70 | * 71 | * Return true if the handle should be freed, false to keep it 72 | */ 73 | typedef bool (*MQTTErrorHandler)(MQTTHandle *handle, MQTTConfig *config, MQTTErrorCode code); 74 | 75 | /** Event handler callback */ 76 | typedef void (*MQTTEventHandler)(MQTTHandle *handle, void *context); 77 | 78 | /** publish event callback */ 79 | typedef void (*MQTTPublishEventHandler)(MQTTHandle *handle, char *topic, char *payload); 80 | 81 | /** 82 | * Connect to MQTT broker 83 | * 84 | * @param config: MQTT configuration 85 | * @param callback: Success callback 86 | * @param callback_context: Context pointer for the callback 87 | * @param error_callback: Callback function to call on errors 88 | * @returns handle to mqtt connection or NULL on error 89 | * 90 | * If the error handler is called with Host not found or Connection refused, 91 | * the handler is in charge of freeing the handle by returning true 92 | * or re-trying by changing settings and calling mqtt_reconnect() and returning false 93 | */ 94 | API MQTTHandle *mqtt_connect(MQTTConfig *config, MQTTEventHandler callback, void *callback_context, MQTTErrorHandler error_callback); 95 | 96 | /** 97 | * Re-Connect to MQTT broker 98 | * 99 | * Usually called in the MQTTErrorHandler callback, if called on a working 100 | * connection the connection will be disconnected before reconnecting. 101 | * 102 | * If there were registered subscriptions they will be re-instated after 103 | * a successful reconnect. 104 | * 105 | * @param handle: MQTT Handle from `mqtt_connect` 106 | * @param callback: Success callback 107 | * @param callback_context: Context pointer for the callback 108 | * @returns: Status code 109 | */ 110 | API MQTTStatus mqtt_reconnect(MQTTHandle *handle, MQTTEventHandler callback, void *callback_context); 111 | 112 | /** 113 | * Subscribe to a topic 114 | * 115 | * @param handle: MQTT Handle from `mqtt_connect` 116 | * @param topic: Topic to subscribe 117 | * @param qos_level: Maximum qos level to subscribe to 118 | * @param callback: Callback function to call when receiving something for that topic 119 | * @returns: Status code 120 | */ 121 | API MQTTStatus mqtt_subscribe(MQTTHandle *handle, char *topic, MQTTQosLevel qos_level, MQTTPublishEventHandler callback); 122 | 123 | /** 124 | * Un-Subscribe from a topic 125 | * 126 | * @param handle: MQTT Handle from `mqtt_connect` 127 | * @param topic: Topic to unsubscribe 128 | * @returns: Status code 129 | */ 130 | API MQTTStatus mqtt_unsubscribe(MQTTHandle *handle, char *topic); 131 | 132 | /** 133 | * Publish something to the broker 134 | * 135 | * @param handle: MQTT Handle from `mqtt_connect` 136 | * @param topic: Topic to publish to 137 | * @param payload: Message payload to publish 138 | * @param qos_level: QoS level to use 139 | * @param callback: finish callback 140 | * @returns: Status code 141 | */ 142 | API MQTTStatus mqtt_publish(MQTTHandle *handle, char *topic, char *payload, MQTTQosLevel qos_level, MQTTPublishEventHandler callback); 143 | 144 | /** 145 | * Disconnect from MQTT broker 146 | * 147 | * @param handle: MQTT Handle from `mqtt_connect` 148 | * @returns: Status code 149 | * 150 | * @attention: do not use the handle after calling this function, 151 | * all resources will be freed, this handle is now invalid! 152 | */ 153 | API MQTTStatus mqtt_disconnect(MQTTHandle *handle, MQTTEventHandler callback, void *callback_context); 154 | 155 | #ifdef __cplusplus 156 | } 157 | #endif /* __cplusplus */ 158 | 159 | #endif /* mqtt_h__included */ 160 | -------------------------------------------------------------------------------- /src/mqtt_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef mqtt_internal_h__included 2 | #define mqtt_internal_h__included 3 | 4 | #include "mqtt.h" 5 | #include "packet.h" 6 | #include "subscriptions.h" 7 | #include "state_queue.h" 8 | 9 | typedef struct _PlatformData PlatformData; 10 | 11 | struct _MQTTHandle { 12 | MQTTConfig *config; 13 | 14 | MQTTErrorHandler error_handler; 15 | Subscriptions subscriptions; 16 | 17 | bool reader_alive; 18 | int read_task_handle; 19 | 20 | uint16_t packet_id_counter; 21 | 22 | MQTTCallbackQueue queue; 23 | PlatformData *platform; 24 | 25 | bool reconnecting; 26 | int keepalive_timer; 27 | }; 28 | 29 | void mqtt_free(MQTTHandle *handle); 30 | 31 | #ifndef KEEPALIVE_INTERVAL 32 | #define KEEPALIVE_INTERVAL 60 33 | #endif 34 | 35 | #endif /* mqtt_internal_h__included */ 36 | -------------------------------------------------------------------------------- /src/packet.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "debug.h" 3 | #include "packet.h" 4 | 5 | /* 6 | * Helper functionality 7 | */ 8 | 9 | MQTTPacket *allocate_MQTTPacket(MQTTControlPacketType type) { 10 | MQTTPacket *packet = (MQTTPacket *)calloc(1, sizeof(MQTTPacket)); 11 | packet->packet_type = type; 12 | 13 | switch (type) { 14 | case PacketTypeConnect: 15 | packet->payload = calloc(1, sizeof(ConnectPayload)); 16 | break; 17 | case PacketTypeConnAck: 18 | packet->payload = calloc(1, sizeof(ConnAckPayload)); 19 | break; 20 | case PacketTypePublish: 21 | packet->payload = calloc(1, sizeof(PublishPayload)); 22 | break; 23 | case PacketTypeSubscribe: 24 | packet->payload = calloc(1, sizeof(SubscribePayload)); 25 | break; 26 | case PacketTypeSubAck: 27 | packet->payload = calloc(1, sizeof(SubAckPayload)); 28 | break; 29 | case PacketTypeUnsubscribe: 30 | packet->payload = calloc(1, sizeof(UnsubscribePayload)); 31 | break; 32 | case PacketTypePubAck: 33 | case PacketTypePubRec: 34 | case PacketTypePubRel: 35 | case PacketTypePubComp: 36 | case PacketTypeUnsubAck: 37 | packet->payload = calloc(1, sizeof(PacketIDPayload)); 38 | break; 39 | case PacketTypePingReq: 40 | case PacketTypePingResp: 41 | case PacketTypeDisconnect: 42 | packet->payload = NULL; 43 | break; 44 | } 45 | 46 | return packet; 47 | } 48 | 49 | void free_MQTTPacket(MQTTPacket *packet) { 50 | switch (packet->packet_type) { 51 | case PacketTypePublish: { 52 | PublishPayload *content = (PublishPayload *)packet->payload; 53 | if (content->topic) free(content->topic); 54 | if (content->message) free(content->message); 55 | break; 56 | } 57 | #if MQTT_SERVER 58 | case PacketTypeConnect: { 59 | ConnectPayload *content = (ConnectPayload *)packet->payload; 60 | if (content->client_id) free(content->client_id); 61 | if (content->will_topic) free(content->will_topic); 62 | if (content->will_message) free(content->will_message); 63 | if (content->username) free(content->username); 64 | if (content->password) free(content->password); 65 | break; 66 | } 67 | case PacketTypeSubscribe: { 68 | SubscribePayload *content = (SubscribePayload *)packet->payload; 69 | if (content->topic) free(content->topic); 70 | break; 71 | } 72 | case PacketTypeUnsubscribe: { 73 | UnsubscribePayload *content = (UnsubscribePayload *)packet->payload; 74 | if (content->topic) free(content->topic); 75 | break; 76 | } 77 | #endif 78 | default: 79 | break; 80 | } 81 | free(packet->payload); 82 | packet->payload = NULL; 83 | free(packet); 84 | } 85 | 86 | 87 | uint16_t variable_length_int_decode(Buffer *buffer) { 88 | uint16_t result = buffer->data[buffer->position++] & 0x7f; 89 | uint16_t shift = 7; 90 | while (buffer->data[buffer->position - 1] & 0x80) { 91 | result += (buffer->data[buffer->position] & 0x7f) << shift; 92 | shift += 7; 93 | if (buffer_eof(buffer)) { 94 | break; // bail out, buffer exhausted 95 | } 96 | buffer->position++; 97 | } 98 | 99 | return result; 100 | } 101 | 102 | char *utf8_string_decode(Buffer *buffer) { 103 | char *result; 104 | 105 | if (buffer_free_space(buffer) < 2) { 106 | return NULL; // buffer too small 107 | } 108 | uint16_t sz = (buffer->data[buffer->position] << 8) + buffer->data[buffer->position + 1]; 109 | if (buffer_free_space(buffer) < (size_t)(sz + 2)) { 110 | return NULL; // incomplete buffer 111 | } 112 | buffer->position += 2; 113 | 114 | result = (char *)malloc(sz + 1); 115 | buffer_copy_out(buffer, result, sz); 116 | result[sz] = '\0'; 117 | return result; 118 | } 119 | 120 | size_t variable_length_int_encode(uint16_t value, Buffer *buffer) { 121 | if (value == 0) { 122 | buffer->data[buffer->position] = 0; 123 | buffer->position++; 124 | return 1; 125 | } 126 | 127 | size_t len = 0; 128 | while (value > 0) { 129 | if (buffer->position + len > buffer->len) { 130 | buffer->position += len - 1; 131 | return len - 1; // bail out, buffer exhausted 132 | } 133 | buffer->data[buffer->position + len] = value % 128; 134 | value = value / 128; 135 | if (value > 0){ 136 | buffer->data[buffer->position + len] |= 0x80; 137 | } 138 | len++; 139 | } 140 | buffer->position += len; 141 | return len; 142 | } 143 | 144 | size_t variable_length_int_size(uint16_t value) { 145 | if (value == 0) { 146 | return 1; 147 | } 148 | 149 | size_t len = 0; 150 | while (value > 0) { 151 | value = value / 128; 152 | len++; 153 | } 154 | return len; 155 | } 156 | 157 | size_t utf8_string_encode(char *string, Buffer *buffer) { 158 | size_t len = 0; 159 | 160 | if (string != NULL) { 161 | len = strlen(string); 162 | } 163 | 164 | if ((len > UINT16_MAX) || (buffer_free_space(buffer) < len + 2)) { 165 | return 0; // bail out, buffer too small 166 | } 167 | 168 | buffer->data[buffer->position] = (len & 0xff00) >> 8; 169 | buffer->data[buffer->position + 1] = (len & 0xff); 170 | buffer->position += 2; 171 | 172 | if (string != NULL) { 173 | (void)buffer_copy_in(string, buffer, len); 174 | } 175 | 176 | return len + 2; 177 | } 178 | 179 | /* 180 | * Decoder 181 | */ 182 | 183 | #if MQTT_SERVER 184 | bool decode_connect(Buffer *buffer, ConnectPayload *payload) { 185 | // Validate this is actually a connect packet 186 | char check[] = { 0x00, 0x04, 'M', 'Q', 'T', 'T' }; 187 | if (memcmp(buffer->data + buffer->position, check, sizeof(check)) != 0) { 188 | return false; 189 | } 190 | buffer->position += sizeof(check); 191 | 192 | payload->protocol_level = buffer->data[buffer->position++]; 193 | uint8_t flags = buffer->data[buffer->position++]; 194 | payload->clean_session = ((flags & 0x02) > 0); 195 | payload->keepalive_interval = 196 | (buffer->data[buffer->position] << 8) 197 | + buffer->data[buffer->position + 1]; 198 | buffer->position += 2; 199 | payload->client_id = utf8_string_decode(buffer); 200 | 201 | // last will 202 | if (flags & 0x04) { 203 | payload->will_topic = utf8_string_decode(buffer); 204 | payload->will_message = utf8_string_decode(buffer); 205 | } 206 | payload->will_qos = (MQTTQosLevel)((flags & 0x18) >> 3); 207 | payload->retain_will = (flags & 0x20) > 0; 208 | 209 | // username 210 | if (flags & 0x40) { 211 | payload->username = utf8_string_decode(buffer); 212 | } 213 | 214 | // password 215 | if (flags & 0x80) { 216 | payload->password = utf8_string_decode(buffer); 217 | } 218 | 219 | return true; 220 | } 221 | #endif /* MQTT_SERVER */ 222 | 223 | bool decode_connack(Buffer *buffer, ConnAckPayload *payload) { 224 | payload->session_present = buffer->data[buffer->position++] & 0x01; 225 | payload->status = (ConnAckStatus)buffer->data[buffer->position++]; 226 | 227 | return true; 228 | } 229 | 230 | bool decode_publish(Buffer *buffer, PublishPayload *payload, size_t sz) { 231 | uint8_t flags = buffer->data[buffer->position - 2] & 0x0f; 232 | uint16_t start_pos = (uint16_t)buffer->position; 233 | 234 | payload->qos = (MQTTQosLevel)((flags & 0x06) >> 1); 235 | payload->retain = ((flags & 0x01) > 0); 236 | payload->duplicate = ((flags & 0x08) > 0); 237 | 238 | payload->topic = utf8_string_decode(buffer); 239 | if (payload->qos != MQTT_QOS_0) { 240 | payload->packet_id = 241 | (buffer->data[buffer->position] << 8) 242 | + buffer->data[buffer->position + 1]; 243 | buffer->position += 2; 244 | } 245 | 246 | size_t len = sz - (buffer->position - start_pos) + 1; 247 | if (len > 1) { 248 | payload->message = (char *)calloc(1, len); 249 | memcpy(payload->message, buffer->data + buffer->position, len - 1); 250 | buffer->position += len - 1; 251 | } 252 | 253 | return true; 254 | } 255 | 256 | bool decode_packet_id(Buffer *buffer, PacketIDPayload *payload) { 257 | payload->packet_id = 258 | (buffer->data[buffer->position] << 8) 259 | + buffer->data[buffer->position + 1]; 260 | buffer->position += 2; 261 | return true; 262 | } 263 | 264 | #if MQTT_SERVER 265 | bool decode_subscribe(Buffer *buffer, SubscribePayload *payload) { 266 | payload->packet_id = 267 | (buffer->data[buffer->position] << 8) 268 | + buffer->data[buffer->position + 1]; 269 | buffer->position += 2; 270 | 271 | payload->topic = utf8_string_decode(buffer); 272 | payload->qos = (MQTTQosLevel)(buffer->data[buffer->position++] & 0x03); 273 | 274 | return true; 275 | } 276 | #endif /* MQTT_SERVER */ 277 | 278 | bool decode_suback(Buffer *buffer, SubAckPayload *payload) { 279 | payload->packet_id = 280 | (buffer->data[buffer->position] << 8) 281 | + buffer->data[buffer->position + 1]; 282 | buffer->position += 2; 283 | 284 | payload->status = (SubAckStatus)(buffer->data[buffer->position++]); 285 | 286 | return true; 287 | } 288 | 289 | #if MQTT_SERVER 290 | bool decode_unsubscribe(Buffer *buffer, UnsubscribePayload *payload) { 291 | payload->packet_id = 292 | (buffer->data[buffer->position] << 8) 293 | + buffer->data[buffer->position + 1]; 294 | buffer->position += 2; 295 | 296 | payload->topic = utf8_string_decode(buffer); 297 | 298 | return true; 299 | } 300 | #endif /* MQTT_SERVER */ 301 | 302 | 303 | MQTTPacket *mqtt_packet_decode(Buffer *buffer) { 304 | // validate that the buffer is big enough 305 | MQTTControlPacketType type = (MQTTControlPacketType)((buffer->data[buffer->position] & 0xf0) >> 4); 306 | buffer->position++; 307 | size_t packet_size = variable_length_int_decode(buffer); 308 | 309 | if (buffer_free_space(buffer) < packet_size) { 310 | return NULL; // buffer incomplete 311 | } 312 | MQTTPacket *result = allocate_MQTTPacket(type); 313 | 314 | bool valid = false; 315 | switch (type) { 316 | case PacketTypeConnAck: 317 | valid = decode_connack(buffer, (ConnAckPayload *)result->payload); 318 | break; 319 | case PacketTypePublish: 320 | valid = decode_publish(buffer, (PublishPayload *)result->payload, packet_size); 321 | break; 322 | case PacketTypeSubAck: 323 | valid = decode_suback(buffer, (SubAckPayload *)result->payload); 324 | break; 325 | case PacketTypePubAck: 326 | case PacketTypePubRec: 327 | case PacketTypePubRel: 328 | case PacketTypePubComp: 329 | case PacketTypeUnsubAck: 330 | valid = decode_packet_id(buffer, (PacketIDPayload *)result->payload); 331 | break; 332 | case PacketTypePingResp: 333 | case PacketTypeDisconnect: 334 | valid = true; // there is no payload 335 | break; 336 | 337 | #if MQTT_SERVER 338 | case PacketTypePingReq: 339 | valid = true; // there is no payload 340 | break; 341 | case PacketTypeConnect: 342 | valid = decode_connect(buffer, (ConnectPayload *)result->payload); 343 | break; 344 | case PacketTypeSubscribe: 345 | valid = decode_subscribe(buffer, (SubscribePayload *)result->payload); 346 | break; 347 | case PacketTypeUnsubscribe: 348 | valid = decode_unsubscribe(buffer, (UnsubscribePayload *)result->payload); 349 | break; 350 | #endif /* MQTT_SERVER */ 351 | 352 | default: 353 | valid = false; 354 | break; 355 | } 356 | 357 | if (!valid) { 358 | free_MQTTPacket(result); 359 | return NULL; 360 | } 361 | return result; 362 | } 363 | 364 | /* 365 | * Encoder 366 | */ 367 | 368 | Buffer *make_buffer_for_header(size_t sz, MQTTControlPacketType type) { 369 | sz += variable_length_int_size((uint16_t)sz); // size field 370 | sz += 1; // packet type and flags 371 | 372 | 373 | Buffer *buffer = buffer_allocate(sz); 374 | buffer->data[0] = type << 4; 375 | 376 | // MQTT Spec means we should set a bit in the flags field for some packet types 377 | switch (type) { 378 | case PacketTypePubRel: 379 | case PacketTypeSubscribe: 380 | case PacketTypeUnsubscribe: 381 | buffer->data[0] |= 0x02; 382 | break; 383 | default: 384 | break; 385 | } 386 | 387 | buffer->position += 1; 388 | variable_length_int_encode((uint16_t)(sz - 2), buffer); 389 | 390 | return buffer; 391 | } 392 | 393 | Buffer *encode_connect(ConnectPayload *payload) { 394 | size_t sz = 10 /* variable header */; 395 | sz += strlen(payload->client_id) + 2; 396 | if (payload->will_topic) { 397 | sz += strlen(payload->will_topic) + 2; 398 | sz += strlen(payload->will_message) + 2; 399 | } 400 | if (payload->username) { 401 | sz += strlen(payload->username) + 2; 402 | } 403 | if (payload->password) { 404 | sz += strlen(payload->password) + 2; 405 | } 406 | 407 | Buffer *buffer = make_buffer_for_header(sz, PacketTypeConnect); 408 | 409 | // variable header 410 | utf8_string_encode("MQTT", buffer); 411 | char *p = buffer->data + buffer->position; 412 | 413 | *(p++) = payload->protocol_level; 414 | 415 | uint8_t flags = ( 416 | ((payload->username) ? (1 << 7) : 0) 417 | + ((payload->password) ? (1 << 6) : 0) 418 | + ((payload->retain_will) ? (1 << 5) : 0) 419 | + ((payload->will_topic) ? (payload->will_qos << 3) : 0) 420 | + ((payload->will_topic) ? (1 << 2) : 0) 421 | + ((payload->clean_session) ? (1 << 1) : 0) 422 | ); 423 | *(p++) = flags; 424 | *(p++) = (payload->keepalive_interval & 0xff00) >> 8; 425 | *(p++) = (payload->keepalive_interval & 0xff); 426 | 427 | buffer->position += 4; 428 | 429 | // payload 430 | utf8_string_encode(payload->client_id, buffer); 431 | if (payload->will_topic) { 432 | utf8_string_encode(payload->will_topic, buffer); 433 | utf8_string_encode(payload->will_message, buffer); 434 | } 435 | if (payload->username) { 436 | utf8_string_encode(payload->username, buffer); 437 | } 438 | if (payload->password) { 439 | utf8_string_encode(payload->password, buffer); 440 | } 441 | 442 | assert(buffer_eof(buffer)); 443 | return buffer; 444 | } 445 | 446 | #if MQTT_SERVER 447 | Buffer *encode_connack(ConnAckPayload *payload) { 448 | size_t sz = 2; // session flag and status 449 | 450 | Buffer *buffer = make_buffer_for_header(sz, PacketTypeConnAck); 451 | buffer->data[buffer->position++] = payload->session_present; 452 | buffer->data[buffer->position++] = payload->status; 453 | 454 | assert(buffer_eof(buffer)); 455 | return buffer; 456 | } 457 | #endif /* MQTT_SERVER */ 458 | 459 | Buffer *encode_publish(PublishPayload *payload) { 460 | size_t sz = 0; 461 | sz += strlen(payload->topic) + 2; // topic 462 | if (payload->qos != MQTT_QOS_0) { 463 | sz += 2; // packet id 464 | } 465 | if (payload->message) { 466 | sz += strlen(payload->message); 467 | } 468 | 469 | Buffer *buffer = make_buffer_for_header(sz, PacketTypePublish); 470 | 471 | // Flags in header 472 | if (payload->retain) { 473 | buffer->data[buffer->position - 2] |= 1; 474 | } 475 | buffer->data[buffer->position - 2] |= (payload->qos << 1); 476 | if (payload->duplicate) { 477 | if (payload->qos == MQTT_QOS_0) { 478 | DEBUG_LOG("You can not set a DUP flag for QoS Level 0."); 479 | buffer_release(buffer); 480 | return NULL; 481 | } 482 | buffer->data[buffer->position - 2] |= 8; 483 | } 484 | 485 | // Variable header 486 | utf8_string_encode(payload->topic, buffer); 487 | if (payload->qos != MQTT_QOS_0) { 488 | buffer->data[buffer->position++] = (payload->packet_id & 0xff00) >> 8; 489 | buffer->data[buffer->position++] = (payload->packet_id & 0xff); 490 | } 491 | 492 | // Payload 493 | if (payload->message) { 494 | buffer_copy_in(payload->message, buffer, strlen(payload->message)); 495 | } 496 | 497 | assert(buffer_eof(buffer)); 498 | return buffer; 499 | } 500 | 501 | Buffer *encode_packet_id(PacketIDPayload *payload, MQTTControlPacketType type) { 502 | size_t sz = 2; // packet id 503 | 504 | Buffer *buffer = make_buffer_for_header(sz, type); 505 | 506 | // Variable header 507 | buffer->data[buffer->position++] = (payload->packet_id & 0xff00) >> 8; 508 | buffer->data[buffer->position++] = (payload->packet_id & 0xff); 509 | 510 | assert(buffer_eof(buffer)); 511 | return buffer; 512 | } 513 | 514 | Buffer *encode_subscribe(SubscribePayload *payload) { 515 | size_t sz = 2; // packet id 516 | sz += strlen(payload->topic) + 2; // topic 517 | sz += 1; // qos level 518 | 519 | Buffer *buffer = make_buffer_for_header(sz, PacketTypeSubscribe); 520 | 521 | // Variable header 522 | buffer->data[buffer->position++] = (payload->packet_id & 0xff00) >> 8; 523 | buffer->data[buffer->position++] = (payload->packet_id & 0xff); 524 | 525 | // Payload 526 | utf8_string_encode(payload->topic, buffer); 527 | buffer->data[buffer->position++] = payload->qos; 528 | 529 | assert(buffer_eof(buffer)); 530 | return buffer; 531 | } 532 | 533 | #if MQTT_SERVER 534 | Buffer *encode_suback(SubAckPayload *payload) { 535 | size_t sz = 2; // packet id 536 | sz += 1; // Status code 537 | 538 | Buffer *buffer = make_buffer_for_header(sz, PacketTypeSubAck); 539 | 540 | // Variable header 541 | buffer->data[buffer->position++] = (payload->packet_id & 0xff00) >> 8; 542 | buffer->data[buffer->position++] = (payload->packet_id & 0xff); 543 | 544 | // Payload 545 | buffer->data[buffer->position++] = payload->status; 546 | 547 | assert(buffer_eof(buffer)); 548 | return buffer; 549 | } 550 | #endif /* MQTT_SERVER */ 551 | 552 | Buffer *encode_unsubscribe(UnsubscribePayload *payload) { 553 | size_t sz = 2; // packet id 554 | sz += strlen(payload->topic) + 2; // topic 555 | 556 | Buffer *buffer = make_buffer_for_header(sz, PacketTypeUnsubscribe); 557 | 558 | // Variable header 559 | buffer->data[buffer->position++] = (payload->packet_id & 0xff00) >> 8; 560 | buffer->data[buffer->position++] = (payload->packet_id & 0xff); 561 | 562 | // Payload 563 | utf8_string_encode(payload->topic, buffer); 564 | 565 | assert(buffer_eof(buffer)); 566 | return buffer; 567 | } 568 | 569 | Buffer *encode_no_payload(MQTTControlPacketType type) { 570 | Buffer *buffer = make_buffer_for_header(0, type); 571 | assert(buffer_eof(buffer)); 572 | return buffer; 573 | } 574 | 575 | 576 | Buffer *mqtt_packet_encode(MQTTPacket *packet) { 577 | switch (packet->packet_type) { 578 | case PacketTypeConnect: 579 | return encode_connect((ConnectPayload *)packet->payload); 580 | case PacketTypePublish: 581 | return encode_publish((PublishPayload *)packet->payload); 582 | case PacketTypeSubscribe: 583 | return encode_subscribe((SubscribePayload *)packet->payload); 584 | case PacketTypeUnsubscribe: 585 | return encode_unsubscribe((UnsubscribePayload *)packet->payload); 586 | case PacketTypePubAck: 587 | case PacketTypePubRec: 588 | case PacketTypePubRel: 589 | case PacketTypePubComp: 590 | return encode_packet_id((PacketIDPayload *)packet->payload, packet->packet_type); 591 | case PacketTypePingReq: 592 | case PacketTypeDisconnect: 593 | return encode_no_payload(packet->packet_type); 594 | 595 | #if MQTT_SERVER 596 | case PacketTypePingResp: 597 | return encode_no_payload(packet->packet_type); 598 | case PacketTypeUnsubAck: 599 | return encode_packet_id((PacketIDPayload *)packet->payload, packet->packet_type); 600 | case PacketTypeConnAck: 601 | return encode_connack((ConnAckPayload *)packet->payload); 602 | case PacketTypeSubAck: 603 | return encode_suback((SubAckPayload *)packet->payload); 604 | #endif /* MQTT_SERVER */ 605 | 606 | default: 607 | return NULL; 608 | } 609 | } 610 | 611 | /* 612 | * Helper functions 613 | */ 614 | 615 | uint16_t get_packet_id(MQTTPacket *packet) { 616 | switch(packet->packet_type) { 617 | case PacketTypePublish: 618 | return ((PublishPayload *)packet->payload)->packet_id; 619 | case PacketTypeSubscribe: 620 | return ((SubscribePayload *)packet->payload)->packet_id; 621 | case PacketTypeSubAck: 622 | return ((SubAckPayload *)packet->payload)->packet_id; 623 | case PacketTypeUnsubscribe: 624 | return ((UnsubscribePayload *)packet->payload)->packet_id; 625 | 626 | // the following ones are identical 627 | case PacketTypePubAck: 628 | case PacketTypePubRec: 629 | case PacketTypePubRel: 630 | case PacketTypePubComp: 631 | case PacketTypeUnsubAck: 632 | return ((PacketIDPayload *)packet->payload)->packet_id; 633 | 634 | // not in list -> no packet_id, revert to invalid 0 635 | default: 636 | return 0; // no packet id in payload 637 | } 638 | } 639 | 640 | char *get_packet_name(MQTTPacket *packet) { 641 | switch (packet->packet_type) { 642 | case PacketTypeConnect: return "CONNECT"; 643 | case PacketTypeConnAck: return "CONNACK"; 644 | case PacketTypePublish: return "PUBLISH"; 645 | case PacketTypePubAck: return "PUBACK"; 646 | case PacketTypePubRec: return "PUBREC"; 647 | case PacketTypePubRel: return "PUBREL"; 648 | case PacketTypePubComp: return "PUBCOMP"; 649 | case PacketTypeSubscribe: return "SUBSCRIBE"; 650 | case PacketTypeSubAck: return "SUBACK"; 651 | case PacketTypeUnsubscribe: return "UNSUBSCRIBE"; 652 | case PacketTypeUnsubAck: return "UNSUBACK"; 653 | case PacketTypePingReq: return "PINGREQ"; 654 | case PacketTypePingResp: return "PINGRESP"; 655 | case PacketTypeDisconnect: return "DISCONNECT"; 656 | } 657 | return "[UNKNOWN]"; 658 | } 659 | -------------------------------------------------------------------------------- /src/packet.h: -------------------------------------------------------------------------------- 1 | #ifndef packet_h__included 2 | #define packet_h__included 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mqtt.h" 9 | #include "buffer.h" 10 | 11 | typedef enum { 12 | PacketTypeConnect = 1, 13 | PacketTypeConnAck = 2, 14 | PacketTypePublish = 3, 15 | PacketTypePubAck = 4, 16 | PacketTypePubRec = 5, 17 | PacketTypePubRel = 6, 18 | PacketTypePubComp = 7, 19 | PacketTypeSubscribe = 8, 20 | PacketTypeSubAck = 9, 21 | PacketTypeUnsubscribe = 10, 22 | PacketTypeUnsubAck = 11, 23 | PacketTypePingReq = 12, 24 | PacketTypePingResp = 13, 25 | PacketTypeDisconnect = 14 26 | } MQTTControlPacketType; 27 | 28 | typedef struct { 29 | // required: 30 | char *client_id; 31 | int protocol_level; 32 | uint16_t keepalive_interval; 33 | 34 | // optional 35 | char *username; 36 | char *password; 37 | char *will_topic; 38 | char *will_message; 39 | MQTTQosLevel will_qos; 40 | bool retain_will; 41 | bool clean_session; 42 | } ConnectPayload; 43 | 44 | typedef enum { 45 | ConnAckStatusAccepted = 0, /**< Connection accepted */ 46 | ConnAckStatusInvalidProtocolLevel = 1, /**< Protocol level not supported */ 47 | ConnAckStatusInvalidIdentifier = 2, /**< Client ID not accepted */ 48 | ConnAckStatusServerUnavailable = 3, /**< Server restarting or too many clients */ 49 | ConnAckStatusAuthenticationError = 4, /**< missing username/password */ 50 | ConnAckStatusNotAuthorized = 5 /**< not authorized */ 51 | } ConnAckStatus; 52 | 53 | typedef struct { 54 | bool session_present; 55 | ConnAckStatus status; 56 | } ConnAckPayload; 57 | 58 | typedef struct { 59 | bool duplicate; 60 | MQTTQosLevel qos; 61 | bool retain; 62 | 63 | char *topic; 64 | uint16_t packet_id; 65 | 66 | char *message; 67 | } PublishPayload; 68 | 69 | typedef struct { 70 | uint16_t packet_id; 71 | } PacketIDPayload; 72 | 73 | #define PubAckPayload PacketIDPayload 74 | #define PubRecPayload PacketIDPayload 75 | #define PubRelPayload PacketIDPayload 76 | #define PubCompPayload PacketIDPayload 77 | 78 | typedef struct { 79 | uint16_t packet_id; 80 | char *topic; 81 | MQTTQosLevel qos; 82 | } SubscribePayload; 83 | 84 | typedef enum { 85 | SubAckStatusQoS0 = 0, 86 | SubAckStatusQoS1 = 1, 87 | SubAckStatusQoS2 = 2, 88 | SubAckFailure = 0x80 89 | } SubAckStatus; 90 | 91 | typedef struct { 92 | uint16_t packet_id; 93 | SubAckStatus status; 94 | } SubAckPayload; 95 | 96 | typedef struct { 97 | uint16_t packet_id; 98 | char *topic; 99 | } UnsubscribePayload; 100 | 101 | #define UnsubAckPayload PacketIDPayload 102 | 103 | typedef struct { 104 | MQTTControlPacketType packet_type; 105 | void *payload; 106 | } MQTTPacket; 107 | 108 | /* 109 | * Decoder 110 | */ 111 | MQTTPacket *mqtt_packet_decode(Buffer *buffer); 112 | void free_MQTTPacket(MQTTPacket *packet); 113 | 114 | /* 115 | * Encoder 116 | */ 117 | 118 | Buffer *mqtt_packet_encode(MQTTPacket *packet); 119 | MQTTPacket *allocate_MQTTPacket(MQTTControlPacketType type); 120 | 121 | /* 122 | * Utility 123 | */ 124 | 125 | uint16_t get_packet_id(MQTTPacket *packet); 126 | char *get_packet_name(MQTTPacket *packet); 127 | 128 | #endif /* packet_h__included */ 129 | -------------------------------------------------------------------------------- /src/protocol.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "mqtt_internal.h" 5 | #include "packet.h" 6 | #include "buffer.h" 7 | 8 | #include "debug.h" 9 | 10 | typedef struct { 11 | PublishPayload *payload; 12 | MQTTPublishEventHandler callback; 13 | MQTTQosLevel qos; 14 | } PublishCallback; 15 | 16 | /* 17 | * Utility 18 | */ 19 | 20 | bool send_buffer(MQTTHandle *handle, Buffer *buffer) { 21 | PlatformStatusCode ret = platform_write(handle, buffer); 22 | buffer_release(buffer); 23 | return (ret == PlatformStatusOk); 24 | } 25 | 26 | /* 27 | * QoS event handlers 28 | */ 29 | 30 | void handle_puback_pubcomp(MQTTHandle *handle, void *context) { 31 | PublishCallback *ctx = (PublishCallback *)context; 32 | 33 | if (ctx->callback) { 34 | ctx->callback(handle, ctx->payload->topic, ctx->payload->message); 35 | } 36 | 37 | free(ctx->payload); 38 | free(ctx); 39 | } 40 | 41 | void handle_pubrec(MQTTHandle *handle, void *context) { 42 | PublishCallback *ctx = (PublishCallback *)context; 43 | 44 | PubRelPayload newPayload = { 45 | .packet_id = ctx->payload->packet_id 46 | }; 47 | 48 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubRel, &newPayload }); 49 | expect_packet(handle, PacketTypePubComp, ctx->payload->packet_id, handle_puback_pubcomp, context); 50 | 51 | encoded->position = 0; 52 | send_buffer(handle, encoded); 53 | } 54 | 55 | void handle_pubrel(MQTTHandle *handle, void *context) { 56 | PublishCallback *ctx = (PublishCallback *)context; 57 | 58 | PubCompPayload newPayload = { 59 | .packet_id = ctx->payload->packet_id 60 | }; 61 | 62 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubComp, &newPayload }); 63 | encoded->position = 0; 64 | if (send_buffer(handle, encoded)) { 65 | if (ctx->callback) { 66 | ctx->callback(handle, ctx->payload->topic, ctx->payload->message); 67 | } 68 | } 69 | 70 | free(ctx->payload->topic); 71 | free(ctx->payload->message); 72 | free(ctx->payload); 73 | free(ctx); 74 | } 75 | 76 | /* 77 | * packet constructors 78 | */ 79 | 80 | #if MQTT_CLIENT 81 | bool send_connect_packet(MQTTHandle *handle) { 82 | ConnectPayload *payload = (ConnectPayload *)calloc(1, sizeof(ConnectPayload)); 83 | 84 | payload->client_id = handle->config->client_id; 85 | payload->protocol_level = 4; 86 | payload->keepalive_interval = KEEPALIVE_INTERVAL; 87 | payload->clean_session = handle->config->clean_session; 88 | 89 | payload->will_topic = handle->config->last_will_topic; 90 | payload->will_message = handle->config->last_will_message; 91 | payload->will_qos = MQTT_QOS_0; 92 | payload->retain_will = handle->config->last_will_retain; 93 | 94 | payload->username = handle->config->username; 95 | payload->password = handle->config->password; 96 | 97 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeConnect, payload }); 98 | free(payload); 99 | 100 | // ConnAck waiting packet added to queue from _mqtt_connect 101 | 102 | encoded->position = 0; 103 | return send_buffer(handle, encoded); 104 | } 105 | #endif /* MQTT_CLIENT */ 106 | 107 | void remove_pending(MQTTHandle *handle, void *context) { 108 | SubscribePayload *payload = (SubscribePayload *)context; 109 | 110 | subscription_set_pending(handle, payload->topic, false); 111 | 112 | free(payload->topic); 113 | free(payload); 114 | } 115 | 116 | #if MQTT_CLIENT 117 | bool send_subscribe_packet(MQTTHandle *handle, char *topic, MQTTQosLevel qos) { 118 | SubscribePayload *payload = (SubscribePayload *)calloc(1, sizeof(SubscribePayload)); 119 | 120 | payload->packet_id = (++handle->packet_id_counter > 0) ? handle->packet_id_counter : ++handle->packet_id_counter; 121 | payload->topic = strdup(topic); 122 | payload->qos = qos; 123 | 124 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeSubscribe, payload }); 125 | 126 | // add waiting for SubAck to queue 127 | expect_packet(handle, PacketTypeSubAck, payload->packet_id, remove_pending, payload); 128 | 129 | encoded->position = 0; 130 | return send_buffer(handle, encoded); 131 | } 132 | #endif /* MQTT_CLIENT */ 133 | 134 | #if MQTT_CLIENT 135 | bool send_unsubscribe_packet(MQTTHandle *handle, char *topic) { 136 | UnsubscribePayload *payload = (UnsubscribePayload *)calloc(1, sizeof(UnsubscribePayload)); 137 | 138 | payload->packet_id = (++handle->packet_id_counter > 0) ? handle->packet_id_counter : ++handle->packet_id_counter; 139 | payload->topic = topic; 140 | 141 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeUnsubscribe, payload }); 142 | 143 | // add waiting for UnsubAck to queue 144 | expect_packet(handle, PacketTypeUnsubAck, payload->packet_id, NULL, NULL); 145 | free(payload); 146 | 147 | encoded->position = 0; 148 | return send_buffer(handle, encoded); 149 | } 150 | #endif /* MQTT_CLIENT */ 151 | 152 | bool send_publish_packet(MQTTHandle *handle, char *topic, char *message, MQTTQosLevel qos, MQTTPublishEventHandler callback) { 153 | PublishPayload *payload = (PublishPayload *)calloc(1, sizeof(PublishPayload)); 154 | 155 | payload->qos = qos; 156 | payload->retain = true; 157 | payload->topic = topic; 158 | payload->packet_id = (++handle->packet_id_counter > 0) ? handle->packet_id_counter : ++handle->packet_id_counter; 159 | payload->message = message; 160 | 161 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePublish, payload }); 162 | encoded->position = 0; 163 | bool result = send_buffer(handle, encoded); 164 | if (!result) { 165 | free(payload); 166 | return false; 167 | } 168 | 169 | // Handle QoS and add waiting packets to queue 170 | switch(payload->qos) { 171 | case MQTT_QOS_0: 172 | // fire and forget 173 | if (callback) { 174 | callback(handle, payload->topic, payload->message); 175 | } 176 | free(payload); 177 | break; 178 | case MQTT_QOS_1: { 179 | PublishCallback *ctx = (PublishCallback *)malloc(sizeof(PublishCallback)); 180 | ctx->payload = payload; 181 | ctx->callback = callback; 182 | ctx->qos = payload->qos; 183 | expect_packet(handle, PacketTypePubAck, payload->packet_id, handle_puback_pubcomp, ctx); 184 | break; 185 | } 186 | case MQTT_QOS_2: { 187 | PublishCallback *ctx = (PublishCallback *)malloc(sizeof(PublishCallback)); 188 | ctx->payload = payload; 189 | ctx->callback = callback; 190 | ctx->qos = payload->qos; 191 | expect_packet(handle, PacketTypePubRec, payload->packet_id, handle_pubrec, ctx); 192 | break; 193 | } 194 | } 195 | 196 | return true; 197 | } 198 | 199 | #if MQTT_CLIENT 200 | bool send_ping_packet(MQTTHandle *handle) { 201 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePingReq, NULL }); 202 | encoded->position = 0; 203 | return send_buffer(handle, encoded); 204 | } 205 | #endif /* MQTT_CLIENT */ 206 | 207 | #if MQTT_CLIENT 208 | bool send_disconnect_packet(MQTTHandle *handle) { 209 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeDisconnect, NULL }); 210 | encoded->position = 0; 211 | return send_buffer(handle, encoded); 212 | } 213 | #endif /* MQTT_CLIENT */ 214 | 215 | #if MQTT_CLIENT 216 | bool send_puback_packet(MQTTHandle *handle, uint16_t packet_id) { 217 | PacketIDPayload payload = { 0 }; 218 | payload.packet_id = packet_id; 219 | 220 | DEBUG_LOG("Sending PUBACK"); 221 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubAck, &payload }); 222 | encoded->position = 0; 223 | return send_buffer(handle, encoded); 224 | } 225 | #endif /* MQTT_CLIENT */ 226 | 227 | #if MQTT_CLIENT 228 | bool send_pubrec_packet(MQTTHandle *handle, uint16_t packet_id, MQTTPublishEventHandler callback, PublishPayload *publish) { 229 | PacketIDPayload payload = { 0 }; 230 | payload.packet_id = packet_id; 231 | 232 | PublishCallback *ctx = (PublishCallback *)malloc(sizeof(PublishCallback)); 233 | ctx->payload = malloc(sizeof(PublishPayload)); 234 | memcpy(ctx->payload, publish, sizeof(PublishPayload)); 235 | ctx->payload->topic = strdup(publish->topic); 236 | ctx->payload->message = strdup(publish->message); 237 | ctx->callback = callback; 238 | ctx->qos = MQTT_QOS_2; 239 | 240 | expect_packet(handle, PacketTypePubRel, packet_id, handle_pubrel, ctx); 241 | 242 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubRec, &payload }); 243 | encoded->position = 0; 244 | return send_buffer(handle, encoded); 245 | } 246 | #endif /* MQTT_CLIENT */ 247 | -------------------------------------------------------------------------------- /src/protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef protocol_h__included 2 | #define protocol_h__included 3 | 4 | #include "mqtt.h" 5 | 6 | #if MQTT_CLIENT 7 | bool send_connect_packet(MQTTHandle *handle); 8 | bool send_subscribe_packet(MQTTHandle *handle, char *topic, MQTTQosLevel qos); 9 | bool send_unsubscribe_packet(MQTTHandle *handle, char *topic); 10 | bool send_ping_packet(MQTTHandle *handle); 11 | bool send_disconnect_packet(MQTTHandle *handle); 12 | bool send_puback_packet(MQTTHandle *handle, uint16_t packet_id); 13 | bool send_pubrec_packet(MQTTHandle *handle, uint16_t packet_id, MQTTPublishEventHandler callback, PublishPayload *publish); 14 | #endif /* MQTT_CLIENT */ 15 | 16 | bool send_publish_packet(MQTTHandle *handle, char *topic, char *message, MQTTQosLevel qos, MQTTPublishEventHandler callback); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/state_queue.c: -------------------------------------------------------------------------------- 1 | #include "mqtt_internal.h" 2 | #include "state_queue.h" 3 | #include "debug.h" 4 | 5 | #if 0 6 | static inline void dump_expected(MQTTHandle *handle) { 7 | MQTTCallbackQueueItem *item = handle->queue.pending; 8 | 9 | DEBUG_LOG("Expected packets:") 10 | 11 | while (item != NULL) { 12 | DEBUG_LOG(" - Type: %d, packet_id: %d", item->type, item->packet_id); 13 | 14 | item = item->next; 15 | } 16 | } 17 | #endif 18 | 19 | void expect_packet(MQTTHandle *handle, MQTTControlPacketType type, uint16_t packet_id, MQTTEventHandler callback, void *context) { 20 | MQTTCallbackQueueItem *item = (MQTTCallbackQueueItem *)calloc(1, sizeof(MQTTCallbackQueueItem)); 21 | 22 | item->type = type; 23 | item->packet_id = packet_id; 24 | item->callback = callback; 25 | item->context = context; 26 | 27 | // insert at start 28 | if (handle->queue.pending != NULL) { 29 | item->next = handle->queue.pending; 30 | } 31 | 32 | handle->queue.pending = item; 33 | // dump_expected(handle); 34 | } 35 | 36 | void remove_from_queue(MQTTHandle *handle, MQTTCallbackQueueItem *remove) { 37 | MQTTCallbackQueueItem *item = handle->queue.pending; 38 | MQTTCallbackQueueItem *prev_item = NULL; 39 | 40 | while (item != NULL) { 41 | if (item == remove) { 42 | // remove from queue 43 | if (prev_item == NULL) { 44 | // no prev item, attach directly to queue 45 | handle->queue.pending = item->next; 46 | } else { 47 | // attach next item to prev item removing this one 48 | prev_item->next = item->next; 49 | } 50 | 51 | break; 52 | } 53 | prev_item = item; 54 | item = item->next; 55 | } 56 | } 57 | 58 | void clear_packet_queue(MQTTHandle *handle) { 59 | MQTTCallbackQueueItem *item = handle->queue.pending; 60 | handle->queue.pending = NULL; 61 | 62 | while (item != NULL) { 63 | MQTTCallbackQueueItem *current = item; 64 | item = item->next; 65 | 66 | // free stuff inside 67 | switch (current->type) { 68 | case PacketTypePubRel: { 69 | PublishPayload *payload = (PublishPayload *)current->context; 70 | free(payload->topic); 71 | free(payload->message); 72 | free(payload); 73 | break; 74 | } 75 | case PacketTypePubAck: 76 | case PacketTypePubComp: 77 | case PacketTypeSubAck: 78 | free(current->context); 79 | break; 80 | 81 | default: 82 | break; 83 | } 84 | 85 | free(current); 86 | } 87 | } 88 | 89 | bool dispatch_packet(MQTTHandle *handle, MQTTPacket *packet) { 90 | MQTTCallbackQueueItem *item = handle->queue.pending; 91 | uint16_t packet_id = get_packet_id(packet); 92 | 93 | while (item != NULL) { 94 | if ((item->type == packet->packet_type) && (item->packet_id == packet_id)) { 95 | remove_from_queue(handle, item); 96 | if (item->callback) { 97 | item->callback(handle, item->context); 98 | } 99 | free(item); 100 | 101 | return true; 102 | } 103 | item = item->next; 104 | } 105 | 106 | // not found 107 | return false; 108 | } 109 | -------------------------------------------------------------------------------- /src/state_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef state_queue_h__included 2 | #define state_queue_h__included 3 | 4 | #include 5 | #include "mqtt.h" 6 | #include "packet.h" 7 | 8 | typedef struct _MQTTCallbackQueueItem { 9 | struct _MQTTCallbackQueueItem *next; 10 | 11 | MQTTControlPacketType type; 12 | uint16_t packet_id; 13 | void *context; 14 | MQTTEventHandler callback; 15 | } MQTTCallbackQueueItem; 16 | 17 | typedef struct { 18 | MQTTCallbackQueueItem *pending; 19 | } MQTTCallbackQueue; 20 | 21 | void expect_packet(MQTTHandle *handle, MQTTControlPacketType type, uint16_t packet_id, MQTTEventHandler callback, void *context); 22 | bool dispatch_packet(MQTTHandle *handle, MQTTPacket *packet); 23 | void clear_packet_queue(MQTTHandle *handle); 24 | 25 | #endif /* state_queue_h__included */ 26 | -------------------------------------------------------------------------------- /src/subscriptions.c: -------------------------------------------------------------------------------- 1 | #include "mqtt_internal.h" 2 | #include "subscriptions.h" 3 | 4 | void add_subscription(MQTTHandle *handle, char *topic, MQTTQosLevel qos, MQTTPublishEventHandler callback) { 5 | SubscriptionItem *item = (SubscriptionItem *)calloc(1, sizeof(SubscriptionItem)); 6 | 7 | item->topic = topic; 8 | item->qos = qos; 9 | item->handler = callback; 10 | item->pending = true; 11 | 12 | // insert at start 13 | if (handle->subscriptions.items != NULL) { 14 | item->next = handle->subscriptions.items; 15 | } 16 | 17 | handle->subscriptions.items = item; 18 | } 19 | 20 | void remove_subscription(MQTTHandle *handle, char *topic) { 21 | SubscriptionItem *item = handle->subscriptions.items; 22 | SubscriptionItem *prev = NULL; 23 | 24 | while (item != NULL) { 25 | if (strcmp(topic, item->topic) == 0) { 26 | if (prev == NULL) { 27 | handle->subscriptions.items = item->next; 28 | } else { 29 | prev->next = item->next; 30 | } 31 | 32 | free(item); 33 | break; 34 | } 35 | 36 | prev = item; 37 | item = item->next; 38 | } 39 | } 40 | 41 | void remove_all_subscriptions(MQTTHandle *handle) { 42 | SubscriptionItem *item = handle->subscriptions.items; 43 | SubscriptionItem *prev = NULL; 44 | 45 | while (item != NULL) { 46 | prev = item; 47 | item = item->next; 48 | free(prev); 49 | } 50 | handle->subscriptions.items = NULL; 51 | } 52 | 53 | void subscription_set_pending(MQTTHandle *handle, char *topic, bool pending) { 54 | SubscriptionItem *item = handle->subscriptions.items; 55 | 56 | while (item != NULL) { 57 | if (strcmp(topic, item->topic) == 0) { 58 | item->pending = pending; 59 | break; 60 | } 61 | 62 | item = item->next; 63 | } 64 | } 65 | 66 | void dispatch_subscription(MQTTHandle *handle, PublishPayload *payload) { 67 | SubscriptionItem *item = handle->subscriptions.items; 68 | 69 | while (item != NULL) { 70 | if ((item->pending == false) && (strcmp(payload->topic, item->topic) == 0)) { 71 | if (item->handler) { 72 | item->handler(handle, payload->topic, payload->message); 73 | } 74 | break; 75 | } 76 | 77 | item = item->next; 78 | } 79 | } 80 | 81 | void dispatch_subscription_direct(MQTTHandle *handle, char *topic, char *message) { 82 | SubscriptionItem *item = handle->subscriptions.items; 83 | 84 | while (item != NULL) { 85 | if ((item->pending == false) && (strcmp(topic, item->topic) == 0)) { 86 | if (item->handler) { 87 | item->handler(handle, topic, message); 88 | } 89 | break; 90 | } 91 | 92 | item = item->next; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/subscriptions.h: -------------------------------------------------------------------------------- 1 | #ifndef subscriptions_h__included 2 | #define subscriptions_h__included 3 | 4 | #include "mqtt.h" 5 | 6 | typedef struct _SubscriptionItem { 7 | struct _SubscriptionItem *next; 8 | 9 | char *topic; 10 | MQTTQosLevel qos; 11 | MQTTPublishEventHandler handler; 12 | bool pending; 13 | } SubscriptionItem; 14 | 15 | typedef struct { 16 | SubscriptionItem *items; 17 | } Subscriptions; 18 | 19 | void add_subscription(MQTTHandle *handle, char *topic, MQTTQosLevel qos, MQTTPublishEventHandler callback); 20 | void remove_subscription(MQTTHandle *handle, char *topic); 21 | void remove_all_subscriptions(MQTTHandle *handle); 22 | void subscription_set_pending(MQTTHandle *handle, char *topic, bool pending); 23 | 24 | void dispatch_subscription(MQTTHandle *handle, PublishPayload *payload); 25 | void dispatch_subscription_direct(MQTTHandle *handle, char *topic, char *message); 26 | 27 | #endif /* subscription_h__included */ 28 | -------------------------------------------------------------------------------- /tests/connect_publish.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "platform.h" 5 | #include "mqtt.h" 6 | 7 | int leave = 0; 8 | 9 | #define LOG(fmt, ...) fprintf(stdout, fmt "\n", ## __VA_ARGS__) 10 | 11 | bool err_handler(MQTTHandle *handle, MQTTConfig *config, MQTTErrorCode error) { 12 | LOG("Error received: %d", error); 13 | exit(1); 14 | 15 | return true; 16 | } 17 | 18 | void publish_handler(MQTTHandle *handle, char *topic, char *message) { 19 | LOG("Published %s -> %s", topic, message); 20 | 21 | leave++; 22 | } 23 | 24 | void mqtt_connected(MQTTHandle *handle, void *context) { 25 | LOG("Connected!"); 26 | MQTTStatus result; 27 | 28 | LOG("Trying publish to testsuite/mqtt/test..."); 29 | result = mqtt_publish(handle, "testsuite/mqtt/test", "payload1", MQTT_QOS_0, publish_handler); 30 | if (result != MQTT_STATUS_OK) { 31 | LOG("Could not publish"); 32 | exit(1); 33 | } 34 | 35 | LOG("Trying publish to testsuite/mqtt/test_qos1..."); 36 | result = mqtt_publish(handle, "testsuite/mqtt/test_qos1", "payload2", MQTT_QOS_1, publish_handler); 37 | if (result != MQTT_STATUS_OK) { 38 | LOG("Could not publish"); 39 | exit(1); 40 | } 41 | 42 | LOG("Trying publish to testsuite/mqtt/test_qos2..."); 43 | result = mqtt_publish(handle, "testsuite/mqtt/test_qos2", "payload3", MQTT_QOS_2, publish_handler); 44 | if (result != MQTT_STATUS_OK) { 45 | LOG("Could not publish"); 46 | exit(1); 47 | } 48 | } 49 | 50 | int main(int argc, char **argv) { 51 | MQTTConfig config = { 0 }; 52 | 53 | config.client_id = "libmqtt_testsuite_this_is_too_long"; 54 | config.hostname = "localhost"; 55 | 56 | config.last_will_topic = "testsuite/last_will"; 57 | config.last_will_message = "RIP"; 58 | 59 | LOG("Testing too long client id..."); 60 | MQTTHandle *mqtt = mqtt_connect(&config, mqtt_connected, NULL, err_handler); 61 | if (mqtt != NULL) { 62 | LOG("Handle should be NULL, but it wasn't"); 63 | return 1; 64 | } 65 | 66 | config.client_id = "libmqtt_testsuite"; 67 | LOG("Trying to connect to %s", config.hostname); 68 | mqtt = mqtt_connect(&config, mqtt_connected, NULL, err_handler); 69 | 70 | if (mqtt == NULL) { 71 | LOG("Connection failed!"); 72 | return 1; 73 | } 74 | 75 | int cancel = 0; 76 | while (leave < 3) { 77 | LOG("Waiting..."); 78 | platform_sleep(1000); 79 | cancel++; 80 | if (cancel == 10) { 81 | LOG("Giving up!"); 82 | return 1; 83 | } 84 | } 85 | 86 | LOG("Waiting for ping to happen..."); 87 | platform_sleep(5000); 88 | 89 | LOG("Disconnecting..."); 90 | MQTTStatus result = mqtt_disconnect(mqtt, NULL, NULL); 91 | if (result != MQTT_STATUS_OK) { 92 | LOG("Could not disconnect"); 93 | exit(1); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/connect_reconnect.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "platform.h" 5 | #include "mqtt.h" 6 | 7 | int leave = 0; 8 | 9 | #define LOG(fmt, ...) fprintf(stdout, fmt "\n", ## __VA_ARGS__) 10 | 11 | bool err_handler(MQTTHandle *handle, MQTTConfig *config, MQTTErrorCode error) { 12 | LOG("Error received: %d", error); 13 | 14 | return 1; 15 | } 16 | 17 | void mqtt_reconnected(MQTTHandle *handle, void *context) { 18 | LOG("Reconnected!"); 19 | } 20 | 21 | void callback(MQTTHandle *handle, char *topic, char *payload) { 22 | LOG("Received publish: %s -> %s", topic, payload); 23 | 24 | if (leave == 0) { 25 | mqtt_reconnect(handle, mqtt_reconnected, NULL); 26 | } 27 | 28 | leave++; 29 | } 30 | 31 | void mqtt_connected(MQTTHandle *handle, void *context) { 32 | LOG("Connected!"); 33 | 34 | LOG("Trying subscribe on testsuite/mqtt/test..."); 35 | MQTTStatus result = mqtt_subscribe(handle, "testsuite/mqtt/test", MQTT_QOS_0, callback); 36 | if (result != MQTT_STATUS_OK) { 37 | LOG("Could not subscribe test"); 38 | exit(1); 39 | } 40 | } 41 | 42 | int main(int argc, char **argv) { 43 | MQTTConfig config = { 0 }; 44 | 45 | config.client_id = "libmqtt_testsuite"; 46 | config.hostname = "localhost"; 47 | config.clean_session = true; 48 | 49 | LOG("Trying to connect to %s...", config.hostname); 50 | MQTTHandle *mqtt = mqtt_connect(&config, mqtt_connected, NULL, err_handler); 51 | 52 | if (mqtt == NULL) { 53 | LOG("Connection failed!"); 54 | return 1; 55 | } 56 | 57 | while (leave < 1) { 58 | LOG("Waiting for first publish..."); 59 | platform_sleep(1000); 60 | } 61 | 62 | while (leave < 2) { 63 | LOG("Waiting for second publish..."); 64 | platform_sleep(1000); 65 | } 66 | 67 | LOG("Disconnecting..."); 68 | mqtt_disconnect(mqtt, NULL, NULL); 69 | } 70 | -------------------------------------------------------------------------------- /tests/connect_subscribe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "platform.h" 5 | #include "mqtt.h" 6 | 7 | bool leave = false; 8 | 9 | #define LOG(fmt, ...) fprintf(stdout, fmt "\n", ## __VA_ARGS__) 10 | 11 | bool err_handler(MQTTHandle *handle, MQTTConfig *config, MQTTErrorCode error) { 12 | LOG("Error received: %d", error); 13 | 14 | return 1; 15 | } 16 | 17 | void callback(MQTTHandle *handle, char *topic, char *payload) { 18 | LOG("Received publish: %s -> %s", topic, payload); 19 | 20 | MQTTStatus result = mqtt_unsubscribe(handle, "testsuite/mqtt/test"); 21 | if (result != MQTT_STATUS_OK) { 22 | LOG("Could not unsubscribe test"); 23 | exit(1); 24 | } 25 | 26 | result = mqtt_unsubscribe(handle, "testsuite/mqtt/test2"); 27 | if (result != MQTT_STATUS_OK) { 28 | LOG("Could not unsubscribe test 2"); 29 | exit(1); 30 | } 31 | 32 | leave = true; 33 | } 34 | 35 | void mqtt_connected(MQTTHandle *handle, void *context) { 36 | LOG("Connected!"); 37 | 38 | LOG("Trying subscribe on testsuite/mqtt/test..."); 39 | MQTTStatus result = mqtt_subscribe(handle, "testsuite/mqtt/test", MQTT_QOS_0, callback); 40 | if (result != MQTT_STATUS_OK) { 41 | LOG("Could not subscribe test"); 42 | exit(1); 43 | } 44 | 45 | result = mqtt_subscribe(handle, "testsuite/mqtt/test2", MQTT_QOS_0, callback); 46 | if (result != MQTT_STATUS_OK) { 47 | LOG("Could not subscribe test 2"); 48 | exit(1); 49 | } 50 | } 51 | 52 | int main(int argc, char **argv) { 53 | MQTTConfig config = { 0 }; 54 | 55 | config.client_id = "libmqtt_testsuite"; 56 | config.hostname = "localhost"; 57 | config.clean_session = true; 58 | 59 | LOG("Trying to connect to %s...", config.hostname); 60 | MQTTHandle *mqtt = mqtt_connect(&config, mqtt_connected, NULL, err_handler); 61 | 62 | if (mqtt == NULL) { 63 | LOG("Connection failed!"); 64 | return 1; 65 | } 66 | 67 | while (!leave) { 68 | LOG("Waiting..."); 69 | platform_sleep(1000); 70 | } 71 | 72 | LOG("Disconnecting..."); 73 | mqtt_disconnect(mqtt, NULL, NULL); 74 | } 75 | -------------------------------------------------------------------------------- /tests/connect_subscribe_qos1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "platform.h" 5 | #include "mqtt.h" 6 | 7 | bool leave = false; 8 | 9 | #define LOG(fmt, ...) fprintf(stdout, fmt "\n", ## __VA_ARGS__) 10 | 11 | bool err_handler(MQTTHandle *handle, MQTTConfig *config, MQTTErrorCode error) { 12 | LOG("Error received: %d", error); 13 | 14 | return 1; 15 | } 16 | 17 | void callback(MQTTHandle *handle, char *topic, char *payload) { 18 | LOG("Received publish: %s -> %s", topic, payload); 19 | 20 | MQTTStatus result = mqtt_unsubscribe(handle, "testsuite/mqtt/test_qos1"); 21 | if (result != MQTT_STATUS_OK) { 22 | LOG("Could not unsubscribe test"); 23 | exit(1); 24 | } 25 | 26 | leave = true; 27 | } 28 | 29 | void mqtt_connected(MQTTHandle *handle, void *context) { 30 | LOG("Connected!"); 31 | 32 | LOG("Trying subscribe on testsuite/mqtt/test_qos1..."); 33 | MQTTStatus result = mqtt_subscribe(handle, "testsuite/mqtt/test_qos1", MQTT_QOS_1, callback); 34 | if (result != MQTT_STATUS_OK) { 35 | LOG("Could not subscribe test"); 36 | exit(1); 37 | } 38 | } 39 | 40 | int main(int argc, char **argv) { 41 | MQTTConfig config = { 0 }; 42 | 43 | config.client_id = "libmqtt_testsuite"; 44 | config.hostname = "localhost"; 45 | config.clean_session = true; 46 | 47 | LOG("Trying to connect to %s...", config.hostname); 48 | MQTTHandle *mqtt = mqtt_connect(&config, mqtt_connected, NULL, err_handler); 49 | 50 | if (mqtt == NULL) { 51 | LOG("Connection failed!"); 52 | return 1; 53 | } 54 | 55 | while (!leave) { 56 | LOG("Waiting..."); 57 | platform_sleep(1000); 58 | } 59 | 60 | LOG("Disconnecting..."); 61 | mqtt_disconnect(mqtt, NULL, NULL); 62 | } 63 | -------------------------------------------------------------------------------- /tests/connect_subscribe_qos2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "platform.h" 5 | #include "mqtt.h" 6 | 7 | bool leave = false; 8 | 9 | #define LOG(fmt, ...) fprintf(stdout, fmt "\n", ## __VA_ARGS__) 10 | 11 | bool err_handler(MQTTHandle *handle, MQTTConfig *config, MQTTErrorCode error) { 12 | LOG("Error received: %d", error); 13 | 14 | return 1; 15 | } 16 | 17 | void callback(MQTTHandle *handle, char *topic, char *payload) { 18 | LOG("Received publish: %s -> %s", topic, payload); 19 | 20 | MQTTStatus result = mqtt_unsubscribe(handle, "testsuite/mqtt/test_qos2"); 21 | if (result != MQTT_STATUS_OK) { 22 | LOG("Could not unsubscribe test"); 23 | exit(1); 24 | } 25 | 26 | leave = true; 27 | } 28 | 29 | void mqtt_connected(MQTTHandle *handle, void *context) { 30 | LOG("Connected!"); 31 | 32 | LOG("Trying subscribe on testsuite/mqtt/test_qos2..."); 33 | MQTTStatus result = mqtt_subscribe(handle, "testsuite/mqtt/test_qos2", MQTT_QOS_2, callback); 34 | if (result != MQTT_STATUS_OK) { 35 | LOG("Could not subscribe test"); 36 | exit(1); 37 | } 38 | } 39 | 40 | int main(int argc, char **argv) { 41 | MQTTConfig config = { 0 }; 42 | 43 | config.client_id = "libmqtt_testsuite"; 44 | config.hostname = "localhost"; 45 | config.clean_session = true; 46 | 47 | LOG("Trying to connect to %s...", config.hostname); 48 | MQTTHandle *mqtt = mqtt_connect(&config, mqtt_connected, NULL, err_handler); 49 | 50 | if (mqtt == NULL) { 51 | LOG("Connection failed!"); 52 | return 1; 53 | } 54 | 55 | while (!leave) { 56 | LOG("Waiting..."); 57 | platform_sleep(1000); 58 | } 59 | 60 | LOG("Disconnecting..."); 61 | mqtt_disconnect(mqtt, NULL, NULL); 62 | } 63 | -------------------------------------------------------------------------------- /tests/decode_packet.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "packet.h" 3 | 4 | extern uint16_t variable_length_int_decode(Buffer *buffer); 5 | extern char *utf8_string_decode(Buffer *buffer); 6 | 7 | // Variable length int data check 8 | 9 | TestResult test_vl_int_data_0(void) { 10 | char data[] = { 0 }; 11 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 12 | uint16_t result = variable_length_int_decode(buffer); 13 | TESTASSERT(result == 0, "Should decode to 0"); 14 | buffer_release(buffer); 15 | TEST_OK(); 16 | } 17 | 18 | TestResult test_vl_int_data_127(void) { 19 | char data[] = { 127 }; 20 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 21 | uint16_t result = variable_length_int_decode(buffer); 22 | TESTASSERT(result == 127, "Should decode to 127"); 23 | buffer_release(buffer); 24 | TEST_OK(); 25 | } 26 | 27 | TestResult test_vl_int_data_128(void) { 28 | char data[] = { 0x80, 0x01 }; 29 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 30 | uint16_t result = variable_length_int_decode(buffer); 31 | TESTASSERT(result == 128, "Should decode to 128"); 32 | buffer_release(buffer); 33 | TEST_OK(); 34 | } 35 | 36 | TestResult test_vl_int_data_16383(void) { 37 | char data[] = { 0xff, 0x7f }; 38 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 39 | uint16_t result = variable_length_int_decode(buffer); 40 | TESTASSERT(result == 16383, "Should decode to 16383"); 41 | buffer_release(buffer); 42 | TEST_OK(); 43 | } 44 | 45 | TestResult test_vl_int_data_16384(void) { 46 | char data[] = { 0x80, 0x80, 0x01 }; 47 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 48 | uint16_t result = variable_length_int_decode(buffer); 49 | TESTASSERT(result == 16384, "Should decode to 16384"); 50 | buffer_release(buffer); 51 | TEST_OK(); 52 | } 53 | 54 | TestResult test_vl_int_data_32767(void) { 55 | char data[] = { 0xff, 0xff, 0x01 }; 56 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 57 | uint16_t result = variable_length_int_decode(buffer); 58 | TESTASSERT(result == 32767, "Should decode to 32767"); 59 | buffer_release(buffer); 60 | TEST_OK(); 61 | } 62 | 63 | // UTF-8 String decoding 64 | 65 | TestResult test_utf8_string_empty(void) { 66 | char data[] = { 0x00, 0x00 }; 67 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 68 | char *string = utf8_string_decode(buffer); 69 | 70 | TESTASSERT(strlen(string) == 0, "Should decode empty string"); 71 | free(string); 72 | buffer_release(buffer); 73 | TEST_OK(); 74 | } 75 | 76 | 77 | TestResult test_utf8_string_hello(void) { 78 | char data[] = { 0x00, 0x05, 'h', 'e', 'l', 'l', 'o' }; 79 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 80 | char *string = utf8_string_decode(buffer); 81 | 82 | TESTASSERT(strncmp("hello", string, 5) == 0, "Should decode to 'hello' string"); 83 | free(string); 84 | buffer_release(buffer); 85 | TEST_OK(); 86 | } 87 | 88 | // packet decoder 89 | 90 | TestResult test_decode_connect_simple(void) { 91 | char data[] = { 92 | 0x10, 0x10, // header 93 | 0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0x02, 0x00, 0x0a, // var header 94 | 0x00, 0x04, 't', 'e', 's', 't' // client id 95 | }; 96 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 97 | MQTTPacket *packet = mqtt_packet_decode(buffer); 98 | 99 | TESTASSERT(packet != NULL, "Packet should be valid"); 100 | TESTASSERT(packet->packet_type == PacketTypeConnect, "Should be connect packet"); 101 | 102 | TESTASSERT(strcmp("CONNECT", get_packet_name(packet)) == 0, "Packet name"); 103 | 104 | ConnectPayload *payload = (ConnectPayload *)packet->payload; 105 | TESTASSERT(strncmp("test", payload->client_id, 4) == 0, "Client id should be 'test'"); 106 | TESTASSERT(payload->protocol_level == 4, "Protocol level should be 4"); 107 | TESTASSERT(payload->keepalive_interval == 10, "Keepalive should be 10"); 108 | TESTASSERT(payload->clean_session == 1, "Clean session should be 1"); 109 | 110 | free_MQTTPacket(packet); 111 | buffer_release(buffer); 112 | 113 | TEST_OK(); 114 | } 115 | 116 | TestResult test_decode_connect_invalid(void) { 117 | char data[] = { 118 | 0x10, 0x10, // header 119 | 0x00, 0x04, 'M', 'Q', 'T', 'X', 0x04, 0x02, 0x00, 0x0a, // var header 120 | 0x00, 0x04, 't', 'e', 's', 't' // client id 121 | }; 122 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 123 | MQTTPacket *packet = mqtt_packet_decode(buffer); 124 | 125 | TESTASSERT(packet == NULL, "Packet should not be valid"); 126 | buffer_release(buffer); 127 | TEST_OK(); 128 | } 129 | 130 | TestResult test_decode_connect_will(void) { 131 | char data[] = { 132 | 0x10, 0x2d, // header 133 | 0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0x2e, 0x00, 0x0a, // var header 134 | 0x00, 0x04, 't', 'e', 's', 't', // client id 135 | 0x00, 0x0d, 't', 'e', 's', 't', '/', 'l', 'a', 's', 't', 'w', 'i', 'l', 'l', 136 | 0x00, 0x0c, 'd', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', 'e', 'd', 137 | 138 | }; 139 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 140 | MQTTPacket *packet = mqtt_packet_decode(buffer); 141 | 142 | TESTASSERT(packet != NULL, "Packet should be valid"); 143 | TESTASSERT(packet->packet_type == PacketTypeConnect, "Should be connect packet"); 144 | 145 | ConnectPayload *payload = (ConnectPayload *)packet->payload; 146 | TESTASSERT(strncmp("test", payload->client_id, 4) == 0, "Client id should be 'test'"); 147 | TESTASSERT(payload->protocol_level == 4, "Protocol level should be 4"); 148 | TESTASSERT(payload->keepalive_interval == 10, "Keepalive should be 10"); 149 | TESTASSERT(payload->clean_session == 1, "Clean session should be 1"); 150 | 151 | TESTASSERT(strncmp("test/lastwill", payload->will_topic, 14) == 0, "Last will topic should be 'test/lastwill'"); 152 | TESTASSERT(strncmp("disconnected", payload->will_message, 13) == 0, "Last will message should be 'disconnected'"); 153 | TESTASSERT(payload->will_qos == MQTT_QOS_1, "Last will QoS should be 1"); 154 | TESTASSERT(payload->retain_will == true, "Last will retain flag should be true"); 155 | 156 | free_MQTTPacket(packet); 157 | buffer_release(buffer); 158 | 159 | TEST_OK(); 160 | } 161 | 162 | TestResult test_decode_connect_auth(void) { 163 | char data[] = { 164 | 0x10, 0x39, // header 165 | 0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0xee, 0x00, 0x0a, // var header 166 | 0x00, 0x04, 't', 'e', 's', 't', // client id 167 | 0x00, 0x0d, 't', 'e', 's', 't', '/', 'l', 'a', 's', 't', 'w', 'i', 'l', 'l', 168 | 0x00, 0x0c, 'd', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', 'e', 'd', 169 | 0x00, 0x04, 'a', 'n', 'o', 'n', // username 170 | 0x00, 0x04, 't', 'e', 's', 't' // password 171 | }; 172 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 173 | MQTTPacket *packet = mqtt_packet_decode(buffer); 174 | 175 | TESTASSERT(packet != NULL, "Packet should be valid"); 176 | TESTASSERT(packet->packet_type == PacketTypeConnect, "Should be connect packet"); 177 | 178 | ConnectPayload *payload = (ConnectPayload *)packet->payload; 179 | TESTASSERT(strncmp("test", payload->client_id, 4) == 0, "Client id should be 'test'"); 180 | TESTASSERT(payload->protocol_level == 4, "Protocol level should be 4"); 181 | TESTASSERT(payload->keepalive_interval == 10, "Keepalive should be 10"); 182 | TESTASSERT(payload->clean_session == 1, "Clean session should be 1"); 183 | 184 | TESTASSERT(strncmp("test/lastwill", payload->will_topic, 14) == 0, "Last will topic should be 'test/lastwill'"); 185 | TESTASSERT(strncmp("disconnected", payload->will_message, 13) == 0, "Last will message should be 'disconnected'"); 186 | TESTASSERT(payload->will_qos == MQTT_QOS_1, "Last will QoS should be 1"); 187 | TESTASSERT(payload->retain_will == true, "Last will retain flag should be true"); 188 | 189 | TESTASSERT(strncmp("anon", payload->username, 4) == 0, "Username should be 'anon'"); 190 | TESTASSERT(strncmp("test", payload->password, 4) == 0, "Password should be 'test'"); 191 | 192 | free_MQTTPacket(packet); 193 | buffer_release(buffer); 194 | 195 | TEST_OK(); 196 | } 197 | 198 | TestResult test_decode_connack(void) { 199 | char data[] = { 200 | 0x20, 0x02, // header 201 | 0x01, // session present 202 | 0x00 // accepted 203 | }; 204 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 205 | MQTTPacket *packet = mqtt_packet_decode(buffer); 206 | 207 | TESTASSERT(packet != NULL, "Packet should be valid"); 208 | TESTASSERT(packet->packet_type == PacketTypeConnAck, "Should be connack packet"); 209 | 210 | TESTASSERT(strcmp("CONNACK", get_packet_name(packet)) == 0, "Packet name"); 211 | 212 | ConnAckPayload *payload = (ConnAckPayload *)packet->payload; 213 | TESTASSERT(payload->session_present == true, "Session should be present"); 214 | TESTASSERT(payload->status == ConnAckStatusAccepted, "Connection status should be accepted"); 215 | 216 | free_MQTTPacket(packet); 217 | buffer_release(buffer); 218 | 219 | TEST_OK(); 220 | } 221 | 222 | TestResult test_decode_publish_no_msg(void) { 223 | char data[] = { 224 | 0x33, 0x0e, // header, qos1, retain 225 | 0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c', 226 | 0x00, 0x0a // packet id 227 | }; 228 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 229 | MQTTPacket *packet = mqtt_packet_decode(buffer); 230 | 231 | TESTASSERT(packet != NULL, "Packet should be valid"); 232 | TESTASSERT(packet->packet_type == PacketTypePublish, "Should be publish packet"); 233 | 234 | TESTASSERT(strcmp("PUBLISH", get_packet_name(packet)) == 0, "Packet name"); 235 | 236 | PublishPayload *payload = (PublishPayload *)packet->payload; 237 | TESTASSERT(payload->qos == MQTT_QOS_1, "QoS should be 1"); 238 | TESTASSERT(payload->retain == true, "Retain should be true"); 239 | TESTASSERT(payload->packet_id == 10, "Packet ID should be 10"); 240 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 241 | TESTASSERT(strncmp("test/topic", payload->topic, 11) == 0, "Topic should match"); 242 | TESTASSERT(payload->message == NULL, "Message should be NULL"); 243 | 244 | free_MQTTPacket(packet); 245 | buffer_release(buffer); 246 | 247 | TEST_OK(); 248 | } 249 | 250 | TestResult test_decode_publish_with_msg(void) { 251 | char data[] = { 252 | 0x33, 0x15, // header, qos1, retain 253 | 0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c', 254 | 0x00, 0x0a, // packet id 255 | 'p', 'a', 'y', 'l', 'o', 'a', 'd' 256 | }; 257 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 258 | MQTTPacket *packet = mqtt_packet_decode(buffer); 259 | 260 | TESTASSERT(packet != NULL, "Packet should be valid"); 261 | TESTASSERT(packet->packet_type == PacketTypePublish, "Should be publish packet"); 262 | 263 | PublishPayload *payload = (PublishPayload *)packet->payload; 264 | TESTASSERT(payload->qos == MQTT_QOS_1, "QoS should be 1"); 265 | TESTASSERT(payload->retain == true, "Retain should be true"); 266 | TESTASSERT(payload->packet_id == 10, "Packet ID should be 10"); 267 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 268 | TESTASSERT(strncmp("test/topic", payload->topic, 11) == 0, "Topic should match"); 269 | TESTASSERT(strncmp("payload", payload->message, 8) == 0, "Message should be 'payload'"); 270 | 271 | free_MQTTPacket(packet); 272 | buffer_release(buffer); 273 | 274 | TEST_OK(); 275 | } 276 | 277 | TestResult test_decode_puback(void) { 278 | char data[] = { 279 | 0x40, 0x02, // header 280 | 0x00, 0x0a // packet id 281 | }; 282 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 283 | MQTTPacket *packet = mqtt_packet_decode(buffer); 284 | 285 | TESTASSERT(packet != NULL, "Packet should be valid"); 286 | TESTASSERT(packet->packet_type == PacketTypePubAck, "Should be puback packet"); 287 | 288 | TESTASSERT(strcmp("PUBACK", get_packet_name(packet)) == 0, "Packet name"); 289 | 290 | PubAckPayload *payload = (PubAckPayload *)packet->payload; 291 | TESTASSERT(payload->packet_id == 10, "Packet id should be 10"); 292 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 293 | 294 | free_MQTTPacket(packet); 295 | buffer_release(buffer); 296 | 297 | TEST_OK(); 298 | } 299 | 300 | TestResult test_decode_pubrec(void) { 301 | char data[] = { 302 | 0x50, 0x02, // header 303 | 0x00, 0x0a // packet id 304 | }; 305 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 306 | MQTTPacket *packet = mqtt_packet_decode(buffer); 307 | 308 | TESTASSERT(packet != NULL, "Packet should be valid"); 309 | TESTASSERT(packet->packet_type == PacketTypePubRec, "Should be pubrec packet"); 310 | 311 | TESTASSERT(strcmp("PUBREC", get_packet_name(packet)) == 0, "Packet name"); 312 | 313 | PubRecPayload *payload = (PubRecPayload *)packet->payload; 314 | TESTASSERT(payload->packet_id == 10, "Packet id should be 10"); 315 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 316 | 317 | free_MQTTPacket(packet); 318 | buffer_release(buffer); 319 | 320 | TEST_OK(); 321 | } 322 | 323 | TestResult test_decode_pubrel(void) { 324 | char data[] = { 325 | 0x62, 0x02, // header 326 | 0x00, 0x0a // packet id 327 | }; 328 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 329 | MQTTPacket *packet = mqtt_packet_decode(buffer); 330 | 331 | TESTASSERT(packet != NULL, "Packet should be valid"); 332 | TESTASSERT(packet->packet_type == PacketTypePubRel, "Should be pubrel packet"); 333 | 334 | TESTASSERT(strcmp("PUBREL", get_packet_name(packet)) == 0, "Packet name"); 335 | 336 | PubRelPayload *payload = (PubRelPayload *)packet->payload; 337 | TESTASSERT(payload->packet_id == 10, "Packet id should be 10"); 338 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 339 | 340 | free_MQTTPacket(packet); 341 | buffer_release(buffer); 342 | 343 | TEST_OK(); 344 | } 345 | 346 | TestResult test_decode_pubcomp(void) { 347 | char data[] = { 348 | 0x70, 0x02, // header 349 | 0x00, 0x0a // packet id 350 | }; 351 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 352 | MQTTPacket *packet = mqtt_packet_decode(buffer); 353 | 354 | TESTASSERT(packet != NULL, "Packet should be valid"); 355 | TESTASSERT(packet->packet_type == PacketTypePubComp, "Should be pubcomp packet"); 356 | 357 | TESTASSERT(strcmp("PUBCOMP", get_packet_name(packet)) == 0, "Packet name"); 358 | 359 | PubCompPayload *payload = (PubCompPayload *)packet->payload; 360 | TESTASSERT(payload->packet_id == 10, "Packet id should be 10"); 361 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 362 | 363 | free_MQTTPacket(packet); 364 | buffer_release(buffer); 365 | 366 | TEST_OK(); 367 | } 368 | 369 | TestResult test_decode_subscribe(void) { 370 | char data[] = { 371 | 0x82, 0x0f, // header 372 | 0x00, 0x0a, // packet id 373 | 0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c', 374 | 0x01 // qos 375 | }; 376 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 377 | MQTTPacket *packet = mqtt_packet_decode(buffer); 378 | 379 | TESTASSERT(packet != NULL, "Packet should be valid"); 380 | TESTASSERT(packet->packet_type == PacketTypeSubscribe, "Should be subscribe packet"); 381 | 382 | TESTASSERT(strcmp("SUBSCRIBE", get_packet_name(packet)) == 0, "Packet name"); 383 | 384 | SubscribePayload *payload = (SubscribePayload *)packet->payload; 385 | TESTASSERT(payload->qos == MQTT_QOS_1, "QoS should be 1"); 386 | TESTASSERT(payload->packet_id == 10, "Packet ID should be 10"); 387 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 388 | TESTASSERT(strncmp("test/topic", payload->topic, 11) == 0, "Topic should match"); 389 | 390 | free_MQTTPacket(packet); 391 | buffer_release(buffer); 392 | 393 | TEST_OK(); 394 | } 395 | 396 | TestResult test_decode_suback(void) { 397 | char data[] = { 398 | 0x90, 0x03, // header 399 | 0x00, 0x0a, // packet id, 400 | 0x02 // status 401 | }; 402 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 403 | MQTTPacket *packet = mqtt_packet_decode(buffer); 404 | 405 | TESTASSERT(packet != NULL, "Packet should be valid"); 406 | TESTASSERT(packet->packet_type == PacketTypeSubAck, "Should be suback packet"); 407 | 408 | TESTASSERT(strcmp("SUBACK", get_packet_name(packet)) == 0, "Packet name"); 409 | 410 | SubAckPayload *payload = (SubAckPayload *)packet->payload; 411 | TESTASSERT(payload->packet_id == 10, "Packet ID should be 10"); 412 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 413 | TESTASSERT(payload->status == SubAckStatusQoS2, "Status should be QoS 2 ack"); 414 | 415 | free_MQTTPacket(packet); 416 | buffer_release(buffer); 417 | 418 | TEST_OK(); 419 | } 420 | 421 | TestResult test_decode_unsubscribe(void) { 422 | char data[] = { 423 | 0xa2, 0x0e, // header 424 | 0x00, 0x0a, // packet id 425 | 0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c', 426 | }; 427 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 428 | MQTTPacket *packet = mqtt_packet_decode(buffer); 429 | 430 | TESTASSERT(packet != NULL, "Packet should be valid"); 431 | TESTASSERT(packet->packet_type == PacketTypeUnsubscribe, "Should be unsubscribe packet"); 432 | 433 | TESTASSERT(strcmp("UNSUBSCRIBE", get_packet_name(packet)) == 0, "Packet name"); 434 | 435 | UnsubscribePayload *payload = (UnsubscribePayload *)packet->payload; 436 | TESTASSERT(payload->packet_id == 10, "Packet ID should be 10"); 437 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 438 | TESTASSERT(strncmp("test/topic", payload->topic, 11) == 0, "Topic should match"); 439 | 440 | free_MQTTPacket(packet); 441 | buffer_release(buffer); 442 | 443 | TEST_OK(); 444 | } 445 | 446 | TestResult test_decode_unsuback(void) { 447 | char data[] = { 448 | 0xb0, 0x02, // header 449 | 0x00, 0x0a // packet id, 450 | }; 451 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 452 | MQTTPacket *packet = mqtt_packet_decode(buffer); 453 | 454 | TESTASSERT(packet != NULL, "Packet should be valid"); 455 | TESTASSERT(packet->packet_type == PacketTypeUnsubAck, "Should be unsuback packet"); 456 | 457 | TESTASSERT(strcmp("UNSUBACK", get_packet_name(packet)) == 0, "Packet name"); 458 | 459 | UnsubAckPayload *payload = (UnsubAckPayload *)packet->payload; 460 | TESTASSERT(payload->packet_id == 10, "Packet ID should be 10"); 461 | TESTASSERT(get_packet_id(packet) == 10, "Packet ID"); 462 | 463 | free_MQTTPacket(packet); 464 | buffer_release(buffer); 465 | 466 | TEST_OK(); 467 | } 468 | 469 | TestResult test_decode_pingreq(void) { 470 | char data[] = { 471 | 0xc0, 0x00 // header 472 | }; 473 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 474 | MQTTPacket *packet = mqtt_packet_decode(buffer); 475 | 476 | TESTASSERT(packet != NULL, "Packet should be valid"); 477 | TESTASSERT(packet->packet_type == PacketTypePingReq, "Should be pingreq packet"); 478 | 479 | TESTASSERT(strcmp("PINGREQ", get_packet_name(packet)) == 0, "Packet name"); 480 | 481 | free_MQTTPacket(packet); 482 | buffer_release(buffer); 483 | 484 | TEST_OK(); 485 | } 486 | 487 | TestResult test_decode_pingresp(void) { 488 | char data[] = { 489 | 0xd0, 0x00 // header 490 | }; 491 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 492 | MQTTPacket *packet = mqtt_packet_decode(buffer); 493 | 494 | TESTASSERT(packet != NULL, "Packet should be valid"); 495 | TESTASSERT(packet->packet_type == PacketTypePingResp, "Should be pingresp packet"); 496 | 497 | TESTASSERT(strcmp("PINGRESP", get_packet_name(packet)) == 0, "Packet name"); 498 | 499 | free_MQTTPacket(packet); 500 | buffer_release(buffer); 501 | 502 | TEST_OK(); 503 | } 504 | 505 | TestResult test_decode_disconnect(void) { 506 | char data[] = { 507 | 0xe0, 0x00 // header 508 | }; 509 | Buffer *buffer = buffer_from_data_copy(data, sizeof(data)); 510 | MQTTPacket *packet = mqtt_packet_decode(buffer); 511 | 512 | TESTASSERT(packet != NULL, "Packet should be valid"); 513 | TESTASSERT(packet->packet_type == PacketTypeDisconnect, "Should be disconnect packet"); 514 | 515 | TESTASSERT(strcmp("DISCONNECT", get_packet_name(packet)) == 0, "Packet name"); 516 | 517 | free_MQTTPacket(packet); 518 | buffer_release(buffer); 519 | 520 | TEST_OK(); 521 | } 522 | 523 | TESTS( 524 | TEST("Variable length int decode for 0", test_vl_int_data_0), 525 | TEST("Variable length int decode for 127", test_vl_int_data_127), 526 | TEST("Variable length int decode for 128", test_vl_int_data_128), 527 | TEST("Variable length int decode for 16383", test_vl_int_data_16383), 528 | TEST("Variable length int decode for 16384", test_vl_int_data_16384), 529 | TEST("Variable length int decode for 32767", test_vl_int_data_32767), 530 | TEST("UTF-8 string decode empty string", test_utf8_string_empty), 531 | TEST("UTF-8 string decode \"hello\"", test_utf8_string_hello), 532 | TEST("Decode Connect simple", test_decode_connect_simple), 533 | TEST("Decode Connect invalid", test_decode_connect_invalid), 534 | TEST("Decode Connect with will", test_decode_connect_will), 535 | TEST("Decode Connect with auth", test_decode_connect_auth), 536 | TEST("Decode ConnAck", test_decode_connack), 537 | TEST("Decode Publish with no message", test_decode_publish_no_msg), 538 | TEST("Decode Publish with message", test_decode_publish_with_msg), 539 | TEST("Decode PubAck", test_decode_puback), 540 | TEST("Decode PubRec", test_decode_pubrec), 541 | TEST("Decode PubRel", test_decode_pubrel), 542 | TEST("Decode PubComp", test_decode_pubcomp), 543 | TEST("Decode Subscribe", test_decode_subscribe), 544 | TEST("Decode SubAck", test_decode_suback), 545 | TEST("Decode Unsubscribe", test_decode_unsubscribe), 546 | TEST("Decode UnsubAck", test_decode_unsuback), 547 | TEST("Decode PingReq", test_decode_pingreq), 548 | TEST("Decode PingResp", test_decode_pingresp), 549 | TEST("Decode Disconnect", test_decode_disconnect) 550 | ); 551 | -------------------------------------------------------------------------------- /tests/encode_packet.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "packet.h" 3 | 4 | extern size_t variable_length_int_encode(uint16_t value, Buffer *buffer); 5 | extern size_t variable_length_int_size(uint16_t value); 6 | extern size_t utf8_string_encode(char *string, Buffer *buffer); 7 | extern Buffer *make_buffer_for_header(size_t sz, MQTTControlPacketType type); 8 | 9 | // Variable length int length calculation 10 | TestResult test_vl_int_0(void) { 11 | TESTASSERT(variable_length_int_size(0) == 1, "Values < 128 should fit in one byte"); 12 | TEST_OK(); 13 | } 14 | 15 | TestResult test_vl_int_127(void) { 16 | TESTASSERT(variable_length_int_size(127) == 1, "Values < 128 should fit in one byte"); 17 | TEST_OK(); 18 | } 19 | 20 | TestResult test_vl_int_128(void) { 21 | TESTASSERT(variable_length_int_size(128) == 2, "Values < 16384 should fit in two bytes"); 22 | TEST_OK(); 23 | } 24 | 25 | TestResult test_vl_int_16383(void) { 26 | TESTASSERT(variable_length_int_size(16383) == 2, "Values < 16384 should fit in two bytes"); 27 | TEST_OK(); 28 | } 29 | 30 | TestResult test_vl_int_16384(void) { 31 | TESTASSERT(variable_length_int_size(16384) == 3, "Values < 2097151 should fit in three bytes"); 32 | TEST_OK(); 33 | } 34 | 35 | // Variable length int data check 36 | 37 | TestResult test_vl_int_data_0(void) { 38 | char data[] = { 0 }; 39 | Buffer *buffer = buffer_allocate(sizeof(data)); 40 | variable_length_int_encode(0, buffer); 41 | 42 | return TESTMEMCMP( 43 | buffer_from_data_copy(data, sizeof(data)), 44 | buffer 45 | ); 46 | } 47 | 48 | TestResult test_vl_int_data_127(void) { 49 | char data[] = { 127 }; 50 | Buffer *buffer = buffer_allocate(sizeof(data)); 51 | variable_length_int_encode(127, buffer); 52 | 53 | return TESTMEMCMP( 54 | buffer_from_data_copy(data, sizeof(data)), 55 | buffer 56 | ); 57 | } 58 | 59 | TestResult test_vl_int_data_128(void) { 60 | char data[] = { 0x80, 0x01 }; 61 | Buffer *buffer = buffer_allocate(sizeof(data)); 62 | variable_length_int_encode(128, buffer); 63 | 64 | return TESTMEMCMP( 65 | buffer_from_data_copy(data, sizeof(data)), 66 | buffer 67 | ); 68 | } 69 | 70 | TestResult test_vl_int_data_16383(void) { 71 | char data[] = { 0xff, 0x7f }; 72 | Buffer *buffer = buffer_allocate(sizeof(data)); 73 | variable_length_int_encode(16383, buffer); 74 | 75 | return TESTMEMCMP( 76 | buffer_from_data_copy(data, sizeof(data)), 77 | buffer 78 | ); 79 | } 80 | 81 | TestResult test_vl_int_data_16384(void) { 82 | char data[] = { 0x80, 0x80, 0x01 }; 83 | Buffer *buffer = buffer_allocate(sizeof(data)); 84 | variable_length_int_encode(16384, buffer); 85 | 86 | return TESTMEMCMP( 87 | buffer_from_data_copy(data, sizeof(data)), 88 | buffer 89 | ); 90 | } 91 | 92 | TestResult test_vl_int_data_32767(void) { 93 | char data[] = { 0xff, 0xff, 0x01 }; 94 | Buffer *buffer = buffer_allocate(sizeof(data)); 95 | variable_length_int_encode(32767, buffer); 96 | 97 | return TESTMEMCMP( 98 | buffer_from_data_copy(data, sizeof(data)), 99 | buffer 100 | ); 101 | } 102 | 103 | // UTF-8 String encoding 104 | 105 | TestResult test_utf8_string_null(void) { 106 | char data[] = { 0x00, 0x00 }; 107 | Buffer *buffer = buffer_allocate(sizeof(data)); 108 | (void)utf8_string_encode(NULL, buffer); 109 | 110 | return TESTMEMCMP( 111 | buffer_from_data_copy(data, sizeof(data)), 112 | buffer 113 | ); 114 | } 115 | 116 | TestResult test_utf8_string_empty(void) { 117 | char data[] = { 0x00, 0x00 }; 118 | Buffer *buffer = buffer_allocate(sizeof(data)); 119 | (void)utf8_string_encode("", buffer); 120 | 121 | return TESTMEMCMP( 122 | buffer_from_data_copy(data, sizeof(data)), 123 | buffer 124 | ); 125 | } 126 | 127 | TestResult test_utf8_string_hello(void) { 128 | char data[] = { 0x00, 0x05, 'h', 'e', 'l', 'l', 'o' }; 129 | Buffer *buffer = buffer_allocate(sizeof(data)); 130 | (void)utf8_string_encode("hello", buffer); 131 | 132 | return TESTMEMCMP( 133 | buffer_from_data_copy(data, sizeof(data)), 134 | buffer 135 | ); 136 | } 137 | 138 | // make header 139 | TestResult test_make_header(void) { 140 | char data[] = { 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 }; 141 | Buffer *buffer = make_buffer_for_header(5, PacketTypeConnect); 142 | 143 | return TESTMEMCMP( 144 | buffer_from_data_copy(data, sizeof(data)), 145 | buffer 146 | ); 147 | } 148 | 149 | TestResult test_encode_connect_simple(void) { 150 | char data[] = { 151 | 0x10, 0x10, // header 152 | 0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0x02, 0x00, 0x0a, // var header 153 | 0x00, 0x04, 't', 'e', 's', 't' // client id 154 | }; 155 | ConnectPayload *payload = (ConnectPayload *)calloc(1, sizeof(ConnectPayload)); 156 | 157 | payload->client_id = "test"; 158 | payload->protocol_level = 4; 159 | payload->keepalive_interval = 10; 160 | payload->clean_session = 1; 161 | 162 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeConnect, payload }); 163 | free(payload); 164 | 165 | return TESTMEMCMP( 166 | buffer_from_data_copy(data, sizeof(data)), 167 | encoded 168 | ); 169 | } 170 | 171 | TestResult test_encode_connect_will(void) { 172 | char data[] = { 173 | 0x10, 0x2d, // header 174 | 0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0x2e, 0x00, 0x0a, // var header 175 | 0x00, 0x04, 't', 'e', 's', 't', // client id 176 | 0x00, 0x0d, 't', 'e', 's', 't', '/', 'l', 'a', 's', 't', 'w', 'i', 'l', 'l', 177 | 0x00, 0x0c, 'd', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', 'e', 'd', 178 | 179 | }; 180 | ConnectPayload *payload = (ConnectPayload *)calloc(1, sizeof(ConnectPayload)); 181 | 182 | payload->client_id = "test"; 183 | payload->protocol_level = 4; 184 | payload->keepalive_interval = 10; 185 | payload->clean_session = 1; 186 | 187 | payload->will_topic = "test/lastwill"; 188 | payload->will_message = "disconnected"; 189 | payload->will_qos = MQTT_QOS_1; 190 | payload->retain_will = true; 191 | 192 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeConnect, payload }); 193 | free(payload); 194 | 195 | return TESTMEMCMP( 196 | buffer_from_data_copy(data, sizeof(data)), 197 | encoded 198 | ); 199 | } 200 | 201 | TestResult test_encode_connect_auth(void) { 202 | char data[] = { 203 | 0x10, 0x39, // header 204 | 0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0xee, 0x00, 0x0a, // var header 205 | 0x00, 0x04, 't', 'e', 's', 't', // client id 206 | 0x00, 0x0d, 't', 'e', 's', 't', '/', 'l', 'a', 's', 't', 'w', 'i', 'l', 'l', 207 | 0x00, 0x0c, 'd', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', 'e', 'd', 208 | 0x00, 0x04, 'a', 'n', 'o', 'n', // username 209 | 0x00, 0x04, 't', 'e', 's', 't' // password 210 | }; 211 | ConnectPayload *payload = (ConnectPayload *)calloc(1, sizeof(ConnectPayload)); 212 | 213 | payload->client_id = "test"; 214 | payload->protocol_level = 4; 215 | payload->keepalive_interval = 10; 216 | payload->clean_session = 1; 217 | 218 | payload->will_topic = "test/lastwill"; 219 | payload->will_message = "disconnected"; 220 | payload->will_qos = MQTT_QOS_1; 221 | payload->retain_will = true; 222 | 223 | payload->username = "anon"; 224 | payload->password = "test"; 225 | 226 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeConnect, payload }); 227 | free(payload); 228 | 229 | return TESTMEMCMP( 230 | buffer_from_data_copy(data, sizeof(data)), 231 | encoded 232 | ); 233 | } 234 | 235 | TestResult test_encode_connack(void) { 236 | char data[] = { 237 | 0x20, 0x02, // header 238 | 0x01, // session present 239 | 0x00 // accepted 240 | }; 241 | ConnAckPayload *payload = (ConnAckPayload *)calloc(1, sizeof(ConnAckPayload)); 242 | 243 | payload->session_present = true; 244 | payload->status = ConnAckStatusAccepted; 245 | 246 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeConnAck, payload }); 247 | free(payload); 248 | 249 | return TESTMEMCMP( 250 | buffer_from_data_copy(data, sizeof(data)), 251 | encoded 252 | ); 253 | } 254 | 255 | TestResult test_encode_publish_no_msg(void) { 256 | char data[] = { 257 | 0x33, 0x0e, // header, qos1, retain 258 | 0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c', 259 | 0x00, 0x0a // packet id 260 | }; 261 | PublishPayload *payload = (PublishPayload *)calloc(1, sizeof(PublishPayload)); 262 | 263 | payload->qos = MQTT_QOS_1; 264 | payload->retain = true; 265 | payload->topic = "test/topic"; 266 | payload->packet_id = 10; 267 | 268 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePublish, payload }); 269 | free(payload); 270 | 271 | return TESTMEMCMP( 272 | buffer_from_data_copy(data, sizeof(data)), 273 | encoded 274 | ); 275 | } 276 | 277 | TestResult test_encode_publish_dup_qos0(void) { 278 | PublishPayload *payload = (PublishPayload *)calloc(1, sizeof(PublishPayload)); 279 | 280 | payload->qos = MQTT_QOS_0; 281 | payload->duplicate = true; 282 | payload->retain = true; 283 | payload->topic = "test/topic"; 284 | payload->packet_id = 10; 285 | 286 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePublish, payload }); 287 | free(payload); 288 | TESTASSERT(encoded == NULL, "DUP and QoS level 0 is an incompatible combination"); 289 | TEST_OK(); 290 | } 291 | 292 | TestResult test_encode_publish_with_msg(void) { 293 | char data[] = { 294 | 0x3b, 0x15, // header, qos1, retain 295 | 0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c', 296 | 0x00, 0x0a, // packet id 297 | 'p', 'a', 'y', 'l', 'o', 'a', 'd' 298 | }; 299 | PublishPayload *payload = (PublishPayload *)calloc(1, sizeof(PublishPayload)); 300 | 301 | payload->qos = MQTT_QOS_1; 302 | payload->retain = true; 303 | payload->duplicate = true; 304 | payload->topic = "test/topic"; 305 | payload->packet_id = 10; 306 | payload->message = "payload"; 307 | 308 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePublish, payload }); 309 | free(payload); 310 | 311 | return TESTMEMCMP( 312 | buffer_from_data_copy(data, sizeof(data)), 313 | encoded 314 | ); 315 | } 316 | 317 | TestResult test_encode_publish_with_msg_qos0(void) { 318 | char data[] = { 319 | 0x31, 0x13, // header, qos1, retain 320 | 0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c', 321 | 'p', 'a', 'y', 'l', 'o', 'a', 'd' 322 | }; 323 | PublishPayload *payload = (PublishPayload *)calloc(1, sizeof(PublishPayload)); 324 | 325 | payload->qos = MQTT_QOS_0; 326 | payload->retain = true; 327 | payload->duplicate = false; 328 | payload->topic = "test/topic"; 329 | payload->packet_id = 10; 330 | payload->message = "payload"; 331 | 332 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePublish, payload }); 333 | free(payload); 334 | 335 | return TESTMEMCMP( 336 | buffer_from_data_copy(data, sizeof(data)), 337 | encoded 338 | ); 339 | } 340 | 341 | TestResult test_encode_puback(void) { 342 | char data[] = { 343 | 0x40, 0x02, // header 344 | 0x00, 0x0a // packet id 345 | }; 346 | PubAckPayload *payload = (PubAckPayload *)calloc(1, sizeof(PubAckPayload)); 347 | 348 | payload->packet_id = 10; 349 | 350 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubAck, payload }); 351 | free(payload); 352 | 353 | return TESTMEMCMP( 354 | buffer_from_data_copy(data, sizeof(data)), 355 | encoded 356 | ); 357 | } 358 | 359 | TestResult test_encode_pubrec(void) { 360 | char data[] = { 361 | 0x50, 0x02, // header 362 | 0x00, 0x0a // packet id 363 | }; 364 | PubRecPayload *payload = (PubRecPayload *)calloc(1, sizeof(PubRecPayload)); 365 | 366 | payload->packet_id = 10; 367 | 368 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubRec, payload }); 369 | free(payload); 370 | 371 | return TESTMEMCMP( 372 | buffer_from_data_copy(data, sizeof(data)), 373 | encoded 374 | ); 375 | } 376 | 377 | TestResult test_encode_pubrel(void) { 378 | char data[] = { 379 | 0x62, 0x02, // header 380 | 0x00, 0x0a // packet id 381 | }; 382 | PubRelPayload *payload = (PubRelPayload *)calloc(1, sizeof(PubRelPayload)); 383 | 384 | payload->packet_id = 10; 385 | 386 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubRel, payload }); 387 | free(payload); 388 | 389 | return TESTMEMCMP( 390 | buffer_from_data_copy(data, sizeof(data)), 391 | encoded 392 | ); 393 | } 394 | 395 | TestResult test_encode_pubcomp(void) { 396 | char data[] = { 397 | 0x70, 0x02, // header 398 | 0x00, 0x0a // packet id 399 | }; 400 | PubCompPayload *payload = (PubCompPayload *)calloc(1, sizeof(PubCompPayload)); 401 | 402 | payload->packet_id = 10; 403 | 404 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubComp, payload }); 405 | free(payload); 406 | 407 | return TESTMEMCMP( 408 | buffer_from_data_copy(data, sizeof(data)), 409 | encoded 410 | ); 411 | } 412 | 413 | TestResult test_encode_subscribe(void) { 414 | char data[] = { 415 | 0x82, 0x0f, // header 416 | 0x00, 0x0a, // packet id 417 | 0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c', 418 | 0x01 // qos 419 | }; 420 | SubscribePayload *payload = (SubscribePayload *)calloc(1, sizeof(SubscribePayload)); 421 | 422 | payload->packet_id = 10; 423 | payload->topic = "test/topic"; 424 | payload->qos = MQTT_QOS_1; 425 | 426 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeSubscribe, payload }); 427 | free(payload); 428 | 429 | return TESTMEMCMP( 430 | buffer_from_data_copy(data, sizeof(data)), 431 | encoded 432 | ); 433 | } 434 | 435 | TestResult test_encode_suback(void) { 436 | char data[] = { 437 | 0x90, 0x03, // header 438 | 0x00, 0x0a, // packet id, 439 | 0x02 // status 440 | }; 441 | SubAckPayload *payload = (SubAckPayload *)calloc(1, sizeof(SubAckPayload)); 442 | 443 | payload->packet_id = 10; 444 | payload->status = SubAckStatusQoS2; 445 | 446 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeSubAck, payload }); 447 | free(payload); 448 | 449 | return TESTMEMCMP( 450 | buffer_from_data_copy(data, sizeof(data)), 451 | encoded 452 | ); 453 | } 454 | 455 | TestResult test_encode_unsubscribe(void) { 456 | char data[] = { 457 | 0xa2, 0x0e, // header 458 | 0x00, 0x0a, // packet id 459 | 0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c', 460 | }; 461 | UnsubscribePayload *payload = (UnsubscribePayload *)calloc(1, sizeof(UnsubscribePayload)); 462 | 463 | payload->packet_id = 10; 464 | payload->topic = "test/topic"; 465 | 466 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeUnsubscribe, payload }); 467 | free(payload); 468 | 469 | return TESTMEMCMP( 470 | buffer_from_data_copy(data, sizeof(data)), 471 | encoded 472 | ); 473 | } 474 | 475 | TestResult test_encode_unsuback(void) { 476 | char data[] = { 477 | 0xb0, 0x02, // header 478 | 0x00, 0x0a // packet id, 479 | }; 480 | UnsubAckPayload *payload = (UnsubAckPayload *)calloc(1, sizeof(UnsubAckPayload)); 481 | 482 | payload->packet_id = 10; 483 | 484 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeUnsubAck, payload }); 485 | free(payload); 486 | 487 | return TESTMEMCMP( 488 | buffer_from_data_copy(data, sizeof(data)), 489 | encoded 490 | ); 491 | } 492 | 493 | TestResult test_encode_pingreq(void) { 494 | char data[] = { 495 | 0xc0, 0x00 // header 496 | }; 497 | 498 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePingReq, NULL }); 499 | 500 | return TESTMEMCMP( 501 | buffer_from_data_copy(data, sizeof(data)), 502 | encoded 503 | ); 504 | } 505 | 506 | TestResult test_encode_pingresp(void) { 507 | char data[] = { 508 | 0xd0, 0x00 // header 509 | }; 510 | 511 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePingResp, NULL }); 512 | 513 | return TESTMEMCMP( 514 | buffer_from_data_copy(data, sizeof(data)), 515 | encoded 516 | ); 517 | } 518 | 519 | TestResult test_encode_disconnect(void) { 520 | char data[] = { 521 | 0xe0, 0x00 // header 522 | }; 523 | 524 | Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeDisconnect, NULL }); 525 | 526 | return TESTMEMCMP( 527 | buffer_from_data_copy(data, sizeof(data)), 528 | encoded 529 | ); 530 | } 531 | 532 | TESTS( 533 | TEST("Variable length int size for 0", test_vl_int_0), 534 | TEST("Variable length int size for 127", test_vl_int_127), 535 | TEST("Variable length int size for 128", test_vl_int_128), 536 | TEST("Variable length int size for 16383", test_vl_int_16383), 537 | TEST("Variable length int size for 16384", test_vl_int_16384), 538 | TEST("Variable length int data for 0", test_vl_int_data_0), 539 | TEST("Variable length int data for 127", test_vl_int_data_127), 540 | TEST("Variable length int data for 128", test_vl_int_data_128), 541 | TEST("Variable length int data for 16383", test_vl_int_data_16383), 542 | TEST("Variable length int data for 16384", test_vl_int_data_16384), 543 | TEST("Variable length int data for 32767", test_vl_int_data_32767), 544 | TEST("UTF-8 string encode NULL", test_utf8_string_null), 545 | TEST("UTF-8 string encode empty string", test_utf8_string_empty), 546 | TEST("UTF-8 string encode \"hello\"", test_utf8_string_hello), 547 | TEST("Make header", test_make_header), 548 | TEST("Encode Connect simple", test_encode_connect_simple), 549 | TEST("Encode Connect with will", test_encode_connect_will), 550 | TEST("Encode Connect with auth", test_encode_connect_auth), 551 | TEST("Encode ConnAck", test_encode_connack), 552 | TEST("Encode Publish with no message", test_encode_publish_no_msg), 553 | TEST("Encode Publish with invalid flags", test_encode_publish_dup_qos0), 554 | TEST("Encode Publish with message", test_encode_publish_with_msg), 555 | TEST("Encode Publish with message on QoS 0", test_encode_publish_with_msg_qos0), 556 | TEST("Encode PubAck", test_encode_puback), 557 | TEST("Encode PubRec", test_encode_pubrec), 558 | TEST("Encode PubRel", test_encode_pubrel), 559 | TEST("Encode PubComp", test_encode_pubcomp), 560 | TEST("Encode Subscribe", test_encode_subscribe), 561 | TEST("Encode SubAck", test_encode_suback), 562 | TEST("Encode Unsubscribe", test_encode_unsubscribe), 563 | TEST("Encode UnsubAck", test_encode_unsuback), 564 | TEST("Encode PingReq", test_encode_pingreq), 565 | TEST("Encode PingResp", test_encode_pingresp), 566 | TEST("Encode Disconnect", test_encode_disconnect) 567 | ); 568 | -------------------------------------------------------------------------------- /tests/test.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "buffer.h" 9 | 10 | typedef enum { 11 | TestStatusSkipped = 0, 12 | TestStatusFailure, 13 | TestStatusOk, 14 | TestStatusFailureHexdump 15 | } TestStatus; 16 | 17 | typedef struct { 18 | TestStatus status; 19 | char *message; 20 | Buffer *buffer; 21 | Buffer *valid; 22 | } TestResult; 23 | 24 | typedef TestResult (*TestPointer)(void); 25 | 26 | typedef struct { 27 | char *name; 28 | TestPointer run; 29 | char *file; 30 | int line; 31 | } DefinedTest; 32 | 33 | extern DefinedTest defined_tests[]; 34 | 35 | #define TEST(_name, _function) (DefinedTest){ _name, _function, __FILE__, __LINE__ } 36 | 37 | #define TESTS(...) \ 38 | DefinedTest defined_tests[] = { \ 39 | __VA_ARGS__, \ 40 | TEST(NULL, NULL) \ 41 | } 42 | 43 | #define TEST_OK() return (TestResult){ TestStatusOk, NULL, NULL, NULL } 44 | #define TESTRESULT(_status, _message) return (TestResult){ _status, _message, NULL, NULL } 45 | #define TESTRESULT_BUFFER(_status, _message, _buffer, _valid) return (TestResult){ _status, _message, _buffer, _valid } 46 | #define TESTASSERT(_assertion, _message) if (!(_assertion)) { return (TestResult){ TestStatusFailure, _message, NULL, NULL }; } 47 | 48 | 49 | static inline TestResult TESTMEMCMP(Buffer *valid, Buffer *check) { 50 | if (valid->len != check->len) { 51 | TESTRESULT_BUFFER(TestStatusFailureHexdump, "Buffer size differs from valid", check, valid); 52 | } 53 | if (memcmp(valid->data, check->data, valid->len) == 0) { 54 | buffer_release(check); 55 | buffer_release(valid); 56 | TESTRESULT(TestStatusOk, "Buffer matches valid"); 57 | } else { 58 | TESTRESULT_BUFFER(TestStatusFailureHexdump, "Buffer and valid differ", check, valid); 59 | } 60 | } 61 | 62 | // not implemented placeholder 63 | 64 | #if 0 65 | static TestResult not_implemented(void) { 66 | TESTRESULT(TestStatusSkipped, "Not implemented"); 67 | } 68 | #endif 69 | 70 | int main(int argc, char **argv) { 71 | uint16_t successes = 0; 72 | uint16_t skips = 0; 73 | uint16_t failures = 0; 74 | 75 | setvbuf(stdout, NULL, _IOLBF, 128); 76 | setvbuf(stderr, NULL, _IOLBF, 128); 77 | 78 | for(DefinedTest *test = defined_tests; test->run != NULL; test++) { 79 | TestResult result = test->run(); 80 | switch (result.status) { 81 | case TestStatusOk: 82 | successes++; 83 | fprintf(stdout, "info: Test %s suceeded\n", test->name); 84 | break; 85 | case TestStatusSkipped: 86 | skips++; 87 | fprintf(stderr, "%s:%d: warning: Skipped test %s\n", test->file, test->line, test->name); 88 | if (result.message) { 89 | fprintf(stderr, " -> %s\n", result.message); 90 | } 91 | break; 92 | case TestStatusFailure: 93 | failures++; 94 | fprintf(stderr, "%s:%d: error: Test %s failed\n", test->file, test->line, test->name); 95 | if (result.message) { 96 | fprintf(stderr, " -> %s\n", result.message); 97 | } 98 | break; 99 | case TestStatusFailureHexdump: 100 | failures++; 101 | fprintf(stderr, "%s:%d: error: Test %s failed\n", test->file, test->line, test->name); 102 | if (result.message) { 103 | fprintf(stderr, " -> %s\n", result.message); 104 | } 105 | if (result.valid) { 106 | fprintf(stderr, " -> Template (%zu bytes)\n", result.valid->len); 107 | buffer_hexdump(result.valid, 5); 108 | } 109 | if (result.buffer) { 110 | fprintf(stderr, " -> Buffer (%zu bytes)\n", result.buffer->len); 111 | buffer_hexdump(result.buffer, 5); 112 | } 113 | break; 114 | } 115 | } 116 | 117 | fprintf(stderr, "\n%d Tests, %d skipped, %d succeeded, %d failed\n\n", 118 | skips + successes + failures, 119 | skips, 120 | successes, 121 | failures 122 | ); 123 | 124 | return failures > 0; 125 | } 126 | --------------------------------------------------------------------------------