Commit ed2f25ca09a1ccdb14868642377feea407ab3483

Authored by Patric Stout
1 parent 2d8bd867

feat(client): library header-file and empty implementation

This contains no actual code yet, just the scaffolding to get
started.
.gitignore 0 → 100644
  1 +/build
... ...
CMakeLists.txt 0 → 100644
  1 +#
  2 +# Copyright (c) TrueBrain
  3 +#
  4 +# This source code is licensed under the MIT license found in the
  5 +# LICENSE file in the root directory of this source tree.
  6 +#
  7 +
  8 +cmake_minimum_required(VERSION 3.16)
  9 +
  10 +project(truemqtt VERSION 1.0.0 DESCRIPTION "A modern C++ MQTT Client library")
  11 +
  12 +set(CMAKE_CXX_STANDARD 17)
  13 +set(CMAKE_CXX_STANDARD_REQUIRED True)
  14 +
  15 +set(MIN_LOGGER_LEVEL "INFO" CACHE STRING "Set minimal logger level (TRACE, DEBUG, INFO, WARN, ERROR). No logs below this level will be omitted.")
  16 +
  17 +include(GNUInstallDirs)
  18 +
  19 +add_library(${PROJECT_NAME}
  20 + src/Client.cpp
  21 +)
  22 +target_include_directories(${PROJECT_NAME} PUBLIC include PRIVATE src)
  23 +
  24 +set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 1 PUBLIC_HEADER include/TrueMQTT.h)
  25 +configure_file(truemqtt.pc.in truemqtt.pc @ONLY)
  26 +
  27 +if(MIN_LOGGER_LEVEL)
  28 + if(("${MIN_LOGGER_LEVEL}" STREQUAL "TRACE") OR
  29 + ("${MIN_LOGGER_LEVEL}" STREQUAL "DEBUG") OR
  30 + ("${MIN_LOGGER_LEVEL}" STREQUAL "INFO") OR
  31 + ("${MIN_LOGGER_LEVEL}" STREQUAL "WARNING") OR
  32 + ("${MIN_LOGGER_LEVEL}" STREQUAL "ERROR"))
  33 + target_compile_definitions(${PROJECT_NAME} PRIVATE MIN_LOGGER_LEVEL=LOGGER_LEVEL_${MIN_LOGGER_LEVEL})
  34 + else()
  35 + message(FATAL_ERROR "Unknown value provided for MIN_LOGGER_LEVEL: \"${MIN_LOGGER_LEVEL}\", must be one of TRACE, DEBUG, INFO, WARNING or ERROR")
  36 + endif()
  37 +endif()
  38 +
  39 +
  40 +target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror)
  41 +
  42 +install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
  43 +install(FILES ${CMAKE_BINARY_DIR}/truemqtt.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)
  44 +
  45 +add_subdirectory(example/pubsub)
... ...
LICENSE 0 → 100644
  1 +Copyright (c) 2022 TrueBrain
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +of this software and associated documentation files (the "Software"), to deal
  5 +in the Software without restriction, including without limitation the rights
  6 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +copies of the Software, and to permit persons to whom the Software is
  8 +furnished to do so, subject to the following conditions:
  9 +
  10 +The above copyright notice and this permission notice shall be included in all
  11 +copies or substantial portions of the Software.
  12 +
  13 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19 +SOFTWARE.
... ...
README.md 0 → 100644
  1 +# TrueMQTT - A modern C++ MQTT Client library
  2 +
  3 +## Development
  4 +
  5 +```bash
  6 +mkdir build
  7 +cd build
  8 +make .. -DBUILD_SHARED_LIBS=ON -DMIN_LOGGER_LEVEL=TRACE
  9 +make -j$(nproc)
  10 +
  11 +example/pubsub/truemqtt_pubsub
  12 +```
... ...
example/pubsub/CMakeLists.txt 0 → 100644
  1 +#
  2 +# Copyright (c) TrueBrain
  3 +#
  4 +# This source code is licensed under the MIT license found in the
  5 +# LICENSE file in the root directory of this source tree.
  6 +#
  7 +
  8 +cmake_minimum_required(VERSION 3.16)
  9 +
  10 +project(truemqtt_pubsub)
  11 +
  12 +set(CMAKE_CXX_STANDARD 17)
  13 +set(CMAKE_CXX_STANDARD_REQUIRED ON)
  14 +
  15 +include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/../../include)
  16 +
  17 +add_executable(${PROJECT_NAME} main.cpp)
  18 +target_link_libraries(${PROJECT_NAME} truemqtt)
