From 381caad1418228afc99cfc4ea18f50079953e4b3 Mon Sep 17 00:00:00 2001 From: fpagliughi Date: Tue, 25 Jun 2024 19:52:21 -0400 Subject: [PATCH] - Added properties iterator. - Added property typeid, type name, and cleaned up constructor. - Exception checks if return code is reason code. - Added 'server_property_v5' example app. --- examples/CMakeLists.txt | 13 ++-- examples/async_publish_time.cpp | 3 +- examples/async_subscribe.cpp | 3 +- examples/server_props_v5.cpp | 117 ++++++++++++++++++++++++++++++++ include/mqtt/exception.h | 27 +++++--- include/mqtt/properties.h | 99 +++++++++++++++++++++++++-- src/properties.cpp | 109 ++++++++++++++++++++++++----- 7 files changed, 329 insertions(+), 42 deletions(-) create mode 100644 examples/server_props_v5.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index eb0e5bd..b2acebb 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -35,18 +35,19 @@ set(EXECUTABLES async_subscribe async_consume async_consume_v5 + data_publish + mqttpp_chat + multithr_pub_sub + pub_speed_test + rpc_math_cli + rpc_math_srvr + server_props_v5 sync_publish sync_consume sync_consume_v5 sync_reconnect - data_publish - rpc_math_cli - rpc_math_srvr - mqttpp_chat topic_publish - multithr_pub_sub ws_publish - pub_speed_test ) # These will only be built if SSL selected diff --git a/examples/async_publish_time.cpp b/examples/async_publish_time.cpp index fe14549..7759df3 100644 --- a/examples/async_publish_time.cpp +++ b/examples/async_publish_time.cpp @@ -130,7 +130,8 @@ int main(int argc, char* argv[]) auto top = mqtt::topic(cli, "data/time", QOS); cout << "Publishing data..." << endl; - while (timestamp() % DELTA_MS != 0); + while (timestamp() % DELTA_MS != 0) + ; uint64_t t = timestamp(), tlast = t, tstart = t; diff --git a/examples/async_subscribe.cpp b/examples/async_subscribe.cpp index 917874a..602ffa8 100644 --- a/examples/async_subscribe.cpp +++ b/examples/async_subscribe.cpp @@ -205,7 +205,8 @@ int main(int argc, char* argv[]) // Just block till user tells us to quit. - while (std::tolower(std::cin.get()) != 'q'); + while (std::tolower(std::cin.get()) != 'q') + ; // Disconnect diff --git a/examples/server_props_v5.cpp b/examples/server_props_v5.cpp new file mode 100644 index 0000000..3cbb843 --- /dev/null +++ b/examples/server_props_v5.cpp @@ -0,0 +1,117 @@ +// server_props_v5.cpp +// +// This is a Paho MQTT C++ client, sample application. +// +// This application is an MQTT client using the C++ asynchronous interface +// which shows how to check the server cofiguration for an MQTT v5 +// connection. +// +// With an MQTT v5 connection, the server specify can some features that it +// doesn't supports, or limits in some way. It does this by adding v5 +// properties to the CONNACK packet it sends back to the client in a connect +// transaction. The C++ application can retrieve these from the connect +// token via the `connect_response` object. +// +// It also shows short-lived persistent sessions. The client asks the server +// to just keep the session for 10sec. If you re-run the application in less +// than 10sec, it should report that the session exists. Any longer, and the +// session will be gone. +// +// Note that 10sec is probably *way too short* a time for real-world +// applications. This is just for demonstrating/testing the session expiry +// interval. +// +// The sample demonstrates: +// - Connecting to an MQTT v5 server/broker. +// - Specifying a short-lived (10sec) persistent session. +// - Retrieving the v5 properties from the connect response (i.e. CONNACK +// packet) +// - Iterating through v5 properties. +// - Displaying server properties to the user. +// + +/******************************************************************************* + * Copyright (c) 2013-2024 Frank Pagliughi + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Frank Pagliughi - initial implementation and documentation + *******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "mqtt/async_client.h" + +using namespace std; + +const string DFLT_SERVER_URI{"mqtt://localhost:1883"}; +const string CLIENT_ID{"server_props_v5"}; + +///////////////////////////////////////////////////////////////////////////// + +int main(int argc, char* argv[]) +{ + auto serverURI = (argc > 1) ? string{argv[1]} : DFLT_SERVER_URI; + + mqtt::async_client cli(serverURI, CLIENT_ID); + auto connOpts = mqtt::connect_options_builder::v5() + .clean_start(false) + .properties({{mqtt::property::SESSION_EXPIRY_INTERVAL, 10}}) + .finalize(); + + try { + // Connect to the server + + cout << "Connecting to the MQTT server at '" << serverURI << "'..." << flush; + auto tok = cli.connect(connOpts); + + // Getting the connect response will block waiting for the + // connection to complete. + auto rsp = tok->get_connect_response(); + cout << "OK" << endl; + + // Make sure we were granted a v5 connection. + if (rsp.get_mqtt_version() < MQTTVERSION_5) { + cout << "Did not get an MQTT v5 connection." << endl; + exit(1); + } + + // Does the server have a session for us? + cout << "\nThe session is " << (rsp.is_session_present() ? "" : "not ") + << "present on the server." << endl; + + // Show the v5 properties from the CONNACK, if any + cout << "\nConnection Properties:" << endl; + if (rsp.get_properties().empty()) { + cout << " " << endl; + } + else { + for (const auto& prop : rsp.get_properties()) { + cout << " " << prop << endl; + } + } + + // OK, we're done. + cli.disconnect()->wait(); + } + catch (const mqtt::exception& exc) { + cerr << "\n " << exc << endl; + return 1; + } + + return 0; +} diff --git a/include/mqtt/exception.h b/include/mqtt/exception.h index 04b8d3a..2616331 100644 --- a/include/mqtt/exception.h +++ b/include/mqtt/exception.h @@ -6,7 +6,7 @@ ///////////////////////////////////////////////////////////////////////////// /******************************************************************************* - * Copyright (c) 2013-2019 Frank Pagliughi + * Copyright (c) 2013-2024 Frank Pagliughi * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -54,6 +54,13 @@ protected: /** The error message from the C library */ string msg_; + /** See if the return code is actually a reason code error value */ + static ReasonCode reason_code(int rc, ReasonCode reasonCode) { + if (reasonCode == ReasonCode::SUCCESS && rc >= ReasonCode::UNSPECIFIED_ERROR) + reasonCode = ReasonCode(rc); + return reasonCode; + } + public: /** * Creates an MQTT exception. @@ -72,11 +79,7 @@ public: * @param rc The error return code from the C library. * @param msg The text message for the error. */ - exception(int rc, const string& msg) - : std::runtime_error(printable_error(rc, ReasonCode::SUCCESS, msg)), - rc_(rc), - reasonCode_(ReasonCode::SUCCESS), - msg_(msg) {} + exception(int rc, const string& msg) : exception(rc, ReasonCode::SUCCESS, msg) {} /** * Creates an MQTT exception. * @param rc The error return code from the C library. @@ -85,9 +88,9 @@ public: */ exception(int rc, ReasonCode reasonCode, const string& msg) : std::runtime_error(printable_error(rc, reasonCode, msg)), - rc_(rc), - reasonCode_(reasonCode), - msg_(msg) {} + rc_{rc}, + reasonCode_{reason_code(rc, reasonCode)}, + msg_{msg} {} /** * Gets an error message from an error code. * @param rc The error code from the C lib @@ -116,13 +119,15 @@ public: * explanation message. */ static string printable_error( - int rc, int reasonCode = ReasonCode::SUCCESS, const string& msg = string() + int rc, ReasonCode reasonCode = ReasonCode::SUCCESS, const string& msg = string() ) { + reasonCode = reason_code(rc, reasonCode); + string s = "MQTT error [" + std::to_string(rc) + "]"; if (!msg.empty()) s += string(": ") + msg; if (reasonCode != ReasonCode::SUCCESS) - s += string(". Reason: ") + reason_code_str(reasonCode); + s += string(". ") + reason_code_str(reasonCode); return s; } /** diff --git a/include/mqtt/properties.h b/include/mqtt/properties.h index 8c63efd..505722c 100644 --- a/include/mqtt/properties.h +++ b/include/mqtt/properties.h @@ -30,7 +30,10 @@ extern "C" { #include #include +#include +#include #include +#include #include "mqtt/buffer_ref.h" #include "mqtt/exception.h" @@ -56,6 +59,9 @@ class property // For string properties, this allocates memory and copied the string(s) void copy(const MQTTProperty& other); + friend class properties; + property() {} + public: /** * The integer codes for the different v5 properties. @@ -90,6 +96,9 @@ public: SHARED_SUBSCRIPTION_AVAILABLE = 42 }; + /** The names of the different types of properties */ + PAHO_MQTTPP_EXPORT static const std::map TYPE_NAME; + /** * Create a numeric property. * This can be a byte, or 2-byte, 4-byte, or variable byte integer. @@ -121,18 +130,20 @@ public: * Creates a property from a C struct. * @param cprop A C struct for a property list. */ - explicit property(const MQTTProperty& cprop); + explicit property(const MQTTProperty& cprop) { copy(cprop); } /** * Moves a C struct into this property list. * This takes ownership of any memory that the C struct is holding. * @param cprop A C struct for a property list. */ - explicit property(MQTTProperty&& cprop); + explicit property(MQTTProperty&& cprop) : prop_(cprop) { + memset(&cprop, 0, sizeof(MQTTProperty)); + } /** * Copy constructor * @param other The other property to copy into this one. */ - property(const property& other); + property(const property& other) { copy(other.prop_); } /** * Move constructor. * @param other The other property that is moved into this one. @@ -169,9 +180,16 @@ public: * Gets a printable name for the property type. * @return A printable name for the property type. */ - const char* type_name() const { return ::MQTTPropertyName(prop_.identifier); } + std::string_view type_name() const; + /** + * Gets the typeid for the value contained in the property. + * @return The typeid for the value contained in the property. + */ + const std::type_info& value_type_id(); }; +std::ostream& operator<<(std::ostream& os, const property& prop); + /** * Extracts the value from the property as the specified type. * @return The value from the property as the specified type. @@ -271,10 +289,10 @@ inline string_pair get(const property& prop) { class properties { /** The default C struct */ - PAHO_MQTTPP_EXPORT static const MQTTProperties DFLT_C_STRUCT; + static constexpr MQTTProperties DFLT_C_STRUCT MQTTProperties_initializer; /** The underlying C properties struct */ - MQTTProperties props_; + MQTTProperties props_{DFLT_C_STRUCT}; template friend T get(const properties& props, property::code propid, size_t idx); @@ -283,11 +301,58 @@ class properties friend T get(const properties& props, property::code propid); public: + /** A const iterator for the properties list */ + class const_iterator + { + const MQTTProperty* curr_; + mutable property prop_; + + friend properties; + const_iterator(const MQTTProperty* curr) : curr_{curr} {} + + public: + /** + * Gets a reference to the current value. + * @return A reference to the current value. + */ + const property& operator*() const { + prop_ = property{*curr_}; + return prop_; + } + /** + * Postfix increment operator. + * @return An iterator pointing to the previous matching item. + */ + const_iterator operator++(int) noexcept { + auto tmp = *this; + curr_++; + return tmp; + } + /** + * Prefix increment operator. + * @return An iterator pointing to the next matching item. + */ + const_iterator& operator++() noexcept { + ++curr_; + return *this; + } + /** + * Compares two iterators to see if they don't refer to the same + * node. + * + * @param other The other iterator to compare against this one. + * @return @em true if they don't match, @em false if they do + */ + bool operator!=(const const_iterator& other) const noexcept { + return curr_ != other.curr_; + } + }; + /** * Default constructor. * Creates an empty properties list. */ - properties(); + properties() {} /** * Copy constructor. * @param other The property list to copy. @@ -342,6 +407,26 @@ public: * @return The number of property items in the list. */ size_t size() const { return size_t(props_.count); } + /** + * Gets a const iterator to the full collection of properties. + * @return A const iterator to the full collection of properties. + */ + const_iterator begin() const { return const_iterator{props_.array}; } + /** + * Gets a const iterator to the full collection of properties. + * @return A const iterator to the full collection of properties. + */ + const_iterator cbegin() const { return begin(); } + /** + * Gets a const iterator to the end of the collection of properties. + * @return A const iterator to the end of collection of properties. + */ + const_iterator end() const { return const_iterator{props_.array + size()}; } + /** + * Gets a const iterator to the end of the collection of properties. + * @return A const iterator to the end of collection of properties. + */ + const_iterator cend() const { return end(); } /** * Adds a property to the list. * @param prop The property to add to the list. diff --git a/src/properties.cpp b/src/properties.cpp index d6ebd33..7af3870 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -20,6 +20,36 @@ namespace mqtt { +PAHO_MQTTPP_EXPORT const std::map property::TYPE_NAME{ + {PAYLOAD_FORMAT_INDICATOR, "PayloadFormatIndicator"}, + {MESSAGE_EXPIRY_INTERVAL, "MessageExpiryInterval"}, + {CONTENT_TYPE, "ContentType"}, + {RESPONSE_TOPIC, "ResponseTopic"}, + {CORRELATION_DATA, "CorrelationData"}, + {SUBSCRIPTION_IDENTIFIER, "SubscriptionIdentifier"}, + {SESSION_EXPIRY_INTERVAL, "SessionExpiryInterval"}, + {ASSIGNED_CLIENT_IDENTIFIER, "AssignedClientIdentifer"}, + {SERVER_KEEP_ALIVE, "ServerKeepAlive"}, + {AUTHENTICATION_METHOD, "AuthenticationMethod"}, + {AUTHENTICATION_DATA, "AuthenticationData"}, + {REQUEST_PROBLEM_INFORMATION, "RequestProblemInformation"}, + {WILL_DELAY_INTERVAL, "WillDelayInterval"}, + {REQUEST_RESPONSE_INFORMATION, "RequestResponseInformation"}, + {RESPONSE_INFORMATION, "ResponseInformation"}, + {SERVER_REFERENCE, "ServerReference"}, + {REASON_STRING, "ReasonString"}, + {RECEIVE_MAXIMUM, "ReceiveMaximum"}, + {TOPIC_ALIAS_MAXIMUM, "TopicAliasMaximum"}, + {TOPIC_ALIAS, "TopicAlias"}, + {MAXIMUM_QOS, "MaximumQos"}, + {RETAIN_AVAILABLE, "RetainAvailable"}, + {USER_PROPERTY, "UserProperty"}, + {MAXIMUM_PACKET_SIZE, "MaximumPacketSize"}, + {WILDCARD_SUBSCRIPTION_AVAILABLE, "WildcardSubscriptionAvailable"}, + {SUBSCRIPTION_IDENTIFIERS_AVAILABLE, "SubscriptionIdentifiersAvailable"}, + {SHARED_SUBSCRIPTION_AVAILABLE, "SharedSubscriptionAvailable"} +}; + ///////////////////////////////////////////////////////////////////////////// property::property(code c, int32_t val) @@ -68,15 +98,6 @@ property::property(code c, string_ref name, string_ref val) std::memcpy(prop_.value.value.data, val.data(), n); } -property::property(const MQTTProperty& cprop) { copy(cprop); } - -property::property(MQTTProperty&& cprop) : prop_(cprop) -{ - memset(&cprop, 0, sizeof(MQTTProperty)); -} - -property::property(const property& other) { copy(other.prop_); } - property::property(property&& other) { std::memcpy(&prop_, &other.prop_, sizeof(MQTTProperty)); @@ -144,16 +165,72 @@ property& property::operator=(property&& rhs) return *this; } +std::string_view property::type_name() const +{ + if (auto p = TYPE_NAME.find(code(prop_.identifier)); p != TYPE_NAME.end()) { + return p->second; + } + return std::string_view("Unknown"); +} + +const std::type_info& property::value_type_id() +{ + switch (::MQTTProperty_getType(prop_.identifier)) { + case MQTTPROPERTY_TYPE_BYTE: + return typeid(uint8_t); + case MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER: + return typeid(uint16_t); + case MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER: + case MQTTPROPERTY_TYPE_VARIABLE_BYTE_INTEGER: + return typeid(uint32_t); + case MQTTPROPERTY_TYPE_BINARY_DATA: + return typeid(binary); + case MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING: + return typeid(string); + case MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR: + return typeid(string_pair); + } + return typeid(int); +} + +std::ostream& operator<<(std::ostream& os, const property& prop) +{ + os << prop.type_name() << ": "; + + switch (::MQTTProperty_getType(MQTTPropertyCodes(prop.type()))) { + case MQTTPROPERTY_TYPE_BYTE: + case MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER: + case MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER: + case MQTTPROPERTY_TYPE_VARIABLE_BYTE_INTEGER: + os << get(prop); + break; + + case MQTTPROPERTY_TYPE_BINARY_DATA: { + auto bin = get(prop); + for (const char& by : bin) os << std::hex << unsigned(by); + os << std::dec; + } break; + + case MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING: + os << get(prop); + break; + + case MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR: + auto sp = get(prop); + os << '(' << std::get<0>(sp) << ',' << std::get<1>(sp) << ')'; + break; + } + + return os; +} + ///////////////////////////////////////////////////////////////////////////// -PAHO_MQTTPP_EXPORT const MQTTProperties properties::DFLT_C_STRUCT = - MQTTProperties_initializer; - -properties::properties() : props_{DFLT_C_STRUCT} {} - -properties::properties(std::initializer_list props) : props_{DFLT_C_STRUCT} +properties::properties(std::initializer_list props) { - for (const auto& prop : props) ::MQTTProperties_add(&props_, &prop.c_struct()); + for (const auto& prop : props) { + ::MQTTProperties_add(&props_, &prop.c_struct()); + } } properties& properties::operator=(const properties& rhs)