Commit 995dd53fb41fbd457b17cafe381a23f2edb9cedc
1 parent
7b1eeefa
feat(connection): connection logic using Happy Eyeballs
By using Happy Eyeballs, we stagger connections of a host resolves into multiple IPs. This is useful for IPv6 / IPv4 hosts, where one of the two can stutter. Sadly, creating a connection is rather complex, with many odd things that can happen along the way. For example, a writeable socket doesn't mean it is actually connected; it can also mean the socket is in an error state. This implementation is inspired by my own work on OpenTTD's variant of this.
Showing
8 changed files
with
626 additions
and
105 deletions
CMakeLists.txt
| @@ -11,6 +11,7 @@ project(truemqtt VERSION 1.0.0 DESCRIPTION "A modern C++ MQTT Client library") | @@ -11,6 +11,7 @@ project(truemqtt VERSION 1.0.0 DESCRIPTION "A modern C++ MQTT Client library") | ||
| 11 | 11 | ||
| 12 | set(CMAKE_CXX_STANDARD 17) | 12 | set(CMAKE_CXX_STANDARD 17) |
| 13 | set(CMAKE_CXX_STANDARD_REQUIRED True) | 13 | set(CMAKE_CXX_STANDARD_REQUIRED True) |
| 14 | +set(THREADS_PREFER_PTHREAD_FLAG ON) | ||
| 14 | 15 | ||
| 15 | set(MIN_LOGGER_LEVEL "INFO" CACHE STRING "Set minimal logger level (TRACE, DEBUG, INFO, WARNING, ERROR). No logs below this level will be omitted.") | 16 | set(MIN_LOGGER_LEVEL "INFO" CACHE STRING "Set minimal logger level (TRACE, DEBUG, INFO, WARNING, ERROR). No logs below this level will be omitted.") |
| 16 | 17 | ||
| @@ -18,9 +19,13 @@ include(GNUInstallDirs) | @@ -18,9 +19,13 @@ include(GNUInstallDirs) | ||
| 18 | 19 | ||
| 19 | add_library(${PROJECT_NAME} | 20 | add_library(${PROJECT_NAME} |
| 20 | src/Client.cpp | 21 | src/Client.cpp |
| 22 | + src/Connection.cpp | ||
| 21 | ) | 23 | ) |
| 22 | target_include_directories(${PROJECT_NAME} PUBLIC include PRIVATE src) | 24 | target_include_directories(${PROJECT_NAME} PUBLIC include PRIVATE src) |
| 23 | 25 | ||
| 26 | +find_package(Threads REQUIRED) | ||
| 27 | +target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) | ||
| 28 | + | ||
| 24 | set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 1 PUBLIC_HEADER include/TrueMQTT.h) | 29 | 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) | 30 | configure_file(truemqtt.pc.in truemqtt.pc @ONLY) |
| 26 | 31 |
example/pubsub/main.cpp
| @@ -17,6 +17,8 @@ int main() | @@ -17,6 +17,8 @@ int main() | ||
| 17 | client.setLogger(TrueMQTT::Client::LogLevel::TRACE, [](TrueMQTT::Client::LogLevel level, std::string message) | 17 | client.setLogger(TrueMQTT::Client::LogLevel::TRACE, [](TrueMQTT::Client::LogLevel level, std::string message) |
| 18 | { std::cout << "Log " << level << ": " << message << std::endl; }); | 18 | { std::cout << "Log " << level << ": " << message << std::endl; }); |
| 19 | client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 10); | 19 | client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 10); |
| 20 | + client.setErrorCallback([](TrueMQTT::Client::Error error, std::string message) | ||
| 21 | + { std::cout << "Error " << error << ": " << message << std::endl; }); | ||
| 20 | 22 | ||
| 21 | client.connect(); | 23 | client.connect(); |
| 22 | 24 |
include/TrueMQTT.h
| @@ -24,10 +24,37 @@ namespace TrueMQTT | @@ -24,10 +24,37 @@ namespace TrueMQTT | ||
| 24 | */ | 24 | */ |
| 25 | enum Error | 25 | enum Error |
| 26 | { | 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. | 27 | + /** |
| 28 | + * @brief The hostname could not be resolved into either an IPv4 or IPv6 address. | ||
| 29 | + * | ||
| 30 | + * This happens if the DNS server didn't return any valid IPv4 or IPv6 address | ||
| 31 | + * based on the hostname given. | ||
| 32 | + * | ||
| 33 | + * Due to the nature of this error, this library has no way to recover from | ||
| 34 | + * this. As such, this is considered a fatal error and the library takes no | ||
| 35 | + * attempt to gracefully handle this. | ||
| 36 | + * | ||
| 37 | + * @note This is a fatal error. You have to call \ref disconnect after this. | ||
| 38 | + */ | ||
| 39 | + HOSTNAME_LOOKUP_FAILED, | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * @brief The subscription failed. | ||
| 43 | + * | ||
| 44 | + * The topic that failed to subscribe is passed as the second argument. | ||
| 45 | + * | ||
| 46 | + * @note This error is non-fatal. | ||
| 47 | + */ | ||
| 48 | + SUBSCRIBE_FAILED, | ||
| 49 | + | ||
| 50 | + /** | ||
| 51 | + * @brief The unsubscription failed. | ||
| 52 | + * | ||
| 53 | + * The topic that failed to unsubscribe is passed as the second argument. | ||
| 54 | + * | ||
| 55 | + * @note This error is non-fatal. | ||
| 56 | + */ | ||
| 57 | + UNSUBSCRIBE_FAILED, | ||
| 31 | }; | 58 | }; |
| 32 | 59 | ||
| 33 | /** | 60 | /** |
| @@ -132,7 +159,7 @@ namespace TrueMQTT | @@ -132,7 +159,7 @@ namespace TrueMQTT | ||
| 132 | * | 159 | * |
| 133 | * @param callback The callback to call when an error occurs. | 160 | * @param callback The callback to call when an error occurs. |
| 134 | */ | 161 | */ |
| 135 | - void setErrorCallback(std::function<void(Error, std::string &)> callback); | 162 | + void setErrorCallback(std::function<void(Error, std::string)> callback); |
| 136 | 163 | ||
| 137 | /** | 164 | /** |
| 138 | * @brief Set the publish queue to use. | 165 | * @brief Set the publish queue to use. |
src/Client.cpp
| @@ -6,69 +6,12 @@ | @@ -6,69 +6,12 @@ | ||
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #include "TrueMQTT.h" | 8 | #include "TrueMQTT.h" |
| 9 | -#include "Log.h" | ||
| 10 | 9 | ||
| 11 | -#include <deque> | ||
| 12 | -#include <map> | ||
| 13 | -#include <mutex> | ||
| 14 | -#include <string> | 10 | +#include "ClientImpl.h" |
| 11 | +#include "Log.h" | ||
| 15 | 12 | ||
| 16 | using TrueMQTT::Client; | 13 | using TrueMQTT::Client; |
| 17 | 14 | ||
| 18 | -// This class tracks all internal variables of the client. This way the header | ||
| 19 | -// doesn't need to include the internal implementation of the Client. | ||
| 20 | -class Client::Impl | ||
| 21 | -{ | ||
| 22 | -public: | ||
| 23 | - Impl(const std::string &host, int port, const std::string &client_id, int connection_timeout, int connection_backoff_max, int keep_alive_interval) | ||
| 24 | - : host(host), | ||
| 25 | - port(port), | ||
| 26 | - client_id(client_id), | ||
| 27 | - connection_timeout(connection_timeout), | ||
| 28 | - connection_backoff_max(connection_backoff_max), | ||
| 29 | - keep_alive_interval(keep_alive_interval) | ||
| 30 | - { | ||
| 31 | - } | ||
| 32 | - | ||
| 33 | - enum State | ||
| 34 | - { | ||
| 35 | - DISCONNECTED, ///< The client is not connected to the broker, nor is it trying to connect. | ||
| 36 | - CONNECTING, ///< The client is either connecting or reconnecting to the broker. This can be in any stage of the connection process. | ||
| 37 | - CONNECTED, ///< The client is connected to the broker. | ||
| 38 | - }; | ||
| 39 | - | ||
| 40 | - void sendPublish(const std::string &topic, const std::string &payload, bool retain); ///< Send a publish message to the broker. | ||
| 41 | - void sendSubscribe(const std::string &topic); ///< Send a subscribe message to the broker. | ||
| 42 | - void sendUnsubscribe(const std::string &topic); ///< Send an unsubscribe message to the broker. | ||
| 43 | - void changeToConnected(); ///< Called when a connection goes from CONNECTING state to CONNECTED state. | ||
| 44 | - void toPublishQueue(const std::string &topic, const std::string &payload, bool retain); ///< Add a publish message to the publish queue. | ||
| 45 | - | ||
| 46 | - State state = State::DISCONNECTED; ///< The current state of the client. | ||
| 47 | - std::mutex state_mutex; ///< Mutex to protect state changes. | ||
| 48 | - | ||
| 49 | - std::string host; ///< Host of the broker. | ||
| 50 | - int port; ///< Port of the broker. | ||
| 51 | - std::string client_id; ///< Client ID to use when connecting to the broker. | ||
| 52 | - int connection_timeout; ///< Timeout in seconds for the connection to the broker. | ||
| 53 | - int connection_backoff_max; ///< Maximum time between backoff attempts in seconds. | ||
| 54 | - int keep_alive_interval; ///< Interval in seconds between keep-alive messages. | ||
| 55 | - | ||
| 56 | - Client::LogLevel log_level = Client::LogLevel::NONE; ///< The log level to use. | ||
| 57 | - std::function<void(Client::LogLevel, std::string)> logger = [](Client::LogLevel, std::string) {}; ///< Logger callback. | ||
| 58 | - | ||
| 59 | - std::string last_will_topic = ""; ///< Topic to publish the last will message to. | ||
| 60 | - std::string last_will_payload = ""; ///< Payload of the last will message. | ||
| 61 | - bool last_will_retain = false; ///< Whether to retain the last will message. | ||
| 62 | - | ||
| 63 | - std::function<void(Error, std::string &)> error_callback = [](Error, std::string &) {}; ///< Error callback. | ||
| 64 | - | ||
| 65 | - Client::PublishQueueType publish_queue_type = Client::PublishQueueType::DROP; ///< The type of queue to use for the publish queue. | ||
| 66 | - size_t publish_queue_size = -1; ///< Size of the publish queue. | ||
| 67 | - std::deque<std::tuple<std::string, std::string, bool>> publish_queue; ///< Queue of publish messages to send to the broker. | ||
| 68 | - | ||
| 69 | - std::map<std::string, std::function<void(std::string, std::string)>> subscriptions; ///< Map of active subscriptions. | ||
| 70 | -}; | ||
| 71 | - | ||
| 72 | Client::Client(const std::string &host, int port, const std::string &client_id, int connection_timeout, int connection_backoff_max, int keep_alive_interval) | 15 | Client::Client(const std::string &host, int port, const std::string &client_id, int connection_timeout, int connection_backoff_max, int keep_alive_interval) |
| 73 | { | 16 | { |
| 74 | this->m_impl = std::make_unique<Client::Impl>(host, port, client_id, connection_timeout, connection_backoff_max, keep_alive_interval); | 17 | this->m_impl = std::make_unique<Client::Impl>(host, port, client_id, connection_timeout, connection_backoff_max, keep_alive_interval); |
| @@ -108,7 +51,7 @@ void Client::setLastWill(const std::string &topic, const std::string &payload, b | @@ -108,7 +51,7 @@ void Client::setLastWill(const std::string &topic, const std::string &payload, b | ||
| 108 | this->m_impl->last_will_retain = retain; | 51 | this->m_impl->last_will_retain = retain; |
| 109 | } | 52 | } |
| 110 | 53 | ||
| 111 | -void Client::setErrorCallback(std::function<void(Error, std::string &)> callback) | 54 | +void Client::setErrorCallback(std::function<void(Error, std::string)> callback) |
| 112 | { | 55 | { |
| 113 | LOG_TRACE(this->m_impl, "Setting error callback"); | 56 | LOG_TRACE(this->m_impl, "Setting error callback"); |
| 114 | 57 | ||
| @@ -141,6 +84,7 @@ void Client::connect() | @@ -141,6 +84,7 @@ void Client::connect() | ||
| 141 | LOG_INFO(this->m_impl, "Connecting to " + this->m_impl->host + ":" + std::to_string(this->m_impl->port)); | 84 | LOG_INFO(this->m_impl, "Connecting to " + this->m_impl->host + ":" + std::to_string(this->m_impl->port)); |
| 142 | 85 | ||
| 143 | this->m_impl->state = Client::Impl::State::CONNECTING; | 86 | this->m_impl->state = Client::Impl::State::CONNECTING; |
| 87 | + this->m_impl->connect(); | ||
| 144 | } | 88 | } |
| 145 | 89 | ||
| 146 | void Client::disconnect() | 90 | void Client::disconnect() |
| @@ -156,7 +100,7 @@ void Client::disconnect() | @@ -156,7 +100,7 @@ void Client::disconnect() | ||
| 156 | LOG_INFO(this->m_impl, "Disconnecting from broker"); | 100 | LOG_INFO(this->m_impl, "Disconnecting from broker"); |
| 157 | 101 | ||
| 158 | this->m_impl->state = Client::Impl::State::DISCONNECTED; | 102 | this->m_impl->state = Client::Impl::State::DISCONNECTED; |
| 159 | - this->m_impl->subscriptions.clear(); | 103 | + this->m_impl->disconnect(); |
| 160 | } | 104 | } |
| 161 | 105 | ||
| 162 | void Client::publish(const std::string &topic, const std::string &payload, bool retain) | 106 | void Client::publish(const std::string &topic, const std::string &payload, bool retain) |
| @@ -234,33 +178,41 @@ void Client::Impl::sendUnsubscribe(const std::string &topic) | @@ -234,33 +178,41 @@ void Client::Impl::sendUnsubscribe(const std::string &topic) | ||
| 234 | LOG_TRACE(this, "Sending unsubscribe message for topic '" + topic + "'"); | 178 | LOG_TRACE(this, "Sending unsubscribe message for topic '" + topic + "'"); |
| 235 | } | 179 | } |
| 236 | 180 | ||
| 237 | -void Client::Impl::changeToConnected() | 181 | +void Client::Impl::connectionStateChange(bool connected) |
| 238 | { | 182 | { |
| 239 | std::scoped_lock lock(this->state_mutex); | 183 | std::scoped_lock lock(this->state_mutex); |
| 240 | 184 | ||
| 241 | - LOG_INFO(this, "Connected to broker"); | 185 | + if (connected) |
| 186 | + { | ||
| 187 | + LOG_INFO(this, "Connected to broker"); | ||
| 242 | 188 | ||
| 243 | - this->state = Client::Impl::State::CONNECTED; | 189 | + this->state = Client::Impl::State::CONNECTED; |
| 244 | 190 | ||
| 245 | - // Restoring subscriptions and flushing the queue is done while still under | ||
| 246 | - // the lock. This to prevent \ref disconnect from being called while we are | ||
| 247 | - // still sending messages. | ||
| 248 | - // The drawback is that we are blocking \ref publish and \ref subscribe too | ||
| 249 | - // when they are called just when we create a connection. But in the grand | ||
| 250 | - // scheme of things, this is not likely, and this makes for a far easier | ||
| 251 | - // implementation. | 191 | + // Restoring subscriptions and flushing the queue is done while still under |
| 192 | + // the lock. This to prevent \ref disconnect from being called while we are | ||
| 193 | + // still sending messages. | ||
| 194 | + // The drawback is that we are blocking \ref publish and \ref subscribe too | ||
| 195 | + // when they are called just when we create a connection. But in the grand | ||
| 196 | + // scheme of things, this is not likely, and this makes for a far easier | ||
| 197 | + // implementation. | ||
| 252 | 198 | ||
| 253 | - // First restore any subscription. | ||
| 254 | - for (auto &subscription : this->subscriptions) | ||
| 255 | - { | ||
| 256 | - this->sendSubscribe(subscription.first); | 199 | + // First restore any subscription. |
| 200 | + for (auto &subscription : this->subscriptions) | ||
| 201 | + { | ||
| 202 | + this->sendSubscribe(subscription.first); | ||
| 203 | + } | ||
| 204 | + // Flush the publish queue. | ||
| 205 | + for (auto &message : this->publish_queue) | ||
| 206 | + { | ||
| 207 | + this->sendPublish(std::get<0>(message), std::get<1>(message), std::get<2>(message)); | ||
| 208 | + } | ||
| 209 | + this->publish_queue.clear(); | ||
| 257 | } | 210 | } |
| 258 | - // Flush the publish queue. | ||
| 259 | - for (auto &message : this->publish_queue) | 211 | + else |
| 260 | { | 212 | { |
| 261 | - this->sendPublish(std::get<0>(message), std::get<1>(message), std::get<2>(message)); | 213 | + LOG_INFO(this, "Disconnected from broker"); |
| 214 | + this->state = Client::Impl::State::CONNECTING; | ||
| 262 | } | 215 | } |
| 263 | - this->publish_queue.clear(); | ||
| 264 | } | 216 | } |
| 265 | 217 | ||
| 266 | void Client::Impl::toPublishQueue(const std::string &topic, const std::string &payload, bool retain) | 218 | void Client::Impl::toPublishQueue(const std::string &topic, const std::string &payload, bool retain) |
src/ClientImpl.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 "TrueMQTT.h" | ||
| 11 | + | ||
| 12 | +#include "Connection.h" | ||
| 13 | + | ||
| 14 | +#include <deque> | ||
| 15 | +#include <map> | ||
| 16 | +#include <mutex> | ||
| 17 | +#include <string> | ||
| 18 | +#include <thread> | ||
| 19 | + | ||
| 20 | +// This class tracks all internal variables of the client. This way the header | ||
| 21 | +// doesn't need to include the internal implementation of the Client. | ||
| 22 | +class TrueMQTT::Client::Impl | ||
| 23 | +{ | ||
| 24 | +public: | ||
| 25 | + Impl(const std::string &host, int port, const std::string &client_id, int connection_timeout, int connection_backoff_max, int keep_alive_interval) | ||
| 26 | + : host(host), | ||
| 27 | + port(port), | ||
| 28 | + client_id(client_id), | ||
| 29 | + connection_timeout(connection_timeout), | ||
| 30 | + connection_backoff_max(connection_backoff_max), | ||
| 31 | + keep_alive_interval(keep_alive_interval) | ||
| 32 | + { | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + enum State | ||
| 36 | + { | ||
| 37 | + DISCONNECTED, ///< The client is not connected to the broker, nor is it trying to connect. | ||
| 38 | + CONNECTING, ///< The client is either connecting or reconnecting to the broker. This can be in any stage of the connection process. | ||
| 39 | + CONNECTED, ///< The client is connected to the broker. | ||
| 40 | + }; | ||
| 41 | + | ||
| 42 | + void connect(); ///< Connect to the broker. | ||
| 43 | + void disconnect(); ///< Disconnect from the broker. | ||
| 44 | + void sendPublish(const std::string &topic, const std::string &payload, bool retain); ///< Send a publish message to the broker. | ||
| 45 | + void sendSubscribe(const std::string &topic); ///< Send a subscribe message to the broker. | ||
| 46 | + void sendUnsubscribe(const std::string &topic); ///< Send an unsubscribe message to the broker. | ||
| 47 | + void connectionStateChange(bool connected); ///< Called when a connection goes from CONNECTING state to CONNECTED state or visa versa. | ||
| 48 | + void toPublishQueue(const std::string &topic, const std::string &payload, bool retain); ///< Add a publish message to the publish queue. | ||
| 49 | + | ||
| 50 | + State state = State::DISCONNECTED; ///< The current state of the client. | ||
| 51 | + std::mutex state_mutex; ///< Mutex to protect state changes. | ||
| 52 | + | ||
| 53 | + std::string host; ///< Host of the broker. | ||
| 54 | + int port; ///< Port of the broker. | ||
| 55 | + std::string client_id; ///< Client ID to use when connecting to the broker. | ||
| 56 | + int connection_timeout; ///< Timeout in seconds for the connection to the broker. | ||
| 57 | + int connection_backoff_max; ///< Maximum time between backoff attempts in seconds. | ||
| 58 | + int keep_alive_interval; ///< Interval in seconds between keep-alive messages. | ||
| 59 | + | ||
| 60 | + Client::LogLevel log_level = Client::LogLevel::NONE; ///< The log level to use. | ||
| 61 | + std::function<void(Client::LogLevel, std::string)> logger = [](Client::LogLevel, std::string) {}; ///< Logger callback. | ||
| 62 | + | ||
| 63 | + std::string last_will_topic = ""; ///< Topic to publish the last will message to. | ||
| 64 | + std::string last_will_payload = ""; ///< Payload of the last will message. | ||
| 65 | + bool last_will_retain = false; ///< Whether to retain the last will message. | ||
| 66 | + | ||
| 67 | + std::function<void(Error, std::string)> error_callback = [](Error, std::string) {}; ///< Error callback. | ||
| 68 | + | ||
| 69 | + Client::PublishQueueType publish_queue_type = Client::PublishQueueType::DROP; ///< The type of queue to use for the publish queue. | ||
| 70 | + size_t publish_queue_size = -1; ///< Size of the publish queue. | ||
| 71 | + std::deque<std::tuple<std::string, std::string, bool>> publish_queue; ///< Queue of publish messages to send to the broker. | ||
| 72 | + | ||
| 73 | + std::map<std::string, std::function<void(std::string, std::string)>> subscriptions; ///< Map of active subscriptions. | ||
| 74 | + | ||
| 75 | + std::unique_ptr<Connection> connection; ///< Connection to the broker. | ||
| 76 | +}; |
src/Connection.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 "ClientImpl.h" | ||
| 9 | +#include "Connection.h" | ||
| 10 | +#include "Log.h" | ||
| 11 | + | ||
| 12 | +#include <memory.h> | ||
| 13 | +#include <netinet/tcp.h> | ||
| 14 | +#include <sys/ioctl.h> | ||
| 15 | +#include <sys/socket.h> | ||
| 16 | +#include <unistd.h> | ||
| 17 | +#include <vector> | ||
| 18 | + | ||
| 19 | +Connection::Connection(TrueMQTT::Client::LogLevel log_level, | ||
| 20 | + const std::function<void(TrueMQTT::Client::LogLevel, std::string)> logger, | ||
| 21 | + const std::function<void(TrueMQTT::Client::Error, std::string)> error_callback, | ||
| 22 | + const std::function<void(bool)> connection_change_callback, | ||
| 23 | + const std::string &host, | ||
| 24 | + int port) | ||
| 25 | + : log_level(log_level), | ||
| 26 | + logger(std::move(logger)), | ||
| 27 | + m_error_callback(std::move(error_callback)), | ||
| 28 | + m_connection_change_callback(std::move(connection_change_callback)), | ||
| 29 | + m_host(host), | ||
| 30 | + m_port(port), | ||
| 31 | + m_thread(&Connection::run, this) | ||
| 32 | +{ | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +Connection::~Connection() | ||
| 36 | +{ | ||
| 37 | + // Make sure the connection thread is terminated. | ||
| 38 | + if (m_thread.joinable()) | ||
| 39 | + { | ||
| 40 | + m_thread.join(); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + // freeaddrinfo() is one of those functions that doesn't take kind to NULL pointers | ||
| 44 | + // on some platforms. | ||
| 45 | + if (this->m_host_resolved != NULL) | ||
| 46 | + { | ||
| 47 | + freeaddrinfo(this->m_host_resolved); | ||
| 48 | + this->m_host_resolved = NULL; | ||
| 49 | + } | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +std::string Connection::addrinfo_to_string(addrinfo *address) | ||
| 53 | +{ | ||
| 54 | + char host[NI_MAXHOST]; | ||
| 55 | + getnameinfo(address->ai_addr, address->ai_addrlen, host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); | ||
| 56 | + | ||
| 57 | + return std::string(host); | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +void Connection::run() | ||
| 61 | +{ | ||
| 62 | + while (true) | ||
| 63 | + { | ||
| 64 | + switch (m_state) | ||
| 65 | + { | ||
| 66 | + case State::RESOLVING: | ||
| 67 | + resolve(); | ||
| 68 | + break; | ||
| 69 | + | ||
| 70 | + case State::CONNECTING: | ||
| 71 | + if (!connect_to_any()) | ||
| 72 | + { | ||
| 73 | + m_state = State::BACKOFF; | ||
| 74 | + } | ||
| 75 | + break; | ||
| 76 | + | ||
| 77 | + case State::BACKOFF: | ||
| 78 | + LOG_WARNING(this, "Connection failed; will retry in NNN seconds"); | ||
| 79 | + | ||
| 80 | + // TODO: use the configuration | ||
| 81 | + std::this_thread::sleep_for(std::chrono::seconds(5)); | ||
| 82 | + | ||
| 83 | + m_state = State::RESOLVING; | ||
| 84 | + break; | ||
| 85 | + | ||
| 86 | + case State::CONNECTED: | ||
| 87 | + { | ||
| 88 | + char buf[9000]; | ||
| 89 | + ssize_t res = recv(m_socket, buf, sizeof(buf), 0); | ||
| 90 | + | ||
| 91 | + if (res == 0) | ||
| 92 | + { | ||
| 93 | + LOG_WARNING(this, "Connection closed by peer"); | ||
| 94 | + m_state = State::BACKOFF; | ||
| 95 | + m_connection_change_callback(false); | ||
| 96 | + } | ||
| 97 | + else if (res < 0) | ||
| 98 | + { | ||
| 99 | + LOG_WARNING(this, "Connection read error: " + std::string(strerror(errno))); | ||
| 100 | + m_state = State::BACKOFF; | ||
| 101 | + m_connection_change_callback(false); | ||
| 102 | + } | ||
| 103 | + else | ||
| 104 | + { | ||
| 105 | + LOG_TRACE(this, "Received " + std::to_string(res) + " bytes"); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + break; | ||
| 109 | + } | ||
| 110 | + } | ||
| 111 | + } | ||
| 112 | +} | ||
| 113 | + | ||
| 114 | +void Connection::resolve() | ||
| 115 | +{ | ||
| 116 | + m_address_current = 0; | ||
| 117 | + m_socket = INVALID_SOCKET; | ||
| 118 | + m_addresses.clear(); | ||
| 119 | + | ||
| 120 | + addrinfo hints; | ||
| 121 | + memset(&hints, 0, sizeof(hints)); | ||
| 122 | + hints.ai_family = AF_UNSPEC; // Request IPv4 and IPv6. | ||
| 123 | + hints.ai_socktype = SOCK_STREAM; | ||
| 124 | + hints.ai_flags = AI_ADDRCONFIG; | ||
| 125 | + | ||
| 126 | + // Request the OS to resolve the hostname into an IP address. | ||
| 127 | + // We do this even if the hostname is already an IP address, as that | ||
| 128 | + // makes for far easier code. | ||
| 129 | + int error = getaddrinfo(m_host.c_str(), std::to_string(m_port).c_str(), &hints, &m_host_resolved); | ||
| 130 | + if (error != 0) | ||
| 131 | + { | ||
| 132 | + m_error_callback(TrueMQTT::Client::Error::HOSTNAME_LOOKUP_FAILED, std::string(gai_strerror(error))); | ||
| 133 | + return; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + // Split the list of addresses in two lists, one for IPv4 and one for | ||
| 137 | + // IPv6. | ||
| 138 | + std::deque<addrinfo *> addresses_ipv4; | ||
| 139 | + std::deque<addrinfo *> addresses_ipv6; | ||
| 140 | + for (addrinfo *ai = this->m_host_resolved; ai != nullptr; ai = ai->ai_next) | ||
| 141 | + { | ||
| 142 | + if (ai->ai_family == AF_INET6) | ||
| 143 | + { | ||
| 144 | + addresses_ipv6.emplace_back(ai); | ||
| 145 | + } | ||
| 146 | + else if (ai->ai_family == AF_INET) | ||
| 147 | + { | ||
| 148 | + addresses_ipv4.emplace_back(ai); | ||
| 149 | + } | ||
| 150 | + // Sometimes there can also be other types of families, but we are | ||
| 151 | + // not interested in those results. | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + // Interweave the IPv6 and IPv4 addresses. For connections we apply | ||
| 155 | + // "Happy Eyeballs", where we try an IPv6 connection first, and if that | ||
| 156 | + // doesn't connect after 100ms, we try an IPv4 connection. | ||
| 157 | + // This is to prevent long timeouts when IPv6 is not available, but | ||
| 158 | + // still prefer IPv6 where possible. | ||
| 159 | + while (!addresses_ipv6.empty() || !addresses_ipv4.empty()) | ||
| 160 | + { | ||
| 161 | + if (!addresses_ipv6.empty()) | ||
| 162 | + { | ||
| 163 | + m_addresses.emplace_back(addresses_ipv6.front()); | ||
| 164 | + addresses_ipv6.pop_front(); | ||
| 165 | + } | ||
| 166 | + if (!addresses_ipv4.empty()) | ||
| 167 | + { | ||
| 168 | + m_addresses.emplace_back(addresses_ipv4.front()); | ||
| 169 | + addresses_ipv4.pop_front(); | ||
| 170 | + } | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | +#if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_DEBUG | ||
| 174 | + // For debugging, print the addresses we resolved into. | ||
| 175 | + if (this->log_level >= TrueMQTT::Client::LogLevel::DEBUG) | ||
| 176 | + { | ||
| 177 | + LOG_DEBUG(this, "Resolved hostname '" + m_host + "' to:"); | ||
| 178 | + for (addrinfo *res : m_addresses) | ||
| 179 | + { | ||
| 180 | + LOG_DEBUG(this, "- " + addrinfo_to_string(res)); | ||
| 181 | + } | ||
| 182 | + } | ||
| 183 | +#endif | ||
| 184 | + | ||
| 185 | + // In some odd cases, the list can be empty. This is a fatal error. | ||
| 186 | + if (m_addresses.empty()) | ||
| 187 | + { | ||
| 188 | + m_error_callback(TrueMQTT::Client::Error::HOSTNAME_LOOKUP_FAILED, ""); | ||
| 189 | + return; | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + m_state = State::CONNECTING; | ||
| 193 | +} | ||
| 194 | + | ||
| 195 | +bool Connection::connect_to_any() | ||
| 196 | +{ | ||
| 197 | + // Check if we have pending attempts. If not, queue a new attempt. | ||
| 198 | + if (m_sockets.empty()) | ||
| 199 | + { | ||
| 200 | + return try_next_address(); | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + // Check for at most 100ms if there is any activity on the sockets. | ||
| 204 | + timeval timeout; | ||
| 205 | + timeout.tv_sec = 0; | ||
| 206 | + timeout.tv_usec = 100; | ||
| 207 | + | ||
| 208 | + fd_set write_fds; | ||
| 209 | + FD_ZERO(&write_fds); | ||
| 210 | + for (const auto &socket : m_sockets) | ||
| 211 | + { | ||
| 212 | + FD_SET(socket, &write_fds); | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + int result = select(FD_SETSIZE, NULL, &write_fds, NULL, &timeout); | ||
| 216 | + | ||
| 217 | + // Check if there was an error on select(). This is hard to recover from. | ||
| 218 | + if (result < 0) | ||
| 219 | + { | ||
| 220 | + LOG_ERROR(this, "select() failed: " + std::string(strerror(errno))); | ||
| 221 | + return true; | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + // A result of zero means there was no activity on any of the sockets. | ||
| 225 | + if (result == 0) | ||
| 226 | + { | ||
| 227 | + // Check if it was more than 250ms ago since we started our last connection. | ||
| 228 | + if (std::chrono::steady_clock::now() < m_last_attempt + std::chrono::milliseconds(250)) | ||
| 229 | + { | ||
| 230 | + return true; | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + // Try to queue the next address for a connection. | ||
| 234 | + if (try_next_address()) | ||
| 235 | + { | ||
| 236 | + return true; | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + // Check if it is more than the timeout ago since we last tried a connection. | ||
| 240 | + // TODO -- Used to configured value | ||
| 241 | + if (std::chrono::steady_clock::now() < m_last_attempt + std::chrono::seconds(10)) | ||
| 242 | + { | ||
| 243 | + return true; | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + LOG_ERROR(this, "Connection attempt to broker timed out"); | ||
| 247 | + | ||
| 248 | + // Cleanup all sockets. | ||
| 249 | + for (const auto &socket : m_sockets) | ||
| 250 | + { | ||
| 251 | + closesocket(socket); | ||
| 252 | + } | ||
| 253 | + m_socket_to_address.clear(); | ||
| 254 | + m_sockets.clear(); | ||
| 255 | + | ||
| 256 | + return false; | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + // A socket that reports to be writeable is either connected or in error-state. | ||
| 260 | + // Remove all sockets that are in error-state. The first that is left and writeable, | ||
| 261 | + // will be the socket to use for the connection. | ||
| 262 | + SOCKET socket_connected = INVALID_SOCKET; | ||
| 263 | + for (auto socket_it = m_sockets.begin(); socket_it != m_sockets.end(); /* nothing */) | ||
| 264 | + { | ||
| 265 | + // Check if the socket is in error-state. | ||
| 266 | + int err; | ||
| 267 | + socklen_t len = sizeof(err); | ||
| 268 | + getsockopt(*socket_it, SOL_SOCKET, SO_ERROR, (char *)&err, &len); | ||
| 269 | + if (err != 0) | ||
| 270 | + { | ||
| 271 | + // It is in error-state: report about it, and remove it. | ||
| 272 | + LOG_ERROR(this, "Could not connect to " + addrinfo_to_string(m_socket_to_address[*socket_it]) + ": " + std::string(strerror(err))); | ||
| 273 | + closesocket(*socket_it); | ||
| 274 | + m_socket_to_address.erase(*socket_it); | ||
| 275 | + socket_it = m_sockets.erase(socket_it); | ||
| 276 | + continue; | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + if (socket_connected == INVALID_SOCKET && FD_ISSET(*socket_it, &write_fds)) | ||
| 280 | + { | ||
| 281 | + socket_connected = *socket_it; | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + socket_it++; | ||
| 285 | + } | ||
| 286 | + | ||
| 287 | + if (socket_connected == INVALID_SOCKET) | ||
| 288 | + { | ||
| 289 | + // No socket is connected yet. Continue waiting. | ||
| 290 | + return true; | ||
| 291 | + } | ||
| 292 | + | ||
| 293 | + // We have a connected socket. | ||
| 294 | + LOG_DEBUG(this, "Connected to " + addrinfo_to_string(m_socket_to_address[socket_connected])); | ||
| 295 | + | ||
| 296 | + // Close all other pending connections. | ||
| 297 | + for (const auto &socket : m_sockets) | ||
| 298 | + { | ||
| 299 | + if (socket != socket_connected) | ||
| 300 | + { | ||
| 301 | + closesocket(socket); | ||
| 302 | + } | ||
| 303 | + } | ||
| 304 | + m_socket_to_address.clear(); | ||
| 305 | + m_sockets.clear(); | ||
| 306 | + | ||
| 307 | + // Disable non-blocking, as we will be reading from a thread, which can be blocking. | ||
| 308 | + int nonblocking = 0; | ||
| 309 | + if (ioctl(socket_connected, FIONBIO, &nonblocking) != 0) | ||
| 310 | + { | ||
| 311 | + LOG_WARNING(this, "Could not set socket to non-blocking; expect performance impact"); | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + m_socket = socket_connected; | ||
| 315 | + m_state = State::CONNECTED; | ||
| 316 | + m_connection_change_callback(true); | ||
| 317 | + return true; | ||
| 318 | +} | ||
| 319 | + | ||
| 320 | +bool Connection::try_next_address() | ||
| 321 | +{ | ||
| 322 | + if (m_address_current >= m_addresses.size()) | ||
| 323 | + { | ||
| 324 | + return false; | ||
| 325 | + } | ||
| 326 | + | ||
| 327 | + m_last_attempt = std::chrono::steady_clock::now(); | ||
| 328 | + connect(m_addresses[m_address_current++]); | ||
| 329 | + | ||
| 330 | + return true; | ||
| 331 | +} | ||
| 332 | + | ||
| 333 | +void Connection::connect(addrinfo *address) | ||
| 334 | +{ | ||
| 335 | + // Create a new socket based on the resolved information. | ||
| 336 | + SOCKET sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol); | ||
| 337 | + if (sock == INVALID_SOCKET) | ||
| 338 | + { | ||
| 339 | + LOG_ERROR(this, "Could not create new socket"); | ||
| 340 | + return; | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + // Set socket to no-delay; this improves latency, but reduces throughput. | ||
| 344 | + int flags = 1; | ||
| 345 | + /* The (const char*) cast is needed for Windows */ | ||
| 346 | + if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&flags, sizeof(flags)) != 0) | ||
| 347 | + { | ||
| 348 | + LOG_WARNING(this, "Could not set TCP_NODELAY on socket"); | ||
| 349 | + } | ||
| 350 | + // Set socket to non-blocking; this allows for multiple connects to be pending. This is | ||
| 351 | + // needed to apply Happy Eyeballs. | ||
| 352 | + int nonblocking = 1; | ||
| 353 | + if (ioctl(sock, FIONBIO, &nonblocking) != 0) | ||
| 354 | + { | ||
| 355 | + LOG_WARNING(this, "Could not set socket to non-blocking; expect performance impact"); | ||
| 356 | + } | ||
| 357 | + | ||
| 358 | + // Start the actual connection attempt. | ||
| 359 | + LOG_DEBUG(this, "Connecting to " + addrinfo_to_string(address)); | ||
| 360 | + int err = ::connect(sock, address->ai_addr, (int)address->ai_addrlen); | ||
| 361 | + if (err != 0 && errno != EINPROGRESS) | ||
| 362 | + { | ||
| 363 | + // As we are non-blocking, normally this returns "in progress". If anything | ||
| 364 | + // else, something is wrong. Report the error and close the socket. | ||
| 365 | + closesocket(sock); | ||
| 366 | + | ||
| 367 | + LOG_ERROR(this, "Could not connect to " + addrinfo_to_string(address) + ": " + std::string(strerror(errno))); | ||
| 368 | + return; | ||
| 369 | + } | ||
| 370 | + | ||
| 371 | + // Connection is pending. | ||
| 372 | + m_socket_to_address[sock] = address; | ||
| 373 | + m_sockets.push_back(sock); | ||
| 374 | +} | ||
| 375 | + | ||
| 376 | +void TrueMQTT::Client::Impl::connect() | ||
| 377 | +{ | ||
| 378 | + this->connection = std::make_unique<Connection>( | ||
| 379 | + this->log_level, this->logger, this->error_callback, [this](bool connected) | ||
| 380 | + { this->connectionStateChange(connected); }, | ||
| 381 | + this->host, this->port); | ||
| 382 | +} | ||
| 383 | + | ||
| 384 | +void TrueMQTT::Client::Impl::disconnect() | ||
| 385 | +{ | ||
| 386 | + this->subscriptions.clear(); | ||
| 387 | + this->publish_queue.clear(); | ||
| 388 | + | ||
| 389 | + this->connection.reset(); | ||
| 390 | +} |
src/Connection.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 "TrueMQTT.h" | ||
| 11 | + | ||
| 12 | +#include <string> | ||
| 13 | +#include <map> | ||
| 14 | +#include <netdb.h> | ||
| 15 | +#include <thread> | ||
| 16 | +#include <vector> | ||
| 17 | + | ||
| 18 | +// Some definitions to make future cross-platform work easier. | ||
| 19 | +#define SOCKET int | ||
| 20 | +#define INVALID_SOCKET -1 | ||
| 21 | +#define closesocket close | ||
| 22 | + | ||
| 23 | +class Connection | ||
| 24 | +{ | ||
| 25 | +public: | ||
| 26 | + Connection(TrueMQTT::Client::LogLevel log_level, | ||
| 27 | + const std::function<void(TrueMQTT::Client::LogLevel, std::string)> logger, | ||
| 28 | + const std::function<void(TrueMQTT::Client::Error, std::string)> error_callback, | ||
| 29 | + const std::function<void(bool)> connection_change_callback, | ||
| 30 | + const std::string &host, | ||
| 31 | + int port); | ||
| 32 | + ~Connection(); | ||
| 33 | + | ||
| 34 | +private: | ||
| 35 | + void run(); | ||
| 36 | + void resolve(); | ||
| 37 | + bool try_next_address(); | ||
| 38 | + void connect(addrinfo *address); | ||
| 39 | + bool connect_to_any(); | ||
| 40 | + std::string addrinfo_to_string(addrinfo *address); | ||
| 41 | + | ||
| 42 | + enum class State | ||
| 43 | + { | ||
| 44 | + RESOLVING, | ||
| 45 | + CONNECTING, | ||
| 46 | + CONNECTED, | ||
| 47 | + BACKOFF, | ||
| 48 | + }; | ||
| 49 | + | ||
| 50 | + TrueMQTT::Client::LogLevel log_level; | ||
| 51 | + const std::function<void(TrueMQTT::Client::LogLevel, std::string)> logger; | ||
| 52 | + | ||
| 53 | + const std::function<void(TrueMQTT::Client::Error, std::string)> m_error_callback; | ||
| 54 | + const std::function<void(bool)> m_connection_change_callback; | ||
| 55 | + | ||
| 56 | + const std::string &m_host; ///< The hostname or IP address to connect to. | ||
| 57 | + int m_port; ///< The port to connect to. | ||
| 58 | + | ||
| 59 | + State m_state = State::RESOLVING; | ||
| 60 | + std::thread m_thread; ///< Current thread used to run this connection. | ||
| 61 | + | ||
| 62 | + addrinfo *m_host_resolved = nullptr; ///< Address info of the hostname, once looked up. | ||
| 63 | + std::vector<addrinfo *> m_addresses = {}; ///< List of addresses to try to connect to. | ||
| 64 | + size_t m_address_current = 0; ///< Index of the address we are currently trying to connect to. | ||
| 65 | + std::chrono::steady_clock::time_point m_last_attempt = {}; ///< Time of the last attempt to connect to the current address. | ||
| 66 | + std::vector<SOCKET> m_sockets = {}; ///< List of sockets we are currently trying to connect to. | ||
| 67 | + std::map<SOCKET, addrinfo *> m_socket_to_address = {}; ///< Map of sockets to the address they are trying to connect to. | ||
| 68 | + SOCKET m_socket = INVALID_SOCKET; ///< The socket we are currently connected with, or INVALID_SOCKET if not connected. | ||
| 69 | +}; |
src/Log.h
| @@ -8,7 +8,7 @@ | @@ -8,7 +8,7 @@ | ||
| 8 | #pragma once | 8 | #pragma once |
| 9 | 9 | ||
| 10 | // Wrappers to make logging a tiny bit easier to read. | 10 | // Wrappers to make logging a tiny bit easier to read. |
| 11 | -// The "obj" is the Client::Impl instance to point to. | 11 | +// The "obj" is the TrueMQTT::Client::Impl instance to point to. |
| 12 | 12 | ||
| 13 | #define LOGGER_LEVEL_ERROR 0 | 13 | #define LOGGER_LEVEL_ERROR 0 |
| 14 | #define LOGGER_LEVEL_WARNING 1 | 14 | #define LOGGER_LEVEL_WARNING 1 |
| @@ -22,50 +22,50 @@ | @@ -22,50 +22,50 @@ | ||
| 22 | #endif | 22 | #endif |
| 23 | 23 | ||
| 24 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_ERROR | 24 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_ERROR |
| 25 | -#define LOG_ERROR(obj, x) \ | ||
| 26 | - if (obj->log_level >= Client::LogLevel::ERROR) \ | ||
| 27 | - { \ | ||
| 28 | - obj->logger(Client::LogLevel::ERROR, x); \ | 25 | +#define LOG_ERROR(obj, x) \ |
| 26 | + if (obj->log_level >= TrueMQTT::Client::LogLevel::ERROR) \ | ||
| 27 | + { \ | ||
| 28 | + obj->logger(TrueMQTT::Client::LogLevel::ERROR, x); \ | ||
| 29 | } | 29 | } |
| 30 | #else | 30 | #else |
| 31 | #define LOG_ERROR(obj, x) | 31 | #define LOG_ERROR(obj, x) |
| 32 | #endif | 32 | #endif |
| 33 | 33 | ||
| 34 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_WARNING | 34 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_WARNING |
| 35 | -#define LOG_WARNING(obj, x) \ | ||
| 36 | - if (obj->log_level >= Client::LogLevel::WARNING) \ | ||
| 37 | - { \ | ||
| 38 | - obj->logger(Client::LogLevel::WARNING, x); \ | 35 | +#define LOG_WARNING(obj, x) \ |
| 36 | + if (obj->log_level >= TrueMQTT::Client::LogLevel::WARNING) \ | ||
| 37 | + { \ | ||
| 38 | + obj->logger(TrueMQTT::Client::LogLevel::WARNING, x); \ | ||
| 39 | } | 39 | } |
| 40 | #else | 40 | #else |
| 41 | #define LOG_WARNING(obj, x) | 41 | #define LOG_WARNING(obj, x) |
| 42 | #endif | 42 | #endif |
| 43 | 43 | ||
| 44 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_INFO | 44 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_INFO |
| 45 | -#define LOG_INFO(obj, x) \ | ||
| 46 | - if (obj->log_level >= Client::LogLevel::INFO) \ | ||
| 47 | - { \ | ||
| 48 | - obj->logger(Client::LogLevel::INFO, x); \ | 45 | +#define LOG_INFO(obj, x) \ |
| 46 | + if (obj->log_level >= TrueMQTT::Client::LogLevel::INFO) \ | ||
| 47 | + { \ | ||
| 48 | + obj->logger(TrueMQTT::Client::LogLevel::INFO, x); \ | ||
| 49 | } | 49 | } |
| 50 | #else | 50 | #else |
| 51 | #define LOG_INFO(obj, x) | 51 | #define LOG_INFO(obj, x) |
| 52 | #endif | 52 | #endif |
| 53 | 53 | ||
| 54 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_DEBUG | 54 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_DEBUG |
| 55 | -#define LOG_DEBUG(obj, x) \ | ||
| 56 | - if (obj->log_level >= Client::LogLevel::DEBUG) \ | ||
| 57 | - { \ | ||
| 58 | - obj->logger(Client::LogLevel::DEBUG, x); \ | 55 | +#define LOG_DEBUG(obj, x) \ |
| 56 | + if (obj->log_level >= TrueMQTT::Client::LogLevel::DEBUG) \ | ||
| 57 | + { \ | ||
| 58 | + obj->logger(TrueMQTT::Client::LogLevel::DEBUG, x); \ | ||
| 59 | } | 59 | } |
| 60 | #else | 60 | #else |
| 61 | #define LOG_DEBUG(obj, x) | 61 | #define LOG_DEBUG(obj, x) |
| 62 | #endif | 62 | #endif |
| 63 | 63 | ||
| 64 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_TRACE | 64 | #if MIN_LOGGER_LEVEL >= LOGGER_LEVEL_TRACE |
| 65 | -#define LOG_TRACE(obj, x) \ | ||
| 66 | - if (obj->log_level >= Client::LogLevel::TRACE) \ | ||
| 67 | - { \ | ||
| 68 | - obj->logger(Client::LogLevel::TRACE, x); \ | 65 | +#define LOG_TRACE(obj, x) \ |
| 66 | + if (obj->log_level >= TrueMQTT::Client::LogLevel::TRACE) \ | ||
| 67 | + { \ | ||
| 68 | + obj->logger(TrueMQTT::Client::LogLevel::TRACE, x); \ | ||
| 69 | } | 69 | } |
| 70 | #else | 70 | #else |
| 71 | #define LOG_TRACE(obj, x) | 71 | #define LOG_TRACE(obj, x) |