... ...
example/pubsub/main.cpp 0 → 100644
  1 +/*
  2 + * Copyright (c) TrueBrain
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +
  8 +#include <TrueMQTT.h>
  9 +#include <iostream>
  10 +
  11 +int main()
  12 +{
  13 + // Create a connection to the local broker.
  14 + TrueMQTT::Client client("localhost", 1883, "test");
  15 +
  16 + client.setLogger(TrueMQTT::Client::LogLevel::TRACE, [](TrueMQTT::Client::LogLevel level, std::string message) {
  17 + std::cout << "Log " << level << ": " << message << std::endl;
  18 + });
  19 +
  20 + client.connect();
  21 +
  22 + // Subscribe to the topic we will be publishing under in a bit.
  23 + client.subscribe("test", [](const std::string &topic, const std::string &payload) {
  24 + std::cout << "Received message on topic " << topic << ": " << payload << std::endl;
  25 + });
  26 +
  27 + // Publish a message on the same topic as we subscribed too.
  28 + client.publish("test", "Hello World!", false);
  29 +
  30 + client.disconnect();
  31 +
  32 + return 0;
  33 +}
... ...
include/TrueMQTT.h 0 → 100644
  1 +/*
  2 + * Copyright (c) TrueBrain
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +
  8 +#pragma once
  9 +
  10 +#include <functional>
  11 +#include <memory>
  12 +
  13 +namespace TrueMQTT
  14 +{
  15 + /**
  16 + * @brief MQTT Client class.
  17 + * This class manages the MQTT connection and provides methods to publish and subscribe to topics.
  18 + */
  19 + class Client
  20 + {
  21 + public:
  22 + /**
  23 + * @brief Error codes that can be returned in the callback set by \ref setErrorCallback.
  24 + */
  25 + enum Error
  26 + {
  27 + SUBSCRIBE_FAILED, ///< The subscription failed. The topic that failed to subscribe is passed as the second argument.
  28 + UNSUBSCRIBE_FAILED, ///< The unsubscription failed. The topic that failed to unsubscribe is passed as the second argument.
  29 + DISCONNECTED, ///< The connection was lost. The reason for the disconnection is passed as the second argument.
  30 + CONNECTION_FAILED, ///< The connection failed. The reason for the failure is passed as the second argument.
  31 + };
  32 +
  33 + /**
  34 + * @brief The type of queue that can be set for publishing messages.
  35 + */
  36 + enum QueueType
  37 + {
  38 + DROP, ///< Do not queue.
  39 + FIFO, ///< Global FIFO.
  40 + LIFO, ///< Global LIFO.
  41 + LIFO_PER_TOPIC, ///< Per topic LIFO.
  42 + };
  43 +
  44 + /**
  45 + * @brief The log levels used by this library.
  46 + */
  47 + enum LogLevel
  48 + {
  49 + NONE, ///< Do not log anything (default).
  50 + ERROR, ///< Something went wrong and the library cannot recover.
  51 + WARNING, ///< Something wasn't right, but the library can recover.
  52 + INFO, ///< Information that might be useful to know.
  53 + DEBUG, ///< Information that might be useful for debugging.
  54 + TRACE, ///< Information that is overly verbose to tell exactly what the library is doing.
  55 + };
  56 +
  57 + /**
  58 + * @brief Constructor for the MQTT client.
  59 + *
  60 + * @param host The hostname of the MQTT broker. Can be either an IP or a domain name.
  61 + * @param port Port of the MQTT broker.
  62 + * @param client_id Client ID to use when connecting to the broker.
  63 + * @param connection_timeout Timeout in seconds for the connection to the broker.
  64 + * @param connection_backoff_max Maximum time between backoff attempts in seconds.
  65 + * @param keep_alive_interval Interval in seconds between keep-alive messages.
  66 + */
  67 + Client(const std::string &host, int port, const std::string &client_id, int connection_timeout = 5, int connection_backoff_max = 30, int keep_alive_interval = 30);
  68 +
  69 + /**
  70 + * @brief Destructor of the MQTT client.
  71 + *
  72 + * Before destruction, any open connection is closed gracefully.
  73 + */
  74 + ~Client();
  75 +
  76 + /**
  77 + * @brief Set the logger callback and level.
  78 + *
  79 + * @param log_level The \ref LogLevel to use for logging.
  80 + * @param logger The callback to call when a log message is generated.
  81 + *
  82 + * @note This library doesn't contain a logger, so you need to provide one.
  83 + * If this method is not called, no logging will be done.
  84 + */
  85 + void setLogger(LogLevel log_level, std::function<void(LogLevel, std::string)> logger);
  86 +
  87 + /**
  88 + * @brief Set the last will message on the connection.
  89 + *
  90 + * @param topic The topic to publish the last will message to.
  91 + * @param payload The payload of the last will message.
  92 + * @param retain Whether to retain the last will message.
  93 + */
  94 + void setLastWill(const std::string &topic, const std::string &payload, bool retain);
  95 +
  96 + /**
  97 + * @brief Set the error callback, called when any error occurs.
  98 + * @param callback The callback to call when an error occurs.
  99 + */
  100 + void setErrorCallback(std::function<void(Error, std::string &)> callback);
  101 +
  102 + /**
  103 + * @brief Set the publish queue to use.
  104 + *
  105 + * @param queue_type The \ref QueueType to use for the publish queue.
  106 + * @param size The size of the queue. If the queue is full, the type of queue defines what happens.
  107 + */
  108 + void setPublishQueue(QueueType queue_type, int size);
  109 +
  110 + /**
  111 + * @brief Connect to the broker.
  112 + *
  113 + * After calling this function, the library will try a connection to the broker.
  114 + * If the connection fails, it will try again after a backoff period.
  115 + * The backoff period will increase until it reaches the maximum backoff period.
  116 + *
  117 + * If the connection succeeds, but it disconnected later (without calling \ref disconnect),
  118 + * the library will try to reconnect.
  119 + *
  120 + * @note Calling connect twice has no effect.
  121 + */
  122 + void connect();
  123 +
  124 + /**
  125 + * @brief Disconnect from the broker.
  126 + *
  127 + * This function will disconnect from the broker and stop trying to reconnect.
  128 + * Additionally, it will clean any publish / subscribe information it has.
  129 + *
  130 + * @note Calling disconnect twice has no effect.
  131 + */
  132 + void disconnect();
  133 +
  134 + /**
  135 + * @brief Publish a payload on a topic.
  136 + *
  137 + * @param topic The topic to publish the payload on.
  138 + * @param payload The payload to publish.
  139 + * @param retain Whether to retain the message on the broker.
  140 + *
  141 + * @note All messages are always published under QoS 0, and this library supports no
  142 + * other QoS level.
  143 + * @note This call is non-blocking, and it is not possible to know whether the message
  144 + * was actually published or not.
  145 + */
  146 + void publish(const std::string &topic, const std::string &payload, bool retain);
  147 +
  148 + /**
  149 + * @brief Subscribe to a topic, and call the callback function when a message arrives.
  150 + *
  151 + * @param topic The topic to subscribe to.
  152 + * @param callback The callback to call when a message arrives on this topic.
  153 + *
  154 + * @note Subscription can overlap, but you cannot subscribe on the exact same topic twice.
  155 + * If you do, the callback of the first subscription will be overwritten.
  156 + * In other words, "a/+" and "a/b" is fine, and callbacks for both subscribes will be
  157 + * called when something is published on "a/b".
  158 + */
  159 + void subscribe(const std::string &topic, std::function<void(std::string, std::string)> callback);
  160 +
  161 + /**
  162 + * @brief Unsubscribe from a topic.
  163 + *
  164 + * @param topic The topic to unsubscribe from.
  165 + *
  166 + * @note If you unsubscribe from a topic you were not subscribed too, nothing happens.
  167 + */
  168 + void unsubscribe(const std::string &topic);
  169 +
  170 + private:
  171 + // Private implementation
  172 + class Impl;
  173 + std::unique_ptr<Impl> m_impl;
  174 + };
  175 +}
