diff --git a/doc/Jamfile b/doc/Jamfile new file mode 100644 index 0000000..c5347cc --- /dev/null +++ b/doc/Jamfile @@ -0,0 +1,124 @@ +project async_mqtt5/doc ; + +import boostbook ; +import quickbook ; + +import os ; +local BOOST_ROOT = [ os.environ BOOST_ROOT ] ; +# echo $(BOOST_ROOT) ; + +# unfortunately, it can only import docca.jam in relative path format +import ../../../../../../../../d/boost_1_82_0/tools/docca/docca.jam ; + +local doxygen_exclussions = + detail + impl +; + +docca.reference reference.qbk + : + xsl/custom-overrides.xsl + [ glob-tree-ex ../include/async_mqtt5/ : *.hpp : $(doxygen_exclussions) ] + : + PROJECT_NAME=async_mqtt5 + PROJECT_BRIEF="MQTT C++ Client Library" + DISTRIBUTE_GROUP_DOC=YES + ENABLE_PREPROCESSING=YES + MACRO_EXPANSION=YES + EXPAND_ONLY_PREDEF=YES + SEARCH_INCLUDES=NO + "PREDEFINED=\\ + \"BOOST_ASIO_INITFN_RESULT_TYPE(t,a)=auto\" \\ + \"BOOST_ASIO_COMPLETION_TOKEN_FOR(sig)=class\" \\ + \"BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(ct,sig)=auto\" \\ + \"BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(ex)=\" \\ + \"BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(ex)=\" \\ + \"protected=private\" \\ + \"BOOST_CXX14_CONSTEXPR=constexpr\" \\ + " + SKIP_FUNCTION_MACROS=NO + OUTPUT_LANGUAGE=English + INLINE_INHERITED_MEMB=YES + AUTOLINK_SUPPORT=NO + BRIEF_MEMBER_DESC=NO + EXTRACT_ALL=YES + HAVE_DOT=NO + HIDE_UNDOC_MEMBERS=YES + HIDE_UNDOC_CLASSES=YES + HIDE_FRIEND_COMPOUNDS=YES + CASE_SENSE_NAMES=YES + SHOW_INCLUDE_FILES=NO + INLINE_INFO=NO + SORT_MEMBER_DOCS=NO + SORT_MEMBERS_CTORS_1ST=YES + SHOW_USED_FILES=NO + SHOW_FILES=NO + SHOW_NAMESPACES=NO + QUIET=NO +; + +install stylesheets + : + $(BOOST_ROOT)/doc/src/boostbook.css + : + html/ + ; + +explicit stylesheets ; + +install callouts + : + [ glob $(BOOST_ROOT)/doc/src/images/callouts/*.png ] + : + html/images/callouts + ; + +explicit callout ; + +install images + : + [ glob $(BOOST_ROOT)/doc/src/images/*.png ] + : + html/images + ; + +explicit images ; + +xml async_mqtt5_doc + : + qbk/00_main.qbk + : + reference.qbk + images + ; + +explicit async_mqtt5_doc ; + +boostbook async_mqtt5 + : + async_mqtt5_doc + : + "boost.root=https://www.boost.org/doc/libs/1_82_0" + boost.graphics.root=images/ + nav.layout=none + chapter.autolabel=1 + chunk.section.depth=8 + chunk.first.sections=1 + toc.section.depth=8 + toc.max.depth=8 + generate.toc="chapter toc,title section nop reference nop part toc" + html.stylesheet=boostbook.css + $(BOOST_ROOT)tools/boostbook/dtd + : + stylesheets + images + ; + +# These are used to inform the build system of the +# means to build the integrated and stand-alone docs. + +alias boostdoc ; +explicit boostdoc ; + +alias boostrelease : async_mqtt5 ; +explicit boostrelease ; diff --git a/doc/qbk/00_main.qbk b/doc/qbk/00_main.qbk new file mode 100644 index 0000000..65a3384 --- /dev/null +++ b/doc/qbk/00_main.qbk @@ -0,0 +1,53 @@ +[library async_mqtt5 + [quickbook 1.7] + [copyright 2023 Mireo] + [id async_mqtt5] + [purpose MQTT C++ client library] + [license + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + [@http://www.boost.org/LICENSE_1_0.txt]) + ] + [authors [A,B,C]] + [category template] + [category generic] +] + +[template nochunk[] [block '''''']] +[template mdash[] '''— '''] +[template include_file[path][^<''''''[path]''''''>]] +[template indexterm1[term1] ''''''[term1]''''''] +[template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] + +[template reflink2[id text][link async_mqtt5.ref.async_mqtt5__[id] [^[text]]]] +[template reflink[id][reflink2 [id] [id]]] +[template refmem[class mem][reflink2 [class].[mem] [class]::[mem]]] +[template refmemunq[class mem][reflink2 [class].[mem] [mem]]] +[template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^boost::asio::[term]]]] +[template beastreflink[id term][@boost:/libs/beast/doc/html/beast/ref/[id].html [^boost::beast::[term]]]] +[template beastconceptslink[id term][@boost:/libs/beast/doc/html/beast/concepts/[id].html [term]]] + +[def __ExecutionContext__ [reflink2 ExecutionContext ['ExecutionContext]]] +[def __StreamType__ [reflink2 StreamType ['StreamType]]] +[def __TlsContext__ [reflink2 TlsContext ['TlsContext]]] + +[def __Boost__ [@https://www.boost.org/ Boost]] +[def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]] +[def __Self__ [async_mqtt5]] + +[/ MQTT stuff] +[def __MQTT__ [@https://mqtt.org/ MQTT]] + +[include 01_intro.qbk] + +[section:ref Reference] +[xinclude helpers/quickref.xml] +[block''''''] +[include reference.qbk] +[include helpers/ExecutionContext.qbk] +[include helpers/StreamType.qbk] +[include helpers/TlsContext.qbk] +[include helpers/Reason_codes.qbk] +[block''''''] +[endsect] + diff --git a/doc/qbk/01_intro.qbk b/doc/qbk/01_intro.qbk new file mode 100644 index 0000000..a2a74ab --- /dev/null +++ b/doc/qbk/01_intro.qbk @@ -0,0 +1,45 @@ +[section:intro Introduction] +[nochunk] + +__Self__ is a C++ client for the __MQTT__ protocol, based on __Asio__. + +[heading Motivation] + +[...motivation here...] + +The design goals of this library are: + +* reason 1 +* reason 2 +* ... + +Non-goals: + +* reason 1 +* reason 2 +* ... + +[heading When to use] + +If any of the following statements is true, you may consider using __Self__: + +* reason 1 +* reason 2 +* ... + +It may not be a good fit for you if: + +* reason 1 +* reason 2 +* ... + + +[heading Tested compilers and systems] + +[...some text here...] + +[heading Acknowledgements] + +[...some acknowledgements here...] + +[endsect] [/intro] diff --git a/doc/qbk/helpers/ExecutionContext.qbk b/doc/qbk/helpers/ExecutionContext.qbk new file mode 100644 index 0000000..e3cf4b4 --- /dev/null +++ b/doc/qbk/helpers/ExecutionContext.qbk @@ -0,0 +1,16 @@ +[/ + Copyright (c) 2023 Mireo + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:async_mqtt5__ExecutionContext ExecutionContext concept] + +`ExecutionContext` represents a place where function objects will be executed. + +`ExecutionContext` type must be convertible to [asioreflink execution_context execution_context] which +is a base class for concrete execution context types. +The [asioreflink io_context io_context] type is an example of a derived type. + +[endsect] \ No newline at end of file diff --git a/doc/qbk/helpers/Reason_codes.qbk b/doc/qbk/helpers/Reason_codes.qbk new file mode 100644 index 0000000..9d08074 --- /dev/null +++ b/doc/qbk/helpers/Reason_codes.qbk @@ -0,0 +1,114 @@ +[/ + Copyright (c) 2023 Mireo + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + + +[section:async_mqtt5__Reason_codes Reason codes] + +This section lists all possible reason codes that Broker can send in a +MQTT Control Packet. + +Reason codes indicating successful completion of an operation: + +[reflink2 reason_codes__success success] + +[reflink2 reason_codes__normal_disconnection normal_disconnection] + +[reflink2 reason_codes__granted_qos_0 granted_qos_0] + +[reflink2 reason_codes__granted_qos_1 granted_qos_1] + +[reflink2 reason_codes__granted_qos_2 granted_qos_2] + +[reflink2 reason_codes__disconnect_with_will_message disconnect_with_will_message] + +[reflink2 reason_codes__no_matching_subscribers no_matching_subscribers] + +[reflink2 reason_codes__no_subscription_existed no_subscription_existed] + +[reflink2 reason_codes__continue_authentication continue_authentication] + +[reflink2 reason_codes__reauthenticate reauthenticate] + + +Reason codes indicating an error during packet exchange: + +[reflink2 reason_codes__unspecified_error unspecified_error] + +[reflink2 reason_codes__malformed_packet malformed_packet] + +[reflink2 reason_codes__protocol_error protocol_error] + +[reflink2 reason_codes__implementation_specific_error implementation_specific_error] + +[reflink2 reason_codes__unsupported_protocol_version unsupported_protocol_version] + +[reflink2 reason_codes__client_id_not_valid client_id_not_valid] + +[reflink2 reason_codes__bad_username_or_password bad_username_or_password] + +[reflink2 reason_codes__not_authorized not_authorized] + +[reflink2 reason_codes__server_unavailable server_unavailable] + +[reflink2 reason_codes__server_busy server_busy] + +[reflink2 reason_codes__banned banned] + +[reflink2 reason_codes__server_shutting_down server_shutting_down] + +[reflink2 reason_codes__bad_authentication_method bad_authentication_method] + +[reflink2 reason_codes__keep_alive_timeout keep_alive_timeout] + +[reflink2 reason_codes__session_taken_over session_taken_over] + +[reflink2 reason_codes__topic_filter_invalid topic_filter_invalid] + +[reflink2 reason_codes__topic_name_invalid topic_name_invalid] + +[reflink2 reason_codes__packet_id_in_use packet_id_in_use] + +[reflink2 reason_codes__packet_id_not_found packet_id_not_found] + +[reflink2 reason_codes__receive_maximum_exceeded receive_maximum_exceeded] + +[reflink2 reason_codes__topic_alias_invalid topic_alias_invalid] + +[reflink2 reason_codes__packet_too_large packet_too_large] + +[reflink2 reason_codes__message_rate_too_high message_rate_too_high] + +[reflink2 reason_codes__quota_exceeded quota_exceeded] + +[reflink2 reason_codes__administrative_action administrative_action] + +[reflink2 reason_codes__payload_format_invalid payload_format_invalid] + +[reflink2 reason_codes__retain_not_supported retain_not_supported] + +[reflink2 reason_codes__qos_not_supported qos_not_supported] + +[reflink2 reason_codes__use_another_server use_another_server] + +[reflink2 reason_codes__server_moved server_moved] + +[reflink2 reason_codes__shared_subscriptions_not_supported shared_subscriptions_not_supported] + +[reflink2 reason_codes__connection_rate_exceeded connection_rate_exceeded] + +[reflink2 reason_codes__maximum_connect_time maximum_connect_time] + +[reflink2 reason_codes__subscription_ids_not_supported subscription_ids_not_supported] + +[reflink2 reason_codes__wildcard_subscriptions_not_supported wildcard_subscriptions_not_supported] + + +Special reason code: + +[reflink2 reason_codes__empty empty] + +[endsect] \ No newline at end of file diff --git a/doc/qbk/helpers/StreamType.qbk b/doc/qbk/helpers/StreamType.qbk new file mode 100644 index 0000000..efd20de --- /dev/null +++ b/doc/qbk/helpers/StreamType.qbk @@ -0,0 +1,23 @@ +[/ + Copyright (c) 2023 Mireo + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:async_mqtt5__StreamType StreamType concept] + +`StreamType` represents the transport protocol type used to transfer stream of bytes. + +`StreamType` should meet the [beastconceptslink streams Stream] concept. + +Additionally, it should follow Asio's layered stream model by having a `lowest_layer_type` member type, +and a `lowest_layer` member function, returing a `lowest_layer_type&`. +The `lowest_layer_type` should inherit from [asioreflink basic_stream_socket basic_stream_socket]. + +The types [asioreflink basic_stream_socket basic_stream_socket], +[asioreflink ssl__stream ssl::stream], and +[beastreflink boost__beast__websocket__stream websocket::stream] +meet these requirements. + +[endsect] \ No newline at end of file diff --git a/doc/qbk/helpers/TlsContext.qbk b/doc/qbk/helpers/TlsContext.qbk new file mode 100644 index 0000000..5491f21 --- /dev/null +++ b/doc/qbk/helpers/TlsContext.qbk @@ -0,0 +1,14 @@ +[/ + Copyright (c) 2023 Mireo + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:async_mqtt5__TlsContext TlsContext concept] + +// TODO + +The type [asioreflink ssl__context ssl::context] meets these requirements. + +[endsect] \ No newline at end of file diff --git a/doc/qbk/helpers/quickref.xml b/doc/qbk/helpers/quickref.xml new file mode 100644 index 0000000..979e2e7 --- /dev/null +++ b/doc/qbk/helpers/quickref.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + Classes + + authority_path + mqtt_client + reason_code + subscribe_options + subscribe_topic + will + + + + Type aliases + + error_code + + Concepts + + ExecutionContext + StreamType + TlsContext + + + + Enumerations + + client_error + disconnect_rc_e + qos_e + retain_e + + Constants + Reason codes + + + + + Functions + + get_error_code_category + make_error_code + + + + + diff --git a/doc/qbk/helpers/rcref.xml b/doc/qbk/helpers/rcref.xml new file mode 100644 index 0000000..7ea5058 --- /dev/null +++ b/doc/qbk/helpers/rcref.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + Success + + success + normal_disconnection + granted_qos_0 + granted_qos_1 + granted_qos_2 + disconnect_with_will_message + no_matching_subscribers + no_subscription_existed + continue_authentication + + + + Error + + unspecified_error + malformed_packet + protocol_error + implementation_specific_error + unsupported_protocol_version + client_id_not_valid + bad_username_or_password + not_authorized + server_unavailable + server_busy + banned + server_shutting_down + bad_authentication_method + keep_alive_timeout + session_taken_over + topic_filter_invalid + topic_name_invalid + packet_id_in_use + packet_id_not_found + receive_maximum_exceeded + topic_alias_invalid + packet_too_large + message_rate_too_high + quota_exceeded + administrative_action + payload_format_invalid + retain_not_supported + qos_not_supported + use_another_server + server_moved + shared_subscriptions_not_supported + connection_rate_exceeded + maximum_connect_time + subscription_ids_not_supported + wildcard_subscriptions_not_supported + + Special + + empty + + + + + diff --git a/doc/xsl/custom-overrides.xsl b/doc/xsl/custom-overrides.xsl new file mode 100644 index 0000000..d78f541 --- /dev/null +++ b/doc/xsl/custom-overrides.xsl @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + {$nl} + Convenience header [include_file {$convenience-header}] + {$nl} + + + + async_mqtt5.hpp + + + + + + diff --git a/include/async_mqtt5/error.hpp b/include/async_mqtt5/error.hpp index ce01623..4e23218 100644 --- a/include/async_mqtt5/error.hpp +++ b/include/async_mqtt5/error.hpp @@ -7,11 +7,21 @@ #include namespace async_mqtt5 { - - +/** + * \brief A representation of Disconnect Reason Code. + * + * \details Represents all Reason Codes that the Client can send to the Server + * in the DISCONNECT packet as the reason for the disconnection. + */ enum class disconnect_rc_e : std::uint8_t { + /// Close the connection normally. Do not send the \ref will Message. normal_disconnection = 0x00, + + /// The Client wishes to disconnect but requires that + /// the Server also publishes its \ref will Message. disconnect_with_will_message = 0x04, + + // TODO: these reason codes should not be available to the user, only the client unspecified_error = 0x80, malformed_packet = 0x81, protocol_error = 0x82, @@ -27,18 +37,35 @@ enum class disconnect_rc_e : std::uint8_t { }; namespace client { - - +/** + * \brief MQTT client error codes. + * + * \details Represents error that occur on the client side. + */ enum class error : int { + /// [unused] A fatal error occurred. fatal_error = 100, + + /// Malformed packet has been detected. malformed_packet, + + /// There are no more available Packet Identifiers to use. pid_overrun, + + /// [unused] The Client has reconnected. reconnected, + + /// The Client has been disconnected. disconnected, - // publish + // publish + /// The Server does not support the specified \ref qos_e. qos_not_supported, + + /// The Server dos not support retained messages. retain_not_available, + + /// The Client attempted to send a Topic Alias that is greater than Topic Alias Maximum. topic_alias_maximum_reached }; @@ -50,9 +77,9 @@ inline std::string client_error_to_string(error err) { case fatal_error: return "A fatal error occurred"; case malformed_packet: - return "Malformed packet has been received"; + return "Malformed packet has been detected"; case pid_overrun: - return "Ran out of the available packet ids to use"; + return "There are no more available Packet Identifiers to use."; case reconnected: return "The Client has reconnected"; case disconnected: @@ -100,11 +127,21 @@ enum class category : uint8_t { } // end namespace reason_codes - +/** + * \brief A class holding Reason Code values originating from Control Packets. + * + * \details A Reason Code is a one byte unsigned value that indicates the result of an operation. + * Reason Codes less than 0x80 indicate successful completion of an operation. + * The normal Reason Code for success is 0. + * Reason Code values of 0x80 or greater indicate failure. + * The CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, DISCONNECT and AUTH Control Packets have a single Reason Code as part of the Variable Header. + * The SUBACK and UNSUBACK packets contain a list of one or more Reason Codes in the Payload. + */ class reason_code { uint8_t _code; reason_codes::category _category { reason_codes::category::none }; public: +/// @cond INTERNAL constexpr reason_code() : _code(0xff) {} constexpr reason_code(uint8_t code, reason_codes::category cat) @@ -112,31 +149,49 @@ public: {} constexpr reason_code(uint8_t code) : _code(code) {} +/// @endcond + /// Copy constructor. reason_code(const reason_code&) = default; + + /// Move constructor. reason_code(reason_code&&) = default; + /** + * \brief Indication if the object holds a Reason Code indicating an error. + * + * \details Any Reason Code holding a value equal or greater than 0x80. + */ explicit operator bool() const noexcept { return _code >= 0x80; } + /** + * \brief Returns the byte value of the Reason Code. + */ constexpr uint8_t value() const noexcept { return _code; } + /// Insertion operator. friend std::ostream& operator<<(std::ostream& os, const reason_code& rc) { os << rc.message(); return os; } + /// Operator less than. friend bool operator<(const reason_code& lhs, const reason_code& rhs) { return lhs._code < rhs._code; } + /// Equality operator. friend bool operator==(const reason_code& lhs, const reason_code& rhs) { return lhs._code == rhs._code && lhs._category == rhs._category; } + /** + * \brief Returns a message describing the meaning behind the Reason Code. + */ std::string message() const { switch (_code) { case 0x00: diff --git a/include/async_mqtt5/mqtt_client.hpp b/include/async_mqtt5/mqtt_client.hpp index 9be13fa..2434c6b 100644 --- a/include/async_mqtt5/mqtt_client.hpp +++ b/include/async_mqtt5/mqtt_client.hpp @@ -16,12 +16,21 @@ namespace async_mqtt5 { namespace asio = boost::asio; +/** + * \brief MQTT client used to connect and communicate with a Broker. + * + * \tparam StreamType Type of the underlying transport protocol used to transfer + * the stream of bytes between the Client and the Broker. The transport must be + * ordered and lossless. + * \tparam TlsContext Type of the context object used in TLS/SSL connections. + */ template < typename StreamType, typename TlsContext = std::monostate > class mqtt_client { public: + /// The executor type associated with the client. using executor_type = typename StreamType::executor_type; private: using stream_type = StreamType; @@ -36,7 +45,13 @@ private: clisvc_ptr _svc_ptr; public: - + /** + * \brief Constructs a Client with given parameters. + * + * \param ex An executor that will be associated with the Client. + * \param cnf + * \param tls_context A context object used in TLS/SLL connection. + */ explicit mqtt_client( const executor_type& ex, const std::string& cnf, @@ -47,6 +62,17 @@ public: )) {} + /** + * \brief Constructs a client with given parameters. + * + * \tparam ExecutionContext Type of a concrete execution context. + * \param context Execution context whose executor will be associated with the Client. + * \param cnf + * \param tls_context A context object used in TLS/SLL connection. + * + * \par Precondition + * `std::is_convertible_v` + */ template requires (std::is_convertible_v) explicit mqtt_client( @@ -57,19 +83,37 @@ public: mqtt_client(context.get_executor(), cnf, std::move(tls_context)) {} + /** + * \brief Destructor. + * + * \details Automatically calls \ref mqtt_client::cancel. + */ ~mqtt_client() { cancel(); } + /** + * \brief Get the executor associated with the object. + */ executor_type get_executor() const noexcept { return _svc_ptr->get_executor(); } + + /** + * \brief Get the context object used in TLS/SSL connection. + * + * \par Precondition + * `!std::is_same_v`. + */ decltype(auto) tls_context() requires (!std::is_same_v) { return _svc_ptr->tls_context(); } + /** + * \brief Start the Client. + */ void run() { _svc_ptr->open_stream(); detail::ping_op { _svc_ptr } @@ -78,17 +122,34 @@ public: detail::sentry_op { _svc_ptr }.perform(); } + // TODO: channel cancel + /** + * \brief Cancel all asynchronous operations. + * + * \details All outstanding operations will complete with `boost::asio::error::operation_aborted`. + */ void cancel() { get_executor().execute([svc_ptr = _svc_ptr]() { svc_ptr->cancel(); }); } + /** + * \brief Assign a \ref will Message. + * + * \details The \ref will Message that will be published after the Network Connection is closed. + */ mqtt_client& will(will will) { _svc_ptr->will(std::move(will)); return *this; } + /** + * \brief Assign credentials that will be used to connect to a Broker. + * + * \details Credentials consist of a unique Client Identifier and, optionally, + * a User Name and Password. + */ mqtt_client& credentials( std::string client_id, std::string username = "", std::string password = "" @@ -100,11 +161,76 @@ public: return *this; } + /** + * \brief Assign a list of Brokers that the Client will attempt to connect to. + * + * \param hosts List of Broker addresses and ports. + * Address and ports are separated with a colon `:` while + * pairs of addresses and ports are separated with a comma `,`. + * \param default_port Default port to connect to in case the port is not + * explicitly specified in the hosts list. + * + * \details Examples of a valid hosts string:\n + * - broker1:1883, broker2, broker3:1883\n + * - broker1 + */ mqtt_client& brokers(std::string hosts, uint16_t default_port = 1883) { _svc_ptr->brokers(std::move(hosts), default_port); return *this; } + + // TODO: doc props + /** + * \brief Send a PUBLISH packet to Broker to transport an + * Application Message. + * + * \tparam qos_type The \ref qos_e level of assurance for delivery. + * \param topic Identification of the information channel to which + * Payload data is published. + * \param payload The Application Message that is being published. + * \param retain The \ref retain_e flag. + * \param props PUBLISH properties. + * \param token Completion token that will be used to produce a + * completion handler, which will be called when the operation completed. + * + * \par Completion signature + * The completion signature for this operation depends on the \ref qos_e specified:\n + * + * - `qos_e::at_most_once`: + * \code + * void ( + * boost::system::error_code // Result of operation + * ) + * \endcode + * + * - `qos_e::at_least_once`: + * \code + * void ( + * boost::system::error_code, // Result of operation. + * async_mqtt5::reason_code, // Reason Code received from Broker. + * puback_props // Properties received in the PUBACK packet. + * ) + * \endcode + * + * - `qos_e::exactly_once`: + * \code + * void ( + * boost::system::error_code, // Result of operation. + * async_mqtt5::reason_code, // Reason Code received from Broker. + * pubcomp_props // Properties received in the PUBCOMP packet. + * ) + * \endcode + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::operation_aborted` \n + * - \link client::error::pid_overrun \endlink + * - \link client::error::qos_not_supported \endlink + * - \link client::error::retain_not_available \endlink + * - \link client::error::topic_alias_maximum_reached \endlink + */ template decltype(auto) async_publish( std::string topic, std::string payload, @@ -133,6 +259,39 @@ public: ); } + // TODO: perhaps there is a way to not copy documentation (\copybrief, \copydetails) + /** + * \brief Send a SUBSCRIBE packet to Broker to create a subscription + * to one or more Topics of interest. + * + * \details After the subscription has been established, the Broker will send + * PUBLISH packets to the Client to forward Application Messages that were published + * to Topics that the Client subscribed to. The PUBLISH packets can be received + * with \ref mqtt_client::async_receive function. + * + * \param topics A list of \ref subscribe_topic of interest. + * \param props SUBSCRIBE properties. + * \param token Completion token that will be used to produce a + * completion handler, which will be called when the operation completed. + * + * \par Completion signature + * The completion signature for this operation: + * \code + * void ( + * boost::system::error_code, // Result of operation. + * std::vector, // Vector of Reason Codes indicating + * // the subscription result for each Topic + * // in the SUBSCRIBE packet. + * suback_props, // Properties received in the SUBACK packet. + * ) + * \endcode + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::operation_aborted` \n + * - \link client::error::pid_overrun \endlink + */ template decltype(auto) async_subscribe( const std::vector& topics, @@ -156,6 +315,38 @@ public: ); } + /** + * \brief Send a SUBSCRIBE packet to Broker to create a subscription + * to one Topics of interest. + * + * \details After the subscription has been established, the Broker will send + * PUBLISH packets to the Client to forward Application Messages that were published + * to Topics that the Client subscribed to. The PUBLISH packets can be received + * with \ref mqtt_client::async_receive function. + * + * \param topic \ref subscribe_topic of interest. + * \param props SUBSCRIBE properties. + * \param token Completion token that will be used to produce a + * completion handler, which will be called when the operation completed. + * + * \par Completion signature + * The completion signature for this operation: + * \code + * void ( + * boost::system::error_code, // Result of operation. + * std::vector, // Vector of Reason Codes containing the + * // single subscription result for the Topic + * // in the SUBSCRIBE packet. + * suback_props, // Properties received in the SUBACK packet. + * ) + * \endcode + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::operation_aborted` \n + * - \link client::error::pid_overrun \endlink + */ template decltype(auto) async_subscribe( const subscribe_topic& topic, const subscribe_props& props, @@ -167,6 +358,38 @@ public: ); } + + /** + * \brief Send an UNSUBSCRIBE packet to Broker to unsubscribe from one + * or more Topics. + * + * \note The Client MAY receive PUBLISH packets with Application + * Messages from Topics the Client just unsubscribed to if + * they were buffered for delivery on the Broker side beforehand. + * + * \param topics List of Topics to unsubscribe from. + * \param props UNSUBSCRIBE properties. + * \param token Completion token that will be used to produce a + * completion handler, which will be called when the operation completed. + * + * \par Completion signature + * The completion signature for this operation: + * \code + * void ( + * boost::system::error_code, // Result of operation. + * std::vector, // Vector of Reason Codes indicating + * // the result of unsubscribe operation + * // for each Topic in the UNSUBSCRIBE packet. + * unsuback_props, // Properties received in the UNSUBACK packet. + * ) + * \endcode + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::operation_aborted` \n + * - \link client::error::pid_overrun \endlink + */ template decltype(auto) async_unsubscribe( const std::vector& topics, const unsubscribe_props& props, @@ -190,6 +413,37 @@ public: ); } + /** + * \brief Send an UNSUBSCRIBE packet to Broker to unsubscribe + * from one Topic. + * + * \note The Client MAY receive PUBLISH packets with Application + * Messages from Topics the Client just unsubscribed to if + * they were buffered for delivery on the Broker side beforehand. + * + * \param topic Topic to unsubscribe from. + * \param props UNSUBSCRIBE properties. + * \param token Completion token that will be used to produce a + * completion handler, which will be called when the operation completed. + * + * \par Completion signature + * The completion signature for this operation: + * \code + * void ( + * boost::system::error_code, // Result of operation. + * std::vector, // Vector of Reason Codes containing + * // the result of unsubscribe operation + * // for the Topic in the UNSUBSCRIBE packet. + * unsuback_props, // Properties received in the UNSUBACK packet. + * ) + * \endcode + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::operation_aborted` \n + * - \link client::error::pid_overrun \endlink + */ template decltype(auto) async_unsubscribe( const std::string& topic, const unsubscribe_props& props, @@ -201,6 +455,37 @@ public: ); } + /** + * \brief Asynchronously receive an Application Message. + * + * \details The Client will receive and complete deliveries for all the + * PUBLISH packets received from the Broker throughout its lifetime. + * The Client will store them internally in order they were delivered. + * Calling this function will attempt to receive an Application Message + * from internal storage. + * + * \note The completion handler will be only invoked if an error occurred + * or there is a pending Application Message. + * + * \param token Completion token that will be used to produce a + * completion handler, which will be called when the operation completed. + * + * \par Completion signature + * The completion signature for this operation: + * \code + * void ( + * boost::system::error_code, // Result of operation. + * std::string, // Topic, the origin of the Application Message. + * std::string, // Payload, the content of the Application Message. + * publish_props, // Properties received in the PUBLISH packet. + * ) + * \endcode + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::experimental::error::channel_cancelled` + */ template decltype(auto) async_receive(CompletionToken&& token) { // Sig = void (error_code, std::string, std::string, publish_props) @@ -209,6 +494,33 @@ public: ); } + /** + * \brief Disconnect the Client. + * + * \details Send a DISCONNECT packet to the Broker with a Reason Code + * describing the reason for disconnection. + * + * \note This function has terminal effects and will close the Client. + * + * \param reason_code Reason Code to notify + * the Broker of the reason for disconnection. + * \param props DISCONNECT properties. + * \param token Completion token that will be used to produce a + * completion handler, which will be called when the operation completed. + * + * \par Completion signature + * The completion signature for this operation: + * \code + * void ( + * boost::system::error_code // Result of operation. + * ) + * \endcode + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::error::try_again` -- why? + */ template decltype(auto) async_disconnect( disconnect_rc_e reason_code, const disconnect_props& props, @@ -220,6 +532,31 @@ public: ); } + /** + * \brief Disconnect the Client. + * + * \details Send a DISCONNECT packet to the Broker with a Reason Code + * \ref reason_codes::normal_disconnection describing + * the reason for disconnection. + * + * \note This function has terminal effects and will close the Client. + * + * \param token Completion token that will be used to produce a + * completion handler, which will be called when the operation completed. + * + * \par Completion signature + * The completion signature for this operation: + * \code + * void ( + * boost::system::error_code // Result of operation. + * ) + * \endcode + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::error::try_again` -- why? + */ template decltype(auto) async_disconnect(CompletionToken&& token) { return async_disconnect( diff --git a/include/async_mqtt5/types.hpp b/include/async_mqtt5/types.hpp index cc3441c..bab4019 100644 --- a/include/async_mqtt5/types.hpp +++ b/include/async_mqtt5/types.hpp @@ -11,52 +11,127 @@ namespace async_mqtt5 { +/// An alias for `boost::system::error_code`; using error_code = boost::system::error_code; struct authority_path { std::string host, port, path; }; +/** + * \brief Represents the Quality of Service (QoS) property of the PUBLISH packets. + * + * \details Determines how the PUBLISH packets are delivered from the sender + * to the receiver. + */ enum class qos_e : std::uint8_t { + /// The message arrives at the receiver either once or not at all. at_most_once = 0b00, + + /// Ensures the message arrives at the receiver at least once. at_least_once = 0b01, + + /// All messages arrive at the receiver exactly once without + /// loss or duplication of the messages. exactly_once = 0b10 }; +/** + * \brief Represents the RETAIN flag in the PUBLISH packets. + * + * \details This flag informs the Server about whether or not it should + * store the current message. + */ enum class retain_e : std::uint8_t { - yes = 0b1, no = 0b0, + /// The Server will replace any existing retained message for this Topic + /// with this message. + yes = 0b1, + + /// The Server will not store this message and will not remove or replace + /// any existing retained message. + no = 0b0 }; +// TODO: should this be moved to internal types? enum class dup_e : std::uint8_t { - yes = 0b1, no = 0b0, + yes = 0b1, no = 0b0 }; +/** + * \brief Represents the Options associated with each Subscription. + */ struct subscribe_options { + /** + * \brief Representation of the No Local Subscribe Option. + * + * \details A Subscribe Option indicating whether or not Application Messages + * will be forwarded to a connection with a ClientID equal to the ClientID of the + * publishing connection. + */ enum class no_local_e : std::uint8_t { + /// Application Messages can be forwarded to a connection with equal ClientID. no = 0b0, - yes = 0b1, + + /// Application Messages MUST NOT be forwarded to a connection with equal ClientID. + yes = 0b1 }; + /** + * \brief Representation of the Retain As Published Subscribe Option. + * + * \details A Subscribe Option indicating whether or not Application Messages forwarded + * using this subscription keep the \ref retain_e flag they were published with. + */ enum class retain_as_published_e : std::uint8_t { + /// Application Messages have the \ref retain_e flag set to 0. dont = 0b0, - retain = 0b1, + + /// Application Messages keep the \ref retain_e flag they were published with. + retain = 0b1 }; + /** + * \brief Representation of the Retain Handling Subscribe Option. + * + * \details A Subscribe Option specifying whether retained messages are sent + * when the subscription is established. + */ enum class retain_handling_e : std::uint8_t { + /// Send retained messages at the time of subscribe. send = 0b00, + + /// Send retained message only if the subscription does not currently exist. new_subscription_only = 0b01, - not_send = 0b10, + + /// Do not send retained messages at the time of subscribe. + not_send = 0b10 }; + //TODO: figure out how to properly link qos_e to link to its page + + /// Maximum \ref qos_e level at which the Server can send Application Messages to the Client. qos_e max_qos = qos_e::exactly_once; + + /// Option determining if Application Messages will be forwarded to a connection with an equal ClientID. no_local_e no_local = no_local_e::yes; + + /// Option determining if Application Message will keep their \ref retain_e flag. retain_as_published_e retain_as_published = retain_as_published_e::retain; + + /// Option determining if retained messages are sent when the subscription is established. retain_handling_e retain_handling = retain_handling_e::new_subscription_only; }; +/** + * \brief A representation of a Topic Subscription consisting of a Topic Filter and + * Subscribe Options. + */ struct subscribe_topic { + /// An UTF-8 Encoded String indicating the Topics to which the Client wants to subscribe. std::string topic_filter; + + /// \ref subscribe_options associated with the subscription. subscribe_options sub_opts; };