... ...
src/Client.cpp 0 → 100644
  1 +/*
  2 + * Copyright (c) TrueBrain
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +
  8 +#include "TrueMQTT.h"
  9 +#include "Log.h"
  10 +
  11 +#include <string>
  12 +
  13 +using TrueMQTT::Client;
  14 +
  15 +// This class tracks all internal variables of the client. This way the header
  16 +// doesn't need to include the internal implementation of the Client.
  17 +class Client::Impl
  18 +{
  19 +public:
  20 + Impl(const std::string &host, int port, const std::string &client_id, int connection_timeout, int connection_backoff_max, int keep_alive_interval)
  21 + : host(host),
  22 + port(port),
  23 + client_id(client_id),
  24 + connection_timeout(connection_timeout),
  25 + connection_backoff_max(connection_backoff_max),
  26 + keep_alive_interval(keep_alive_interval)
  27 + {
  28 + }
  29 +
  30 + enum State
  31 + {
  32 + DISCONNECTED,
  33 + CONNECTING,
  34 + CONNECTED,
  35 + };
  36 +
  37 + State state = State::DISCONNECTED; ///< The current state of the client.
  38 +
  39 + std::string host; ///< Host of the broker.
  40 + int port; ///< Port of the broker.
  41 + std::string client_id; ///< Client ID to use when connecting to the broker.
  42 + int connection_timeout; ///< Timeout in seconds for the connection to the broker.
  43 + int connection_backoff_max; ///< Maximum time between backoff attempts in seconds.
  44 + int keep_alive_interval; ///< Interval in seconds between keep-alive messages.
  45 +
  46 + Client::LogLevel log_level = Client::LogLevel::NONE; ///< The log level to use.
  47 + std::function<void(Client::LogLevel, std::string)> logger = [](Client::LogLevel, std::string) {}; ///< Logger callback.
  48 +
  49 + std::string last_will_topic = ""; ///< Topic to publish the last will message to.
  50 + std::string last_will_payload = ""; ///< Payload of the last will message.
  51 + bool last_will_retain = false; ///< Whether to retain the last will message.
  52 +
  53 + std::function<void(Error, std::string &)> error_callback = [](Error, std::string &) {}; ///< Error callback.
  54 +
  55 + Client::QueueType publish_queue_type = Client::QueueType::DROP; ///< The type of queue to use for the publish queue.
  56 + int publish_queue_size = -1; ///< Size of the publish queue.
  57 +};
  58 +
  59 +Client::Client(const std::string &host, int port, const std::string &client_id, int connection_timeout, int connection_backoff_max, int keep_alive_interval)
  60 +{
  61 + this->m_impl = std::make_unique<Client::Impl>(host, port, client_id, connection_timeout, connection_backoff_max, keep_alive_interval);
  62 +
  63 + LOG_TRACE("Constructor of client called");
  64 +}
  65 +
  66 +Client::~Client()
  67 +{
  68 + LOG_TRACE("Destructor of client called");
  69 +
  70 + this->disconnect();
  71 +}
  72 +
  73 +void Client::setLogger(Client::LogLevel log_level, std::function<void(Client::LogLevel, std::string)> logger)
  74 +{
  75 + LOG_TRACE("Setting logger to log level " + std::to_string(log_level));
  76 +
  77 + this->m_impl->log_level = log_level;
  78 + this->m_impl->logger = logger;
  79 +
  80 + LOG_DEBUG("Log level now on " + std::to_string(this->m_impl->log_level));
  81 +}
  82 +
  83 +void Client::setLastWill(const std::string &topic, const std::string &payload, bool retain)
  84 +{
  85 + LOG_TRACE("Setting last will to topic " + topic + " with payload " + payload + " and retain " + std::to_string(retain));
  86 +
  87 + this->m_impl->last_will_topic = topic;
  88 + this->m_impl->last_will_payload = payload;
  89 + this->m_impl->last_will_retain = retain;
  90 +}
  91 +
  92 +void Client::setErrorCallback(std::function<void(Error, std::string &)> callback)
  93 +{
  94 + LOG_TRACE("Setting error callback");
  95 +
  96 + this->m_impl->error_callback = callback;
  97 +}
  98 +
  99 +void Client::setPublishQueue(Client::QueueType queue_type, int size)
  100 +{
  101 + LOG_TRACE("Setting publish queue to type " + std::to_string(queue_type) + " and size " + std::to_string(size));
  102 +
  103 + this->m_impl->publish_queue_type = queue_type;
  104 + this->m_impl->publish_queue_size = size;
  105 +}
  106 +
  107 +void Client::connect()
  108 +{
  109 + if (this->m_impl->state != Client::Impl::State::DISCONNECTED)
  110 + {
  111 + return;
  112 + }
  113 +
  114 + LOG_INFO("Connecting to " + this->m_impl->host + ":" + std::to_string(this->m_impl->port));
  115 +
  116 + this->m_impl->state = Client::Impl::State::CONNECTING;
  117 +}
  118 +
  119 +void Client::disconnect()
  120 +{
  121 + if (this->m_impl->state == Client::Impl::State::DISCONNECTED)
  122 + {
  123 + LOG_TRACE("Already disconnected");
  124 + return;
  125 + }
  126 +
  127 + LOG_INFO("Disconnecting from broker");
  128 +
  129 + this->m_impl->state = Client::Impl::State::DISCONNECTED;
  130 +}
  131 +
  132 +void Client::publish(const std::string &topic, const std::string &payload, bool retain)
  133 +{
  134 + LOG_DEBUG("Publishing message on topic '" + topic + "': " + payload + " (" + (retain ? "retained" : "not retained") + ")");
  135 +}
  136 +
  137 +void Client::subscribe(const std::string &topic, std::function<void(std::string, std::string)> callback)
  138 +{
  139 + LOG_DEBUG("Subscribing to topic '" + topic + "'");
  140 +
  141 + (void)callback;
  142 +}
  143 +
  144 +void Client::unsubscribe(const std::string &topic)
  145 +{
  146 + LOG_DEBUG("Unsubscribing from topic '" + topic + "'");
  147 +}
... ...
src/Log.h 0 → 100644
  1 +/*
  2 + * Copyright (c) TrueBrain
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +
  8 +#pragma once
  9 +
  10 +// Wrappers to make logging a tiny bit easier to read.
  11 +// It heavily depends on Client.cpp's structure, and assumes
  12 +// this->m_impl is reachable.
  13 +
  14 +#define LOGGER_LEVEL_ERROR 0
  15 +#define LOGGER_LEVEL_WARN 1
  16 +#define LOGGER_LEVEL_INFO 2
  17 +#define LOGGER_LEVEL_DEBUG 3
  18 +#define LOGGER_LEVEL_TRACE 4
  19 +
  20 +// If no longer is defined, assume DEBUG level.
  21 +#ifndef MIN_LOGGER_LEVEL
  22 +#define MIN_LOGGER_LEVEL LOGGER_LEVEL_DEBUG
  23 +#endif
  24 +
  25 +#if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_ERROR
  26 +#define LOG_ERROR(x) \
  27 + if (this->m_impl->log_level >= Client::LogLevel::ERROR) \
  28 + { \
  29 + this->m_impl->logger(Client::LogLevel::ERROR, x); \
  30 + }
  31 +#else
  32 +#define LOG_ERROR(x)
  33 +#endif
  34 +
  35 +#if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_WARN
  36 +#define LOG_WARN(x) \
  37 + if (this->m_impl->log_level >= Client::LogLevel::WARN) \
  38 + { \
  39 + this->m_impl->logger(Client::LogLevel::WARN, x); \
  40 + }
  41 +#else
  42 +#define LOG_WARN(x)
  43 +#endif
  44 +
  45 +#if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_INFO
  46 +#define LOG_INFO(x) \
  47 + if (this->m_impl->log_level >= Client::LogLevel::INFO) \
  48 + { \
  49 + this->m_impl->logger(Client::LogLevel::INFO, x); \
  50 + }
  51 +#else
  52 +#define LOG_INFO(x)
  53 +#endif
  54 +
  55 +#if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_DEBUG
  56 +#define LOG_DEBUG(x) \
  57 + if (this->m_impl->log_level >= Client::LogLevel::DEBUG) \
  58 + { \
  59 + this->m_impl->logger(Client::LogLevel::DEBUG, x); \
  60 + }
  61 +#else
  62 +#define LOG_DEBUG(x)
  63 +#endif
  64 +
  65 +#if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_TRACE
  66 +#define LOG_TRACE(x) \
  67 + if (this->m_impl->log_level >= Client::LogLevel::TRACE) \
  68 + { \
  69 + this->m_impl->logger(Client::LogLevel::TRACE, x); \
  70 + }
  71 +#else
  72 +#define LOG_TRACE(x)
  73 +#endif
... ...
truemqtt.pc.in 0 → 100644
  1 +prefix=@CMAKE_INSTALL_PREFIX@
  2 +exec_prefix=@CMAKE_INSTALL_PREFIX@
  3 +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
  4 +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
  5 +
  6 +Name: @PROJECT_NAME@
  7 +Description: @PROJECT_DESCRIPTION@
  8 +Version: @PROJECT_VERSION@
  9 +
  10 +Requires:
  11 +Libs: -L${libdir} -ltruemqtt
  12 +Cflags: -I${includedir}
... ...