diff options
| author | robot-contrib <[email protected]> | 2025-05-14 16:50:13 +0300 |
|---|---|---|
| committer | robot-contrib <[email protected]> | 2025-05-14 17:05:53 +0300 |
| commit | 7b6ce38ca5dde1bd2ab16ffa714a2a3d8c4ebe7f (patch) | |
| tree | 728bb5e8eb7e4d7de275d53687a119354e1e55e6 | |
| parent | b78b05b5986a1525f0dc69dcc0887cd7e855a31c (diff) | |
Update contrib/restricted/aws/aws-c-mqtt to 0.10.4
commit_hash:27ca94d1c91767f620f86a6e9a6af618b0771fdf
37 files changed, 6392 insertions, 1257 deletions
diff --git a/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/devtools.copyrights.report b/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/devtools.copyrights.report index e3ea41760ee..fb8c16b3fff 100644 --- a/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/devtools.copyrights.report +++ b/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/devtools.copyrights.report @@ -47,8 +47,11 @@ BELONGS ya.make include/aws/mqtt/exports.h [4:4] include/aws/mqtt/mqtt.h [5:5] include/aws/mqtt/private/client_impl.h [5:5] + include/aws/mqtt/private/client_impl_shared.h [5:5] include/aws/mqtt/private/fixed_header.h [5:5] + include/aws/mqtt/private/mqtt311_decoder.h [5:5] include/aws/mqtt/private/mqtt_client_test_helper.h [4:4] + include/aws/mqtt/private/mqtt_subscription_set.h [2:2] include/aws/mqtt/private/packets.h [5:5] include/aws/mqtt/private/shared_constants.h [2:2] include/aws/mqtt/private/topic_tree.h [5:5] @@ -57,6 +60,7 @@ BELONGS ya.make include/aws/mqtt/private/v5/mqtt5_decoder.h [2:2] include/aws/mqtt/private/v5/mqtt5_encoder.h [2:2] include/aws/mqtt/private/v5/mqtt5_options_storage.h [5:5] + include/aws/mqtt/private/v5/mqtt5_to_mqtt3_adapter_impl.h [2:2] include/aws/mqtt/private/v5/mqtt5_topic_alias.h [2:2] include/aws/mqtt/private/v5/mqtt5_utils.h [5:5] include/aws/mqtt/private/v5/rate_limiters.h [2:2] @@ -66,8 +70,11 @@ BELONGS ya.make include/aws/mqtt/v5/mqtt5_types.h [5:5] source/client.c [2:2] source/client_channel_handler.c [2:2] + source/client_impl_shared.c [2:2] source/fixed_header.c [2:2] source/mqtt.c [2:2] + source/mqtt311_decoder.c [2:2] + source/mqtt_subscription_set.c [2:2] source/packets.c [2:2] source/shared_constants.c [2:2] source/topic_tree.c [2:2] @@ -77,6 +84,7 @@ BELONGS ya.make source/v5/mqtt5_encoder.c [2:2] source/v5/mqtt5_listener.c [2:2] source/v5/mqtt5_options_storage.c [2:2] + source/v5/mqtt5_to_mqtt3_adapter.c [2:2] source/v5/mqtt5_topic_alias.c [2:2] source/v5/mqtt5_types.c [2:2] source/v5/mqtt5_utils.c [2:2] diff --git a/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/devtools.licenses.report b/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/devtools.licenses.report index 5e96c1ab427..6fc0570ab0d 100644 --- a/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/devtools.licenses.report +++ b/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/devtools.licenses.report @@ -96,8 +96,11 @@ BELONGS ya.make include/aws/mqtt/exports.h [5:5] include/aws/mqtt/mqtt.h [6:6] include/aws/mqtt/private/client_impl.h [6:6] + include/aws/mqtt/private/client_impl_shared.h [6:6] include/aws/mqtt/private/fixed_header.h [6:6] + include/aws/mqtt/private/mqtt311_decoder.h [6:6] include/aws/mqtt/private/mqtt_client_test_helper.h [5:5] + include/aws/mqtt/private/mqtt_subscription_set.h [3:3] include/aws/mqtt/private/packets.h [6:6] include/aws/mqtt/private/shared_constants.h [3:3] include/aws/mqtt/private/topic_tree.h [6:6] @@ -106,6 +109,7 @@ BELONGS ya.make include/aws/mqtt/private/v5/mqtt5_decoder.h [3:3] include/aws/mqtt/private/v5/mqtt5_encoder.h [3:3] include/aws/mqtt/private/v5/mqtt5_options_storage.h [6:6] + include/aws/mqtt/private/v5/mqtt5_to_mqtt3_adapter_impl.h [3:3] include/aws/mqtt/private/v5/mqtt5_topic_alias.h [3:3] include/aws/mqtt/private/v5/mqtt5_utils.h [6:6] include/aws/mqtt/private/v5/rate_limiters.h [3:3] @@ -115,8 +119,11 @@ BELONGS ya.make include/aws/mqtt/v5/mqtt5_types.h [6:6] source/client.c [3:3] source/client_channel_handler.c [3:3] + source/client_impl_shared.c [3:3] source/fixed_header.c [3:3] source/mqtt.c [3:3] + source/mqtt311_decoder.c [3:3] + source/mqtt_subscription_set.c [3:3] source/packets.c [3:3] source/shared_constants.c [3:3] source/topic_tree.c [3:3] @@ -126,6 +133,7 @@ BELONGS ya.make source/v5/mqtt5_encoder.c [3:3] source/v5/mqtt5_listener.c [3:3] source/v5/mqtt5_options_storage.c [3:3] + source/v5/mqtt5_to_mqtt3_adapter.c [3:3] source/v5/mqtt5_topic_alias.c [3:3] source/v5/mqtt5_types.c [3:3] source/v5/mqtt5_utils.c [3:3] diff --git a/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/override.nix b/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/override.nix index 1361221b408..f02bd667fdb 100644 --- a/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/override.nix +++ b/contrib/restricted/aws/aws-c-mqtt/.yandex_meta/override.nix @@ -1,10 +1,10 @@ pkgs: attrs: with pkgs; with attrs; rec { - version = "0.8.8"; + version = "0.10.4"; src = fetchFromGitHub { owner = "awslabs"; repo = "aws-c-mqtt"; rev = "v${version}"; - hash = "sha256-bt5Qjw+CqgTfi/Ibhc4AwmJxr22Q6m3ygpmeMhvQTT0="; + hash = "sha256-i+ssZzHC8MPfyOaRqvjq0z7w772BJqIA6BwntW1fRek="; }; } diff --git a/contrib/restricted/aws/aws-c-mqtt/README.md b/contrib/restricted/aws/aws-c-mqtt/README.md index 070aab53b5e..2f99277e399 100644 --- a/contrib/restricted/aws/aws-c-mqtt/README.md +++ b/contrib/restricted/aws/aws-c-mqtt/README.md @@ -1,6 +1,6 @@ ## AWS C MQTT -C99 implementation of the MQTT 3.1.1 specification. +C99 implementation of the MQTT 3.1.1 and MQTT 5 specifications. ## License diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/client.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/client.h index f12a5c19b5a..8d85bfe89f3 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/client.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/client.h @@ -17,11 +17,14 @@ #include <aws/mqtt/mqtt.h> +AWS_PUSH_SANE_WARNING_LEVEL + /* forward declares */ struct aws_client_bootstrap; struct aws_http_header; struct aws_http_message; struct aws_http_proxy_options; +struct aws_mqtt5_client; struct aws_socket_options; struct aws_tls_connection_options; @@ -63,6 +66,27 @@ typedef void(aws_mqtt_client_on_connection_complete_fn)( bool session_present, void *userdata); +/* Called when a connection attempt succeed (with a successful CONNACK) + * + * The callback is derived from aws_mqtt_client_on_connection_complete_fn. + * It gets triggered when connection succeed (with a successful CONNACK) + */ +typedef void(aws_mqtt_client_on_connection_success_fn)( + struct aws_mqtt_client_connection *connection, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata); + +/* Called if the connection attempt failed. + * + * The callback is derived from aws_mqtt_client_on_connection_complete_fn. + * It gets triggered when connection failed. + */ +typedef void(aws_mqtt_client_on_connection_failure_fn)( + struct aws_mqtt_client_connection *connection, + int error_code, + void *userdata); + /* Called if the connection to the server is lost. */ typedef void(aws_mqtt_client_on_connection_interrupted_fn)( struct aws_mqtt_client_connection *connection, @@ -138,6 +162,11 @@ typedef void(aws_mqtt_client_publish_received_fn)( typedef void(aws_mqtt_client_on_disconnect_fn)(struct aws_mqtt_client_connection *connection, void *userdata); /** + * Signature of callback invoked on a connection destruction. + */ +typedef void(aws_mqtt_client_on_connection_termination_fn)(void *userdata); + +/** * Function to invoke when the websocket handshake request transformation completes. * This function MUST be invoked or the application will soft-lock. * @@ -225,7 +254,7 @@ struct aws_mqtt_topic_subscription { */ struct aws_mqtt_connection_options { struct aws_byte_cursor host_name; - uint16_t port; + uint32_t port; struct aws_socket_options *socket_options; struct aws_tls_connection_options *tls_options; struct aws_byte_cursor client_id; @@ -308,6 +337,16 @@ AWS_MQTT_API struct aws_mqtt_client_connection *aws_mqtt_client_connection_new(struct aws_mqtt_client *client); /** + * Creates a new MQTT311 connection object that uses an MQTT5 client under the hood + * + * \param[in] client The mqtt5 client to create the connection from + * + * \returns a new mqtt (311) connection on success, NULL otherwise + */ +AWS_MQTT_API +struct aws_mqtt_client_connection *aws_mqtt_client_connection_new_from_mqtt5_client(struct aws_mqtt5_client *client); + +/** * Increments the ref count to an mqtt client connection, allowing the caller to take a reference to it * * \param[in] connection The connection object @@ -387,6 +426,14 @@ int aws_mqtt_client_connection_set_http_proxy_options( struct aws_http_proxy_options *proxy_options); /** + * Set host resolution ooptions for the connection. + */ +AWS_MQTT_API +int aws_mqtt_client_connection_set_host_resolution_options( + struct aws_mqtt_client_connection *connection, + const struct aws_host_resolution_config *host_resolution_config); + +/** * Sets the minimum and maximum reconnect timeouts. * * The time between reconnect attempts will start at min and multiply by 2 until max is reached. @@ -402,6 +449,23 @@ int aws_mqtt_client_connection_set_reconnect_timeout( uint64_t max_timeout); /** + * Sets the callbacks to call when a connection succeeds or fails + * + * \param[in] connection The connection object + * \param[in] on_connection_success The function to call when a connection is successful or gets resumed + * \param[in] on_connection_success_ud Userdata for on_connection_success + * \param[in] on_connection_failure The function to call when a connection fails + * \param[in] on_connection_failure_ud Userdata for on_connection_failure + */ +AWS_MQTT_API +int aws_mqtt_client_connection_set_connection_result_handlers( + struct aws_mqtt_client_connection *connection, + aws_mqtt_client_on_connection_success_fn *on_connection_success, + void *on_connection_success_ud, + aws_mqtt_client_on_connection_failure_fn *on_connection_failure, + void *on_connection_failure_ud); + +/** * Sets the callbacks to call when a connection is interrupted and resumed. * * \param[in] connection The connection object @@ -448,6 +512,19 @@ int aws_mqtt_client_connection_set_on_any_publish_handler( void *on_any_publish_ud); /** + * Sets the callback to call on a connection destruction. + * + * \param[in] connection The connection object. + * \param[in] on_termination The function to call when a connection is destroyed. + * \param[in] on_termination_ud Userdata for on_termination. + */ +AWS_MQTT_API +int aws_mqtt_client_connection_set_connection_termination_handler( + struct aws_mqtt_client_connection *connection, + aws_mqtt_client_on_connection_termination_fn *on_termination, + void *on_termination_ud); + +/** * Opens the actual connection defined by aws_mqtt_client_connection_new. * Once the connection is opened, on_connack will be called. Only called when connection is disconnected. * @@ -547,32 +624,6 @@ uint16_t aws_mqtt_client_connection_subscribe( void *on_suback_ud); /** - * Subscribe to a single topic filter WITHOUT sending a SUBSCRIBE packet. - * This is useful if you expect the broker to send PUBLISHES without first subscribing. - * on_publish will be called when a PUBLISH matching topic_filter is received. - * - * \param[in] connection The connection to subscribe on - * \param[in] topic_filter The topic filter to subscribe on. This resource must persist until on_suback. - * \param[in] on_publish (nullable) Called when a PUBLISH packet matching topic_filter is received - * \param[in] on_publish_ud (nullable) Passed to on_publish - * \param[in] on_ud_cleanup (nullable) Called when a subscription is removed, on_publish_ud is passed. - * \param[in] on_suback (nullable) Called when a SUBACK has been received from the server and the subscription is - * complete - * \param[in] on_suback_ud (nullable) Passed to on_suback - * - * \returns The "packet id" of the operation if successfully initiated, otherwise 0. - */ -AWS_MQTT_API -uint16_t aws_mqtt_client_connection_subscribe_local( - struct aws_mqtt_client_connection *connection, - const struct aws_byte_cursor *topic_filter, - aws_mqtt_client_publish_received_fn *on_publish, - void *on_publish_ud, - aws_mqtt_userdata_cleanup_fn *on_ud_cleanup, - aws_mqtt_suback_fn *on_suback, - void *on_suback_ud); - -/** * Resubscribe to all topics currently subscribed to. This is to help when resuming a connection with a clean session. * * \param[in] connection The connection to subscribe on @@ -643,5 +694,6 @@ int aws_mqtt_client_connection_get_stats( struct aws_mqtt_connection_operation_statistics *stats); AWS_EXTERN_C_END +AWS_POP_SANE_WARNING_LEVEL #endif /* AWS_MQTT_CLIENT_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/mqtt.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/mqtt.h index 22a63ce2259..38510c8c80e 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/mqtt.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/mqtt.h @@ -11,6 +11,8 @@ #include <aws/mqtt/exports.h> +AWS_PUSH_SANE_WARNING_LEVEL + #define AWS_C_MQTT_PACKAGE_ID 5 /* Quality of Service associated with a publish action or subscription [MQTT-4.3]. */ @@ -76,6 +78,10 @@ enum aws_mqtt_error { AWS_ERROR_MQTT5_INVALID_INBOUND_TOPIC_ALIAS, AWS_ERROR_MQTT5_INVALID_OUTBOUND_TOPIC_ALIAS, AWS_ERROR_MQTT5_INVALID_UTF8_STRING, + AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT, + AWS_ERROR_MQTT_CONNECTION_RESUBSCRIBE_NO_TOPICS, + AWS_ERROR_MQTT_CONNECTION_SUBSCRIBE_FAILURE, + AWS_ERROR_MQTT_ACK_REASON_CODE_FAILURE, AWS_ERROR_END_MQTT_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_MQTT_PACKAGE_ID), }; @@ -87,6 +93,7 @@ enum aws_mqtt_log_subject { AWS_LS_MQTT5_GENERAL, AWS_LS_MQTT5_CLIENT, AWS_LS_MQTT5_CANARY, + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, }; /** Function called on cleanup of a userdata. */ @@ -96,10 +103,19 @@ AWS_EXTERN_C_BEGIN AWS_MQTT_API bool aws_mqtt_is_valid_topic(const struct aws_byte_cursor *topic); + AWS_MQTT_API bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter); /** + * Validate utf-8 string under mqtt specs + * + * @param text + * @return AWS_OP_SUCCESS if the text is validate, otherwise AWS_OP_ERR + */ +AWS_MQTT_API int aws_mqtt_validate_utf8_text(struct aws_byte_cursor text); + +/** * Initializes internal datastructures used by aws-c-mqtt. * Must be called before using any functionality in aws-c-mqtt. */ @@ -116,5 +132,6 @@ AWS_MQTT_API void aws_mqtt_fatal_assert_library_initialized(void); AWS_EXTERN_C_END +AWS_POP_SANE_WARNING_LEVEL #endif /* AWS_MQTT_MQTT_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/client_impl.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/client_impl.h index 6bdfe749c91..1d0dd67a0c2 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/client_impl.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/client_impl.h @@ -8,7 +8,9 @@ #include <aws/mqtt/client.h> +#include <aws/mqtt/private/client_impl_shared.h> #include <aws/mqtt/private/fixed_header.h> +#include <aws/mqtt/private/mqtt311_decoder.h> #include <aws/mqtt/private/topic_tree.h> #include <aws/common/hash_table.h> @@ -21,16 +23,18 @@ #include <aws/io/socket.h> #include <aws/io/tls_channel_handler.h> +struct aws_mqtt_client_connection_311_impl; + #define MQTT_CLIENT_CALL_CALLBACK(client_ptr, callback) \ do { \ if ((client_ptr)->callback) { \ - (client_ptr)->callback((client_ptr), (client_ptr)->callback##_ud); \ + (client_ptr)->callback((&client_ptr->base), (client_ptr)->callback##_ud); \ } \ } while (false) #define MQTT_CLIENT_CALL_CALLBACK_ARGS(client_ptr, callback, ...) \ do { \ if ((client_ptr)->callback) { \ - (client_ptr)->callback((client_ptr), __VA_ARGS__, (client_ptr)->callback##_ud); \ + (client_ptr)->callback((&client_ptr->base), __VA_ARGS__, (client_ptr)->callback##_ud); \ } \ } while (false) @@ -101,7 +105,8 @@ typedef enum aws_mqtt_client_request_state( /** * Called when the operation statistics change. */ -typedef void(aws_mqtt_on_operation_statistics_fn)(struct aws_mqtt_client_connection *connection, void *userdata); +typedef void( + aws_mqtt_on_operation_statistics_fn)(struct aws_mqtt_client_connection_311_impl *connection, void *userdata); /* Flags that indicate the way in which way an operation is currently affecting the statistics of the connection */ enum aws_mqtt_operation_statistic_state_flags { @@ -119,10 +124,15 @@ struct aws_mqtt_request { struct aws_linked_list_node list_node; struct aws_allocator *allocator; - struct aws_mqtt_client_connection *connection; + struct aws_mqtt_client_connection_311_impl *connection; struct aws_channel_task outgoing_task; + /* + * The request send time. Currently used to push off keepalive packet. + */ + uint64_t request_send_timestamp; + /* How this operation is currently affecting the statistics of the connection */ enum aws_mqtt_operation_statistic_state_flags statistic_state_flags; /* The encoded size of the packet - used for operation statistics tracking */ @@ -146,7 +156,7 @@ struct aws_mqtt_reconnect_task { /* The lifetime of this struct is from subscribe -> suback */ struct subscribe_task_arg { - struct aws_mqtt_client_connection *connection; + struct aws_mqtt_client_connection_311_impl *connection; /* list of pointer of subscribe_task_topics */ struct aws_array_list topics; @@ -166,19 +176,21 @@ struct subscribe_task_arg { /* The lifetime of this struct is the same as the lifetime of the subscription */ struct subscribe_task_topic { - struct aws_mqtt_client_connection *connection; + struct aws_mqtt_client_connection_311_impl *connection; struct aws_mqtt_topic_subscription request; struct aws_string *filter; - bool is_local; struct aws_ref_count ref_count; }; -struct aws_mqtt_client_connection { - +struct aws_mqtt_client_connection_311_impl { struct aws_allocator *allocator; + + struct aws_mqtt_client_connection base; + struct aws_ref_count ref_count; + struct aws_mqtt_client *client; /* Channel handler information */ @@ -187,16 +199,18 @@ struct aws_mqtt_client_connection { /* The host information, changed by user when state is AWS_MQTT_CLIENT_STATE_DISCONNECTED */ struct aws_string *host_name; - uint16_t port; + uint32_t port; struct aws_tls_connection_options tls_options; struct aws_socket_options socket_options; struct aws_http_proxy_config *http_proxy_config; struct aws_event_loop *loop; + struct aws_host_resolution_config host_resolution_config; /* Connect parameters */ struct aws_byte_buf client_id; bool clean_session; uint16_t keep_alive_time_secs; + uint64_t keep_alive_time_ns; uint64_t ping_timeout_ns; uint64_t operation_timeout_ns; struct aws_string *username; @@ -222,6 +236,10 @@ struct aws_mqtt_client_connection { /* User connection callbacks */ aws_mqtt_client_on_connection_complete_fn *on_connection_complete; void *on_connection_complete_ud; + aws_mqtt_client_on_connection_success_fn *on_connection_success; + void *on_connection_success_ud; + aws_mqtt_client_on_connection_failure_fn *on_connection_failure; + void *on_connection_failure_ud; aws_mqtt_client_on_connection_interrupted_fn *on_interrupted; void *on_interrupted_ud; aws_mqtt_client_on_connection_resumed_fn *on_resumed; @@ -232,6 +250,8 @@ struct aws_mqtt_client_connection { void *on_any_publish_ud; aws_mqtt_client_on_disconnect_fn *on_disconnect; void *on_disconnect_ud; + aws_mqtt_client_on_connection_termination_fn *on_termination; + void *on_termination_ud; aws_mqtt_on_operation_statistics_fn *on_any_operation_statistics; void *on_any_operation_statistics_ud; @@ -249,8 +269,7 @@ struct aws_mqtt_client_connection { /* Only the event-loop thread may touch this data */ struct { - /* If an incomplete packet arrives, store the data here. */ - struct aws_byte_buf pending_packet; + struct aws_mqtt311_decoder decoder; bool waiting_on_ping_response; @@ -309,6 +328,14 @@ struct aws_mqtt_client_connection { } websocket; /** + * The time that the next ping task should execute at. Note that this does not mean that + * this IS when the ping task will execute, but rather that this is when the next ping + * SHOULD execute. There may be an already scheduled PING task that will elapse sooner + * than this time that has to be rescheduled. + */ + uint64_t next_ping_time; + + /** * Statistics tracking operational state */ struct aws_mqtt_connection_operation_statistics_impl operation_statistics_impl; @@ -318,15 +345,15 @@ struct aws_channel_handler_vtable *aws_mqtt_get_client_channel_vtable(void); /* Helper for getting a message object for a packet */ struct aws_io_message *mqtt_get_message_for_packet( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, struct aws_mqtt_fixed_header *header); -void mqtt_connection_lock_synced_data(struct aws_mqtt_client_connection *connection); -void mqtt_connection_unlock_synced_data(struct aws_mqtt_client_connection *connection); +void mqtt_connection_lock_synced_data(struct aws_mqtt_client_connection_311_impl *connection); +void mqtt_connection_unlock_synced_data(struct aws_mqtt_client_connection_311_impl *connection); /* Note: needs to be called with lock held. */ void mqtt_connection_set_state( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, enum aws_mqtt_client_connection_state state); /** @@ -336,7 +363,7 @@ void mqtt_connection_set_state( * noRetry is true for the packets will never be retried or offline queued. */ AWS_MQTT_API uint16_t mqtt_create_request( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, aws_mqtt_send_request_fn *send_request, void *send_request_ud, aws_mqtt_op_complete_fn *on_complete, @@ -346,15 +373,15 @@ AWS_MQTT_API uint16_t mqtt_create_request( /* Call when an ack packet comes back from the server. */ AWS_MQTT_API void mqtt_request_complete( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, int error_code, uint16_t packet_id); /* Call to close the connection with an error code */ -AWS_MQTT_API void mqtt_disconnect_impl(struct aws_mqtt_client_connection *connection, int error_code); +AWS_MQTT_API void mqtt_disconnect_impl(struct aws_mqtt_client_connection_311_impl *connection, int error_code); /* Creates the task used to reestablish a broken connection */ -AWS_MQTT_API void aws_create_reconnect_task(struct aws_mqtt_client_connection *connection); +AWS_MQTT_API void aws_create_reconnect_task(struct aws_mqtt_client_connection_311_impl *connection); /** * Sets the callback to call whenever the operation statistics change. @@ -364,7 +391,7 @@ AWS_MQTT_API void aws_create_reconnect_task(struct aws_mqtt_client_connection *c * \param[in] on_operation_statistics_ud Userdata for on_operation_statistics */ AWS_MQTT_API int aws_mqtt_client_connection_set_on_operation_statistics_handler( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, aws_mqtt_on_operation_statistics_fn *on_operation_statistics, void *on_operation_statistics_ud); @@ -378,7 +405,7 @@ AWS_MQTT_API int aws_mqtt_client_connection_set_on_operation_statistics_handler( * \returns AWS_OP_SUCCESS if the connection is open and the PINGREQ is sent or queued to send, * otherwise AWS_OP_ERR and aws_last_error() is set. */ -int aws_mqtt_client_connection_ping(struct aws_mqtt_client_connection *connection); +int aws_mqtt_client_connection_ping(struct aws_mqtt_client_connection_311_impl *connection); /** * Changes the operation statistics for the passed-in aws_mqtt_request. Used for tracking @@ -392,8 +419,10 @@ int aws_mqtt_client_connection_ping(struct aws_mqtt_client_connection *connectio * @param new_state_flags The new state to use */ void aws_mqtt_connection_statistics_change_operation_statistic_state( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, struct aws_mqtt_request *request, enum aws_mqtt_operation_statistic_state_flags new_state_flags); +AWS_MQTT_API const struct aws_mqtt_client_connection_packet_handlers *aws_mqtt311_get_default_packet_handlers(void); + #endif /* AWS_MQTT_PRIVATE_CLIENT_IMPL_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/client_impl_shared.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/client_impl_shared.h new file mode 100644 index 00000000000..d244cfe7c66 --- /dev/null +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/client_impl_shared.h @@ -0,0 +1,123 @@ +#ifndef AWS_MQTT_PRIVATE_CLIENT_IMPL_SHARED_H +#define AWS_MQTT_PRIVATE_CLIENT_IMPL_SHARED_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include <aws/mqtt/client.h> + +struct aws_mqtt_client_connection; + +struct aws_mqtt_client_connection_vtable { + + struct aws_mqtt_client_connection *(*acquire_fn)(void *impl); + + void (*release_fn)(void *impl); + + int (*set_will_fn)( + void *impl, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + bool retain, + const struct aws_byte_cursor *payload); + + int (*set_login_fn)(void *impl, const struct aws_byte_cursor *username, const struct aws_byte_cursor *password); + + int (*use_websockets_fn)( + void *impl, + aws_mqtt_transform_websocket_handshake_fn *transformer, + void *transformer_ud, + aws_mqtt_validate_websocket_handshake_fn *validator, + void *validator_ud); + + int (*set_http_proxy_options_fn)(void *impl, struct aws_http_proxy_options *proxy_options); + + int (*set_host_resolution_options_fn)(void *impl, const struct aws_host_resolution_config *host_resolution_config); + + int (*set_reconnect_timeout_fn)(void *impl, uint64_t min_timeout, uint64_t max_timeout); + + int (*set_connection_interruption_handlers_fn)( + void *impl, + aws_mqtt_client_on_connection_interrupted_fn *on_interrupted, + void *on_interrupted_ud, + aws_mqtt_client_on_connection_resumed_fn *on_resumed, + void *on_resumed_ud); + + int (*set_connection_result_handlers)( + void *impl, + aws_mqtt_client_on_connection_success_fn *on_connection_success, + void *on_connection_success_ud, + aws_mqtt_client_on_connection_failure_fn *on_connection_failure, + void *on_connection_failure_ud); + + int (*set_connection_closed_handler_fn)( + void *impl, + aws_mqtt_client_on_connection_closed_fn *on_closed, + void *on_closed_ud); + + int (*set_on_any_publish_handler_fn)( + void *impl, + aws_mqtt_client_publish_received_fn *on_any_publish, + void *on_any_publish_ud); + + int (*set_connection_termination_handler_fn)( + void *impl, + aws_mqtt_client_on_connection_termination_fn *on_termination, + void *on_termination_ud); + + int (*connect_fn)(void *impl, const struct aws_mqtt_connection_options *connection_options); + + int (*reconnect_fn)(void *impl, aws_mqtt_client_on_connection_complete_fn *on_connection_complete, void *userdata); + + int (*disconnect_fn)(void *impl, aws_mqtt_client_on_disconnect_fn *on_disconnect, void *userdata); + + uint16_t (*subscribe_multiple_fn)( + void *impl, + const struct aws_array_list *topic_filters, + aws_mqtt_suback_multi_fn *on_suback, + void *on_suback_ud); + + uint16_t (*subscribe_fn)( + void *impl, + const struct aws_byte_cursor *topic_filter, + enum aws_mqtt_qos qos, + aws_mqtt_client_publish_received_fn *on_publish, + void *on_publish_ud, + aws_mqtt_userdata_cleanup_fn *on_ud_cleanup, + aws_mqtt_suback_fn *on_suback, + void *on_suback_ud); + + uint16_t (*resubscribe_existing_topics_fn)(void *impl, aws_mqtt_suback_multi_fn *on_suback, void *on_suback_ud); + + uint16_t (*unsubscribe_fn)( + void *impl, + const struct aws_byte_cursor *topic_filter, + aws_mqtt_op_complete_fn *on_unsuback, + void *on_unsuback_ud); + + uint16_t (*publish_fn)( + void *impl, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + bool retain, + const struct aws_byte_cursor *payload, + aws_mqtt_op_complete_fn *on_complete, + void *userdata); + + int (*get_stats_fn)(void *impl, struct aws_mqtt_connection_operation_statistics *stats); +}; + +struct aws_mqtt_client_connection { + struct aws_mqtt_client_connection_vtable *vtable; + void *impl; +}; + +AWS_MQTT_API uint64_t aws_mqtt_hash_uint16_t(const void *item); + +AWS_MQTT_API bool aws_mqtt_compare_uint16_t_eq(const void *a, const void *b); + +AWS_MQTT_API bool aws_mqtt_byte_cursor_hash_equality(const void *a, const void *b); + +#endif /* AWS_MQTT_PRIVATE_CLIENT_IMPL_SHARED_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/fixed_header.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/fixed_header.h index 4944c86fe95..98e2758a98d 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/fixed_header.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/fixed_header.h @@ -59,4 +59,6 @@ AWS_MQTT_API int aws_mqtt_fixed_header_encode(struct aws_byte_buf *buf, const st */ AWS_MQTT_API int aws_mqtt_fixed_header_decode(struct aws_byte_cursor *cur, struct aws_mqtt_fixed_header *header); +AWS_MQTT_API int aws_mqtt311_decode_remaining_length(struct aws_byte_cursor *cur, size_t *remaining_length_out); + #endif /* AWS_MQTT_PRIVATE_FIXED_HEADER_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt311_decoder.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt311_decoder.h new file mode 100644 index 00000000000..4147168d901 --- /dev/null +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt311_decoder.h @@ -0,0 +1,135 @@ +#ifndef AWS_MQTT_PRIVATE_MQTT311_DECODER_H +#define AWS_MQTT_PRIVATE_MQTT311_DECODER_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include <aws/mqtt/mqtt.h> + +#include <aws/common/byte_buf.h> + +/* + * Per-packet-type callback signature. message_cursor contains the entire packet's data. + */ +typedef int(packet_handler_fn)(struct aws_byte_cursor message_cursor, void *user_data); + +/* + * Wrapper for a set of packet handlers for each possible MQTT packet type. Some values are invalid in 311 (15), and + * some values are only valid from the perspective of the server or client. + */ +struct aws_mqtt_client_connection_packet_handlers { + packet_handler_fn *handlers_by_packet_type[16]; +}; + +/* + * Internal state of the 311 decoder/framing logic. + * + * When a packet is fragmented across multiple io buffers, state moves circularly: + * first byte -> remaining length -> body -> first byte etc... + * + * When a packet is completely contained inside a single io buffer, the entire packet is processed within + * the READ_FIRST_BYTE state. + */ +enum aws_mqtt_311_decoder_state_type { + + /* + * The decoder is expecting the first byte of the fixed header of an MQTT control packet + */ + AWS_MDST_READ_FIRST_BYTE, + + /* + * The decoder is expecting the vli-encoded total remaining length field of the fixed header on an MQTT control + * packet. + */ + AWS_MDST_READ_REMAINING_LENGTH, + + /* + * The decoder is expecting the "rest" of the MQTT packet's data based on the remaining length value that has + * already been read. + */ + AWS_MDST_READ_BODY, + + /* + * Terminal state for when a protocol error has been encountered by the decoder. The only way to leave this + * state is to reset the decoder via the aws_mqtt311_decoder_reset_for_new_connection() API. + */ + AWS_MDST_PROTOCOL_ERROR, +}; + +/* + * Configuration options for the decoder. When used by the actual implementation, handler_user_data is the + * connection object and the packet handlers are channel functions that hook into reactionary behavior and user + * callbacks. + */ +struct aws_mqtt311_decoder_options { + const struct aws_mqtt_client_connection_packet_handlers *packet_handlers; + void *handler_user_data; +}; + +/* + * Simple MQTT311 decoder structure. Actual decoding is deferred to per-packet functions that expect the whole + * packet in a buffer. The primary function of this sub-system is correctly framing a stream of bytes into the + * constituent packets. + */ +struct aws_mqtt311_decoder { + struct aws_mqtt311_decoder_options config; + + enum aws_mqtt_311_decoder_state_type state; + + /* + * If zero, not valid. If non-zero, represents the number of bytes that need to be read to finish the packet. + * This includes the total encoding size of the fixed header. + */ + size_t total_packet_length; + + /* scratch buffer to hold individual packets when they fragment across incoming data frame boundaries */ + struct aws_byte_buf packet_buffer; +}; + +AWS_EXTERN_C_BEGIN + +/** + * Initialize function for the MQTT311 decoder + * + * @param decoder decoder to initialize + * @param allocator memory allocator to use + * @param options additional decoder configuration options + */ +AWS_MQTT_API void aws_mqtt311_decoder_init( + struct aws_mqtt311_decoder *decoder, + struct aws_allocator *allocator, + const struct aws_mqtt311_decoder_options *options); + +/** + * Clean up function for an MQTT311 decoder + * + * @param decoder decoder to release resources for + */ +AWS_MQTT_API void aws_mqtt311_decoder_clean_up(struct aws_mqtt311_decoder *decoder); + +/** + * Callback function to decode the incoming data stream of an MQTT311 connection. Handles packet framing and + * correct decoder/handler function dispatch. + * + * @param decoder decoder to decode with + * @param data raw plaintext bytes of a connection operating on the MQTT311 protocol + * @return success/failure, failure represents a protocol error and implies the connection must be shut down + */ +AWS_MQTT_API int aws_mqtt311_decoder_on_bytes_received( + struct aws_mqtt311_decoder *decoder, + struct aws_byte_cursor data); + +/** + * Resets a decoder's state to its initial values. If using a decoder across multiple network connections (within + * the same client), you must invoke this when setting up a new connection, before any MQTT protocol bytes are + * processed. Breaks the decoder out of any previous protocol error terminal state. + * + * @param decoder decoder to reset + */ +AWS_MQTT_API void aws_mqtt311_decoder_reset_for_new_connection(struct aws_mqtt311_decoder *decoder); + +AWS_EXTERN_C_END + +#endif /* AWS_MQTT_PRIVATE_MQTT311_DECODER_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt_client_test_helper.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt_client_test_helper.h index 9dc2f129963..c3c430d2c1f 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt_client_test_helper.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt_client_test_helper.h @@ -10,7 +10,7 @@ struct aws_allocator; struct aws_byte_cursor; -struct aws_mqtt_client_connection; +struct aws_mqtt_client_connection_311_impl; struct aws_string; AWS_EXTERN_C_BEGIN diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt_subscription_set.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt_subscription_set.h new file mode 100644 index 00000000000..2ccb5d7e8ad --- /dev/null +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/mqtt_subscription_set.h @@ -0,0 +1,227 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#ifndef AWS_MQTT_MQTT3_TO_MQTT5_ADAPTER_SUBSCRIPTION_SET_H +#define AWS_MQTT_MQTT3_TO_MQTT5_ADAPTER_SUBSCRIPTION_SET_H + +#include "aws/mqtt/mqtt.h" + +#include "aws/mqtt/client.h" +#include "aws/mqtt/v5/mqtt5_types.h" +#include <aws/common/hash_table.h> + +/** + * (Transient) configuration options about a single persistent MQTT topic filter subscription + */ +struct aws_mqtt_subscription_set_subscription_options { + struct aws_byte_cursor topic_filter; + + enum aws_mqtt5_qos qos; + + bool no_local; + bool retain_as_published; + enum aws_mqtt5_retain_handling_type retain_handling_type; + + /* Callback invoked when this subscription matches an incoming publish */ + aws_mqtt_client_publish_received_fn *on_publish_received; + + /* Callback invoked when this subscription is removed from the set */ + aws_mqtt_userdata_cleanup_fn *on_cleanup; + + void *callback_user_data; +}; + +/** + * Persistent structure to track a single MQTT topic filter subscription + */ +struct aws_mqtt_subscription_set_subscription_record { + struct aws_allocator *allocator; + struct aws_byte_buf topic_filter; + + struct aws_mqtt_subscription_set_subscription_options subscription_view; +}; + +/** + * (Transient) configuration options about an incoming publish message + */ +struct aws_mqtt_subscription_set_publish_received_options { + struct aws_mqtt_client_connection *connection; + + struct aws_byte_cursor topic; + enum aws_mqtt_qos qos; + bool retain; + bool dup; + + struct aws_byte_cursor payload; +}; + +/** + * A node in the topic trie maintained by the subscription set. Each node represents a single "path segment" in a + * topic filter "path." Segments can be empty. + * + * Some examples (topic filter -> path segments): + * + * "hello/world" -> [ "hello", "world" ] + * "a/b/" -> [ "a", "b", "" ] + * "/b/" -> [ "", "b", "" ] + * "a/#/c" -> [ "a", "#", "c" ] + * + * On incoming publish, we walk the tree invoking callbacks based on topic vs. topic filter matching, segment by + * segment. + * + */ +struct aws_mqtt_subscription_set_topic_tree_node { + struct aws_allocator *allocator; + + struct aws_byte_cursor topic_segment_cursor; /* segment can be empty */ + struct aws_byte_buf topic_segment; + + struct aws_mqtt_subscription_set_topic_tree_node *parent; + struct aws_hash_table children; /* (embedded topic_segment -> containing node) */ + + /* + * A node starts with a ref count of one and is incremented every time a new, overlapping path is added + * to the subscription set. When the ref count goes to zero, that means there are not subscriptions using the + * segment (or path suffix) represented by this node and therefor it can be deleted without any additional + * analysis. + * + * Replacing an existing path does not change the ref count. + */ + size_t ref_count; + + bool is_subscription; + + aws_mqtt_client_publish_received_fn *on_publish_received; + aws_mqtt_userdata_cleanup_fn *on_cleanup; + + void *callback_user_data; +}; + +/** + * Utility type to track a client's subscriptions. + * + * The topic tree supports per-subscription callbacks as used by the MQTT311 implementation. + * + * The subscriptions table supports resubscribe APIs for both MQTT311 and MQTT5 by tracking the subscription + * details on a per-topic-filter basis. + */ +struct aws_mqtt_subscription_set { + struct aws_allocator *allocator; + + /* a permanent ref */ + struct aws_mqtt_subscription_set_topic_tree_node *root; + + /* embedded topic_filter_cursor -> persistent subscription */ + struct aws_hash_table subscriptions; +}; + +AWS_EXTERN_C_BEGIN + +/** + * Creates a new subscription set + * + * @param allocator allocator to use + * @return a new subscription set or NULL + */ +AWS_MQTT_API struct aws_mqtt_subscription_set *aws_mqtt_subscription_set_new(struct aws_allocator *allocator); + +/** + * Destroys a subscription set + * + * @param subscription_set subscription set to destroy + */ +AWS_MQTT_API void aws_mqtt_subscription_set_destroy(struct aws_mqtt_subscription_set *subscription_set); + +/** + * Checks if a topic filter exists in the subscription set's hash table of subscriptions + * + * @param subscription_set subscription set to check + * @param topic_filter topic filter to check for existence in the set + * @return true if the topic filter exists in the table of subscriptions, false otherwise + */ +AWS_MQTT_API bool aws_mqtt_subscription_set_is_subscribed( + const struct aws_mqtt_subscription_set *subscription_set, + struct aws_byte_cursor topic_filter); + +/** + * Checks if a topic filter exists as a subscription (has a publish received handler) in the set's topic tree + * + * @param subscription_set subscription set to check + * @param topic_filter topic filter to check for existence in the set's topic tree + * @return true if the set's topic tree contains a publish received callback for the topic filter, false otherwise + */ +AWS_MQTT_API bool aws_mqtt_subscription_set_is_in_topic_tree( + const struct aws_mqtt_subscription_set *subscription_set, + struct aws_byte_cursor topic_filter); + +/** + * Adds a subscription to the subscription set. If a subscription already exists with a matching topic filter, it + * will be overwritten. + * + * @param subscription_set subscription set to add a subscription to + * @param subscription_options options for the new subscription + */ +AWS_MQTT_API void aws_mqtt_subscription_set_add_subscription( + struct aws_mqtt_subscription_set *subscription_set, + const struct aws_mqtt_subscription_set_subscription_options *subscription_options); + +/** + * Removes a subscription from the subscription set + * + * @param subscription_set subscription set to remove a subscription from + * @param topic_filter topic filter to remove subscription information for + */ +AWS_MQTT_API void aws_mqtt_subscription_set_remove_subscription( + struct aws_mqtt_subscription_set *subscription_set, + struct aws_byte_cursor topic_filter); + +/** + * Given a publish message, invokes all publish received handlers for matching subscriptions in the subscription set + * + * @param subscription_set subscription set to invoke publish received callbacks for + * @param publish_options received publish message properties + */ +AWS_MQTT_API void aws_mqtt_subscription_set_on_publish_received( + const struct aws_mqtt_subscription_set *subscription_set, + const struct aws_mqtt_subscription_set_publish_received_options *publish_options); + +/** + * Queries the properties of all subscriptions tracked by this subscription set. Used to implement re-subscribe + * behavior. + * + * @param subscription_set subscription set to query the subscriptions on + * @param subscriptions uninitialized array list to hold the subscriptions. + * + * The caller must invoke the cleanup function for array lists on the result. The list elements are of type + * 'struct aws_mqtt_subscription_set_subscription_options' and the topic filter cursor points to the subscription set's + * internal record. This means that the result must be used and cleaned up in local scope. + */ +AWS_MQTT_API void aws_mqtt_subscription_set_get_subscriptions( + struct aws_mqtt_subscription_set *subscription_set, + struct aws_array_list *subscriptions); + +/** + * Creates a new subscription record. A subscription record tracks all information about a single MQTT topic filter + * subscription + * + * @param allocator memory allocator to use + * @param subscription all relevant information about the subscription + * @return a new persistent subscription record + */ +AWS_MQTT_API struct aws_mqtt_subscription_set_subscription_record *aws_mqtt_subscription_set_subscription_record_new( + struct aws_allocator *allocator, + const struct aws_mqtt_subscription_set_subscription_options *subscription); + +/** + * Destroys a subscription record + * + * @param record subscription record to destroy + */ +AWS_MQTT_API void aws_mqtt_subscription_set_subscription_record_destroy( + struct aws_mqtt_subscription_set_subscription_record *record); + +AWS_EXTERN_C_END + +#endif /* AWS_MQTT_MQTT3_TO_MQTT5_ADAPTER_SUBSCRIPTION_SET_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/packets.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/packets.h index 94a7591257f..c3b63ac5df8 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/packets.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/packets.h @@ -220,6 +220,9 @@ AWS_MQTT_API int aws_mqtt_packet_publish_decode(struct aws_byte_cursor *cur, struct aws_mqtt_packet_publish *packet); AWS_MQTT_API +void aws_mqtt_packet_publish_set_dup(struct aws_mqtt_packet_publish *packet); + +AWS_MQTT_API bool aws_mqtt_packet_publish_get_dup(const struct aws_mqtt_packet_publish *packet); AWS_MQTT_API diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_client_impl.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_client_impl.h index 7c08354963b..9a59de241dc 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_client_impl.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_client_impl.h @@ -259,6 +259,12 @@ struct aws_mqtt5_client_operational_state { struct aws_linked_list write_completion_operations; /* + * heap of operation pointers where the timeout is the sort value. Elements are added/removed from this + * data structure in exact synchronization with unacked_operations_table. + */ + struct aws_priority_queue operations_by_ack_timeout; + + /* * Is there an io message in transit (to the socket) that has not invoked its write completion callback yet? * The client implementation only allows one in-transit message at a time, and so if this is true, we don't * send additional ones/ @@ -342,7 +348,7 @@ struct aws_mqtt5_client { /* * Client configuration */ - const struct aws_mqtt5_client_options_storage *config; + struct aws_mqtt5_client_options_storage *config; /* * The recurrent task that runs all client logic outside of external event callbacks. Bound to the client's @@ -489,6 +495,29 @@ struct aws_mqtt5_client { * with clean start set to false. */ bool has_connected_successfully; + + /* + * A flag that allows in-thread observers (currently the mqtt3_to_5 adapter) to signal that the connection + * should be torn down and re-established. Only relevant to the CONNECTING state which is not interruptible: + * + * If the mqtt5 client is in the CONNECTING state (ie waiting for bootstrap to complete) and the 3-adapter + * is asked to connect, then we *MUST* discard the in-progress connection attempt in order to guarantee the + * connection we establish uses all of the configuration parameters that are passed during the mqtt3 API's connect + * call (host, port, tls options, socket options, etc...). Since we can't interrupt the CONNECTING state, we + * instead set a flag that tells the mqtt5 client to tear down the connection as soon as the initial bootstrap + * completes. The reconnect will establish the requested connection using the parameters passed to + * the mqtt3 API. + * + * Rather than try and catch every escape path from CONNECTING, we lazily reset this flag to false when we + * enter the CONNECTING state. On a similar note, we only check this flag as we transition to MQTT_CONNECT. + * + * This flag is ultimately only needed when the 3 adapter and 5 client are used out-of-sync. If you use the + * 3 adapter exclusively after 5 client creation, it never comes into play. + * + * Even the adapter shouldn't manipulate this directly. Instead, use the aws_mqtt5_client_reset_connection private + * API to tear down an in-progress or established connection in response to a connect() request on the adapter. + */ + bool should_reset_connection; }; AWS_EXTERN_C_BEGIN @@ -638,10 +667,44 @@ AWS_MQTT_API void aws_mqtt5_client_statistics_change_operation_statistic_state( */ AWS_MQTT_API const char *aws_mqtt5_client_state_to_c_string(enum aws_mqtt5_client_state state); -/* - * Temporary, private API to turn on total incoming packet logging at the byte level. +/** + * An internal API used by the MQTT3 adapter to force any existing-or-in-progress connection to + * be torn down and re-established. Necessary because the MQTT3 interface allows overrides on a large number + * of configuration parameters through the connect() call. We must honor those parameters and the safest thing + * to do is to just throw away the current connection (if it exists) and make a new one. In the case that an MQTT5 + * client is being driven entirely by the MQTT3 adapter, this case never actually happens. + * + * @param client client to reset an existing or in-progress connection for + * @return true if a connection reset was triggered, false if there was nothing to do + */ +AWS_MQTT_API bool aws_mqtt5_client_reset_connection(struct aws_mqtt5_client *client); + +/** + * Event-loop-internal API used to switch the client's desired state. Used by both start() and stop() cross-thread + * tasks as well as by the 3-to-5 adapter to make changes synchronously (when in the event loop). + * + * @param client mqtt5 client to update desired state for + * @param desired_state new desired state + * @param disconnect_op optional description of a DISCONNECT packet to send as part of a stop command + */ +AWS_MQTT_API void aws_mqtt5_client_change_desired_state( + struct aws_mqtt5_client *client, + enum aws_mqtt5_client_state desired_state, + struct aws_mqtt5_operation_disconnect *disconnect_op); + +/** + * Event-loop-internal API to add an operation to the client's queue. Used by the 3-to-5 adapter to synchnrously + * inject the MQTT5 operation once the adapter operation has reached the event loop. + * + * @param client MQTT5 client to submit an operation to + * @param operation MQTT5 operation to submit + * @param is_terminated flag that indicates whether the submitter is shutting down or not. Needed to differentiate + * between adapter submissions and MQTT5 client API submissions and correctly handle ref count adjustments. */ -AWS_MQTT_API void aws_mqtt5_client_enable_full_packet_logging(struct aws_mqtt5_client *client); +AWS_MQTT_API void aws_mqtt5_client_submit_operation_internal( + struct aws_mqtt5_client *client, + struct aws_mqtt5_operation *operation, + bool is_terminated); AWS_EXTERN_C_END diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_options_storage.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_options_storage.h index dcc07d399d4..ddcb0ab5923 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_options_storage.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_options_storage.h @@ -12,6 +12,7 @@ #include <aws/common/logging.h> #include <aws/common/ref_count.h> #include <aws/http/proxy.h> +#include <aws/io/host_resolver.h> #include <aws/io/retry_strategy.h> #include <aws/io/socket.h> #include <aws/io/tls_channel_handler.h> @@ -41,6 +42,8 @@ struct aws_mqtt5_operation_vtable { int (*aws_mqtt5_operation_validate_vs_connection_settings_fn)( const void *operation_packet_view, const struct aws_mqtt5_client *client); + + uint32_t (*aws_mqtt5_operation_get_ack_timeout_override_fn)(const struct aws_mqtt5_operation *operation); }; /* Flags that indicate the way in which an operation is currently affecting the statistics of the client */ @@ -63,6 +66,7 @@ struct aws_mqtt5_operation { const struct aws_mqtt5_operation_vtable *vtable; struct aws_ref_count ref_count; uint64_t ack_timeout_timepoint_ns; + struct aws_priority_queue_node priority_queue_node; struct aws_linked_list_node node; enum aws_mqtt5_packet_type packet_type; @@ -137,7 +141,7 @@ struct aws_mqtt5_client_options_storage { struct aws_allocator *allocator; struct aws_string *host_name; - uint16_t port; + uint32_t port; struct aws_client_bootstrap *bootstrap; struct aws_socket_options socket_options; @@ -162,20 +166,22 @@ struct aws_mqtt5_client_options_storage { uint64_t max_reconnect_delay_ms; uint64_t min_connected_time_to_reset_reconnect_delay_ms; - uint64_t ack_timeout_seconds; + uint32_t ack_timeout_seconds; uint32_t ping_timeout_ms; uint32_t connack_timeout_ms; struct aws_mqtt5_client_topic_alias_options topic_aliasing_options; - struct aws_mqtt5_packet_connect_storage connect; + struct aws_mqtt5_packet_connect_storage *connect; aws_mqtt5_client_connection_event_callback_fn *lifecycle_event_handler; void *lifecycle_event_handler_user_data; aws_mqtt5_client_termination_completion_fn *client_termination_handler; void *client_termination_handler_user_data; + + struct aws_host_resolution_config host_resolution_override; }; AWS_EXTERN_C_BEGIN @@ -205,6 +211,8 @@ AWS_MQTT_API int aws_mqtt5_operation_validate_vs_connection_settings( const struct aws_mqtt5_operation *operation, const struct aws_mqtt5_client *client); +AWS_MQTT_API uint32_t aws_mqtt5_operation_get_ack_timeout_override(const struct aws_mqtt5_operation *operation); + /* Connect */ AWS_MQTT_API struct aws_mqtt5_operation_connect *aws_mqtt5_operation_connect_new( @@ -254,9 +262,6 @@ AWS_MQTT_API struct aws_mqtt5_operation_publish *aws_mqtt5_operation_publish_new AWS_MQTT_API int aws_mqtt5_packet_publish_view_validate(const struct aws_mqtt5_packet_publish_view *publish_view); -AWS_MQTT_API int aws_mqtt5_packet_publish_view_validate_vs_iot_core( - const struct aws_mqtt5_packet_publish_view *publish_view); - AWS_MQTT_API void aws_mqtt5_packet_publish_view_log( const struct aws_mqtt5_packet_publish_view *publish_view, enum aws_log_level level); @@ -281,9 +286,6 @@ AWS_MQTT_API struct aws_mqtt5_operation_subscribe *aws_mqtt5_operation_subscribe AWS_MQTT_API int aws_mqtt5_packet_subscribe_view_validate(const struct aws_mqtt5_packet_subscribe_view *subscribe_view); -AWS_MQTT_API int aws_mqtt5_packet_subscribe_view_validate_vs_iot_core( - const struct aws_mqtt5_packet_subscribe_view *subscribe_view); - AWS_MQTT_API void aws_mqtt5_packet_subscribe_view_log( const struct aws_mqtt5_packet_subscribe_view *subscribe_view, enum aws_log_level level); @@ -305,9 +307,6 @@ AWS_MQTT_API struct aws_mqtt5_operation_unsubscribe *aws_mqtt5_operation_unsubsc AWS_MQTT_API int aws_mqtt5_packet_unsubscribe_view_validate( const struct aws_mqtt5_packet_unsubscribe_view *unsubscribe_view); -AWS_MQTT_API int aws_mqtt5_packet_unsubscribe_view_validate_vs_iot_core( - const struct aws_mqtt5_packet_unsubscribe_view *unsubscribe_view); - AWS_MQTT_API void aws_mqtt5_packet_unsubscribe_view_log( const struct aws_mqtt5_packet_unsubscribe_view *unsubscribe_view, enum aws_log_level level); diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_to_mqtt3_adapter_impl.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_to_mqtt3_adapter_impl.h new file mode 100644 index 00000000000..d892d07159c --- /dev/null +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_to_mqtt3_adapter_impl.h @@ -0,0 +1,341 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#ifndef AWS_MQTT_MQTT5_TO_MQTT3_ADAPTER_IMPL_H +#define AWS_MQTT_MQTT5_TO_MQTT3_ADAPTER_IMPL_H + +#include <aws/mqtt/mqtt.h> + +#include <aws/common/mutex.h> +#include <aws/common/rw_lock.h> +#include <aws/common/task_scheduler.h> +#include <aws/mqtt/client.h> +#include <aws/mqtt/private/client_impl_shared.h> +#include <aws/mqtt/v5/mqtt5_client.h> + +struct aws_mqtt_subscription_set; + +struct aws_mqtt5_to_mqtt3_adapter_publish_options { + struct aws_mqtt_client_connection_5_impl *adapter; + + const struct aws_byte_cursor topic; + enum aws_mqtt_qos qos; + bool retain; + const struct aws_byte_cursor payload; + + aws_mqtt_op_complete_fn *on_complete; + void *on_complete_userdata; +}; + +/* + * A subscribe with no subscriptions represents a re-subscribe of all internally tracked topics. While this + * is a bit hacky, the alternative is to copy-and-paste almost the entire multi-subscribe adapter operation and + * supporting logic, which is approximately 300 lines. + */ +struct aws_mqtt5_to_mqtt3_adapter_subscribe_options { + struct aws_mqtt_client_connection_5_impl *adapter; + + struct aws_mqtt_topic_subscription *subscriptions; + size_t subscription_count; + + aws_mqtt_suback_fn *on_suback; + void *on_suback_user_data; + + aws_mqtt_suback_multi_fn *on_multi_suback; + void *on_multi_suback_user_data; +}; + +struct aws_mqtt5_to_mqtt3_adapter_unsubscribe_options { + struct aws_mqtt_client_connection_5_impl *adapter; + + struct aws_byte_cursor topic_filter; + + aws_mqtt_op_complete_fn *on_unsuback; + void *on_unsuback_user_data; +}; + +enum aws_mqtt5_to_mqtt3_adapter_operation_type { + AWS_MQTT5TO3_AOT_PUBLISH, + AWS_MQTT5TO3_AOT_SUBSCRIBE, + AWS_MQTT5TO3_AOT_UNSUBSCRIBE, +}; + +struct aws_mqtt5_to_mqtt3_adapter_operation_vtable { + void (*fail_fn)(void *impl, int error_code); +}; + +struct aws_mqtt5_to_mqtt3_adapter_operation_base { + struct aws_allocator *allocator; + struct aws_ref_count ref_count; + const struct aws_mqtt5_to_mqtt3_adapter_operation_vtable *vtable; + + void *impl; + + /* + * Holds an internal reference to the adapter while traveling to the event loop. Reference gets released + * after intake on the event loop. + * + * We avoid calling back into a deleted adapter by zeroing out the + * mqtt5 operation callbacks for everything we've submitted before final mqtt5 client release. + */ + struct aws_mqtt_client_connection_5_impl *adapter; + bool holding_adapter_ref; + + struct aws_task submission_task; + + enum aws_mqtt5_to_mqtt3_adapter_operation_type type; + uint16_t id; +}; + +struct aws_mqtt5_to_mqtt3_adapter_operation_publish { + struct aws_mqtt5_to_mqtt3_adapter_operation_base base; + + /* + * holds a reference to the MQTT5 client publish operation until the operation completes or our adapter + * goes away. + * + * In the case where we're going away, we zero out the MQTT5 operation callbacks to prevent crash-triggering + * notifications. + */ + struct aws_mqtt5_operation_publish *publish_op; + + aws_mqtt_op_complete_fn *on_publish_complete; + void *on_publish_complete_user_data; +}; + +struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe { + struct aws_mqtt5_to_mqtt3_adapter_operation_base base; + + /* + * holds a reference to the MQTT5 client subscribe operation until the operation completes or our adapter + * goes away. + * + * In the case where we're going away, we zero out the MQTT5 operation callbacks to prevent crash-triggering + * notifications. + */ + struct aws_mqtt5_operation_subscribe *subscribe_op; + + /* aws_array_list<struct aws_mqtt_subscription_set_subscription_record *> */ + struct aws_array_list subscriptions; + + aws_mqtt_suback_fn *on_suback; + void *on_suback_user_data; + + aws_mqtt_suback_multi_fn *on_multi_suback; + void *on_multi_suback_user_data; +}; + +struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe { + struct aws_mqtt5_to_mqtt3_adapter_operation_base base; + + /* + * holds a reference to the MQTT5 client unsubscribe operation until the operation completes or our adapter + * goes away. + * + * In the case where we're going away, we zero out the MQTT5 operation callbacks to prevent crash-triggering + * notifications. + */ + struct aws_mqtt5_operation_unsubscribe *unsubscribe_op; + + struct aws_byte_buf topic_filter; + + aws_mqtt_op_complete_fn *on_unsuback; + void *on_unsuback_user_data; +}; + +/* + + Sequencing (PUBLISH example): + + Mqtt311 public API call + Create cross thread task + Create adapter op -> Create and attach mqtt5 op + allocate id and add operation to adapter table + Add adapter op's internal ref to adapter + submit cross thread task to event loop + return id or 0 + + Adapter Op reaches event loop task function: (from this point, all callbacks must be safe-guarded) + terminated = true + Safe handler: + If adapter not terminated: + terminated = false + Synchronously enqueue operation to mqtt5 client + if terminated: + remove adapter op from table + destroy adapter op + Release adapter op's internal ref to adapter + + On publish completion: + Safe handler: + If not terminated: + invoke mqtt311 callback + Remove adapter op from table + Destroy adapter op + + On final destroy (zero internal refs): + Iterate all incomplete adapter operations and cancel them: zero callbacks and remove from queue if in queue and + unbound + Destroy all adapter ops + Clear table +*/ + +struct aws_mqtt5_to_mqtt3_adapter_operation_table { + struct aws_mutex lock; + + struct aws_hash_table operations; + uint16_t next_id; +}; + +/* + * The adapter maintains a notion of state based on how its 311 API has been used. This state guides how it handles + * external lifecycle events. + * + * Operational (sourced from the adapter) events are always relayed unless the adapter has been terminated. + */ +enum aws_mqtt_adapter_state { + + /* + * The 311 API has had connect() called but that connect has not yet resolved. + * + * If it resolves successfully we will move to the STAY_CONNECTED state which will relay lifecycle callbacks + * transparently. + * + * If it resolves unsuccessfully, we will move to the STAY_DISCONNECTED state where we will ignore lifecycle + * events because, from the 311 API's perspective, nothing should be getting emitted. + */ + AWS_MQTT_AS_FIRST_CONNECT, + + /* + * A call to the 311 connect API has resolved successfully. Relay all lifecycle events until told otherwise. + */ + AWS_MQTT_AS_STAY_CONNECTED, + + /* + * We have not observed a successful initial connection attempt via the 311 API (or disconnect has been + * invoked afterwards). Ignore all lifecycle events. + */ + AWS_MQTT_AS_STAY_DISCONNECTED, +}; + +struct aws_mqtt_client_connection_5_impl { + + struct aws_allocator *allocator; + + struct aws_mqtt_client_connection base; + + struct aws_mqtt5_client *client; + struct aws_mqtt5_listener *listener; + struct aws_event_loop *loop; + + /* + * The current adapter state based on the sequence of connect(), disconnect(), and connection completion events. + * This affects how the adapter reacts to incoming mqtt5 events. Under certain conditions, we may change + * this state value based on unexpected events (stopping the mqtt5 client underneath the adapter, for example) + */ + enum aws_mqtt_adapter_state adapter_state; + + /* + * Tracks all references from external sources (ie users). Incremented and decremented by the public + * acquire/release APIs of the 311 connection. + * + * When this value drops to zero, the terminated flag is set and no further callbacks will be invoked. This + * also starts the asynchronous destruction process for the adapter. + */ + struct aws_ref_count external_refs; + + /* + * Tracks all references to the adapter from internal sources (temporary async processes that need the + * adapter to stay alive for an interval of time, like sending tasks across thread boundaries). + * + * Starts with a single reference that is held until the adapter's listener has fully detached from the mqtt5 + * client. + * + * Once the internal ref count drops to zero, the adapter may be destroyed synchronously. + */ + struct aws_ref_count internal_refs; + + struct aws_mqtt5_to_mqtt3_adapter_operation_table operational_state; + + struct aws_mqtt_subscription_set *subscriptions; + + /* All fields after here are internal to the adapter event loop thread */ + + /* 311 interface callbacks */ + aws_mqtt_client_on_connection_interrupted_fn *on_interrupted; + void *on_interrupted_user_data; + + aws_mqtt_client_on_connection_resumed_fn *on_resumed; + void *on_resumed_user_data; + + aws_mqtt_client_on_connection_closed_fn *on_closed; + void *on_closed_user_data; + + aws_mqtt_client_on_connection_success_fn *on_connection_success; + void *on_connection_success_user_data; + + aws_mqtt_client_on_connection_failure_fn *on_connection_failure; + void *on_connection_failure_user_data; + + aws_mqtt_client_publish_received_fn *on_any_publish; + void *on_any_publish_user_data; + + aws_mqtt_transform_websocket_handshake_fn *websocket_handshake_transformer; + void *websocket_handshake_transformer_user_data; + + aws_mqtt5_transform_websocket_handshake_complete_fn *mqtt5_websocket_handshake_completion_function; + void *mqtt5_websocket_handshake_completion_user_data; + + /* (mutually exclusive) 311 interface one-time transient callbacks */ + aws_mqtt_client_on_disconnect_fn *on_disconnect; + void *on_disconnect_user_data; + + aws_mqtt_client_on_connection_complete_fn *on_connection_complete; + void *on_connection_complete_user_data; + + aws_mqtt_client_on_connection_termination_fn *on_termination; + void *on_termination_user_data; +}; + +AWS_EXTERN_C_BEGIN + +AWS_MQTT_API void aws_mqtt5_to_mqtt3_adapter_operation_table_init( + struct aws_mqtt5_to_mqtt3_adapter_operation_table *table, + struct aws_allocator *allocator); + +AWS_MQTT_API void aws_mqtt5_to_mqtt3_adapter_operation_table_clean_up( + struct aws_mqtt5_to_mqtt3_adapter_operation_table *table); + +AWS_MQTT_API int aws_mqtt5_to_mqtt3_adapter_operation_table_add_operation( + struct aws_mqtt5_to_mqtt3_adapter_operation_table *table, + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation); + +AWS_MQTT_API void aws_mqtt5_to_mqtt3_adapter_operation_table_remove_operation( + struct aws_mqtt5_to_mqtt3_adapter_operation_table *table, + uint16_t operation_id); + +AWS_MQTT_API struct aws_mqtt5_to_mqtt3_adapter_operation_publish *aws_mqtt5_to_mqtt3_adapter_operation_new_publish( + struct aws_allocator *allocator, + const struct aws_mqtt5_to_mqtt3_adapter_publish_options *options); + +AWS_MQTT_API struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *aws_mqtt5_to_mqtt3_adapter_operation_new_subscribe( + struct aws_allocator *allocator, + const struct aws_mqtt5_to_mqtt3_adapter_subscribe_options *options, + struct aws_mqtt_client_connection_5_impl *adapter); + +AWS_MQTT_API struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe * + aws_mqtt5_to_mqtt3_adapter_operation_new_unsubscribe( + struct aws_allocator *allocator, + const struct aws_mqtt5_to_mqtt3_adapter_unsubscribe_options *options); + +AWS_MQTT_API struct aws_mqtt5_to_mqtt3_adapter_operation_base *aws_mqtt5_to_mqtt3_adapter_operation_release( + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation); + +AWS_MQTT_API struct aws_mqtt5_to_mqtt3_adapter_operation_base *aws_mqtt5_to_mqtt3_adapter_operation_acquire( + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation); + +AWS_EXTERN_C_END + +#endif /* AWS_MQTT_MQTT5_TO_MQTT3_ADAPTER_IMPL_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_utils.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_utils.h index be4c8ba2cf8..697a433099b 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_utils.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/private/v5/mqtt5_utils.h @@ -67,10 +67,8 @@ struct aws_mqtt5_negotiated_settings; #define AWS_MQTT5_SUBSCRIBE_FLAGS_QOS_BIT_MASK 0x03 /* Static AWS IoT Core Limit/Quota Values */ -#define AWS_IOT_CORE_MAXIMUM_CLIENT_ID_LENGTH 128 #define AWS_IOT_CORE_MAXIMUM_TOPIC_LENGTH 256 #define AWS_IOT_CORE_MAXIMUM_TOPIC_SEGMENTS 8 -#define AWS_IOT_CORE_MAXIMUM_SUSBCRIPTIONS_PER_SUBSCRIBE 8 /* Dynamic IoT Core Limits */ #define AWS_IOT_CORE_PUBLISH_PER_SECOND_LIMIT 100 @@ -97,14 +95,6 @@ AWS_EXTERN_C_BEGIN AWS_MQTT_API extern struct aws_byte_cursor g_aws_mqtt5_connect_protocol_cursor; /** - * Validate utf-8 string under mqtt5 specs - * - * @param text - * @return AWS_OP_SUCCESS if the text is validate, otherwise AWS_OP_ERR - */ -AWS_MQTT_API int aws_mqtt5_validate_utf8_text(struct aws_byte_cursor text); - -/** * Simple helper function to compute the first byte of an MQTT packet encoding as a function of 4 bit flags * and the packet type. * @@ -221,6 +211,15 @@ AWS_MQTT_API const char *aws_mqtt5_outbound_topic_alias_behavior_type_to_c_strin enum aws_mqtt5_client_outbound_topic_alias_behavior_type outbound_aliasing_behavior); /** + * Checks an outbound aliasing behavior type value for validity + * + * @param outbound_aliasing_behavior value to check + * @return true if this is a valid value, false otherwise + */ +AWS_MQTT_API bool aws_mqtt5_outbound_topic_alias_behavior_type_validate( + enum aws_mqtt5_client_outbound_topic_alias_behavior_type outbound_aliasing_behavior); + +/** * Converts an outbound topic aliasing behavior type value to a final non-default value. * * @param outbound_aliasing_behavior type of outbound topic aliasing behavior @@ -240,6 +239,15 @@ AWS_MQTT_API const char *aws_mqtt5_inbound_topic_alias_behavior_type_to_c_string enum aws_mqtt5_client_inbound_topic_alias_behavior_type inbound_aliasing_behavior); /** + * Checks an inbound aliasing behavior type value for validity + * + * @param inbound_aliasing_behavior value to check + * @return true if this is a valid value, false otherwise + */ +AWS_MQTT_API bool aws_mqtt5_inbound_topic_alias_behavior_type_validate( + enum aws_mqtt5_client_inbound_topic_alias_behavior_type inbound_aliasing_behavior); + +/** * Converts an inbound topic aliasing behavior type value to a final non-default value. * * @param inbound_aliasing_behavior type of inbound topic aliasing behavior @@ -328,7 +336,8 @@ AWS_MQTT_API uint64_t aws_mqtt5_client_random_in_range(uint64_t from, uint64_t t * @param topic_cursor topic to get the non-rules suffix for * @return remaining part of the topic after the leading AWS IoT Rules prefix has been skipped, if present */ -AWS_MQTT_API struct aws_byte_cursor aws_mqtt5_topic_skip_aws_iot_rules_prefix(struct aws_byte_cursor topic_cursor); +AWS_MQTT_API struct aws_byte_cursor aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix( + struct aws_byte_cursor topic_cursor); /** * Computes the number of topic segments in a topic or topic filter diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_client.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_client.h index e99338cee25..d04d29814b0 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_client.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_client.h @@ -6,23 +6,17 @@ * SPDX-License-Identifier: Apache-2.0. */ -/** - * DEVELOPER PREVIEW DISCLAIMER - * - * MQTT5 support is currently in **developer preview**. We encourage feedback at all times, but feedback during the - * preview window is especially valuable in shaping the final product. During the preview period we may make - * backwards-incompatible changes to the public API, but in general, this is something we will try our best to avoid. - */ - #include <aws/mqtt/mqtt.h> #include <aws/io/retry_strategy.h> #include <aws/mqtt/v5/mqtt5_types.h> +AWS_PUSH_SANE_WARNING_LEVEL + struct aws_allocator; struct aws_client_bootstrap; +struct aws_host_resolution_config; struct aws_http_message; -struct aws_input_stream; struct aws_mqtt5_client; struct aws_mqtt5_client_lifecycle_event; struct aws_tls_connection_options; @@ -84,16 +78,15 @@ enum aws_mqtt5_client_outbound_topic_alias_behavior_type { * topic alias mappings unpredictably. The client will properly use the alias when the current connection * has seen the alias binding already. */ - AWS_MQTT5_COTABT_USER, + AWS_MQTT5_COTABT_MANUAL, /** - * Client fails any user-specified topic aliasing and acts on the outbound alias set as an LRU cache. + * Client ignores any user-specified topic aliasing and acts on the outbound alias set as an LRU cache. */ AWS_MQTT5_COTABT_LRU, /** - * Completely disable outbound topic aliasing. Attempting to set a topic alias on a PUBLISH results in - * an error. + * Completely disable outbound topic aliasing. */ AWS_MQTT5_COTABT_DISABLED }; @@ -160,7 +153,7 @@ struct aws_mqtt5_client_topic_alias_options { * disabled, this setting has no effect. * * Behaviorally, this value overrides anything present in the topic_alias_maximum field of - * the CONNECT packet options. We intentionally don't bind that field to managed clients to reduce + * the CONNECT packet options. */ uint16_t inbound_alias_cache_size; }; @@ -179,16 +172,10 @@ enum aws_mqtt5_extended_validation_and_flow_control_options { AWS_MQTT5_EVAFCO_NONE, /** - * Apply additional client-side validation and operational flow control that respects the + * Apply additional client-side operational flow control that respects the * default AWS IoT Core limits. * - * Currently applies the following additional validation: - * (1) No more than 8 subscriptions per SUBSCRIBE packet - * (2) Topics and topic filters have a maximum of 7 slashes (8 segments), not counting any AWS rules prefix - * (3) Topics must be <= 256 bytes in length - * (4) Client id must be <= 128 bytes in length - * - * Also applies the following flow control: + * Applies the following flow control: * (1) Outbound throughput throttled to 512KB/s * (2) Outbound publish TPS throttled to 100 */ @@ -348,31 +335,37 @@ typedef void(aws_mqtt5_client_termination_completion_fn)(void *complete_ctx); /* operation completion options structures */ /** - * Completion callback options for the Publish operation + * Completion options for the Publish operation */ struct aws_mqtt5_publish_completion_options { aws_mqtt5_publish_completion_fn *completion_callback; void *completion_user_data; + + uint32_t ack_timeout_seconds_override; }; /** - * Completion callback options for the Subscribe operation + * Completion options for the Subscribe operation */ struct aws_mqtt5_subscribe_completion_options { aws_mqtt5_subscribe_completion_fn *completion_callback; void *completion_user_data; + + uint32_t ack_timeout_seconds_override; }; /** - * Completion callback options for the Unsubscribe operation + * Completion options for the Unsubscribe operation */ struct aws_mqtt5_unsubscribe_completion_options { aws_mqtt5_unsubscribe_completion_fn *completion_callback; void *completion_user_data; + + uint32_t ack_timeout_seconds_override; }; /** - * Public completion callback options for the a DISCONNECT operation + * Completion options for the a DISCONNECT operation */ struct aws_mqtt5_disconnect_completion_options { aws_mqtt5_disconnect_completion_fn *completion_callback; @@ -533,7 +526,7 @@ struct aws_mqtt5_client_options { /** * Port to establish mqtt connections to */ - uint16_t port; + uint32_t port; /** * Client bootstrap to use whenever this client establishes a connection @@ -629,7 +622,7 @@ struct aws_mqtt5_client_options { /** * Controls how the client uses mqtt5 topic aliasing. If NULL, zero-based defaults will be used. */ - struct aws_mqtt5_client_topic_alias_options *topic_aliasing_options; + const struct aws_mqtt5_client_topic_alias_options *topic_aliasing_options; /** * Callback for received publish packets @@ -655,6 +648,12 @@ struct aws_mqtt5_client_options { */ aws_mqtt5_client_termination_completion_fn *client_termination_handler; void *client_termination_handler_user_data; + + /** + * Options to override aspects of DNS resolution. If unspecified, use a default that matches the regular + * configuration but changes the refresh frequency to a value that prevents DNS pinging. + */ + struct aws_host_resolution_config *host_resolution_override; }; AWS_EXTERN_C_BEGIN @@ -786,12 +785,14 @@ AWS_MQTT_API int aws_mqtt5_negotiated_settings_init( const struct aws_byte_cursor *client_id); /** - * Makes an owning copy of a negotiated settings structure + * Makes an owning copy of a negotiated settings structure. * * @param source settings to copy from * @param dest settings to copy into. Must be in a zeroed or initialized state because it gets clean up * called on it as the first step of the copy process. * @return success/failure + * + * Used in downstream. */ AWS_MQTT_API int aws_mqtt5_negotiated_settings_copy( const struct aws_mqtt5_negotiated_settings *source, @@ -805,5 +806,6 @@ AWS_MQTT_API int aws_mqtt5_negotiated_settings_copy( AWS_MQTT_API void aws_mqtt5_negotiated_settings_clean_up(struct aws_mqtt5_negotiated_settings *negotiated_settings); AWS_EXTERN_C_END +AWS_POP_SANE_WARNING_LEVEL #endif /* AWS_MQTT_MQTT5_CLIENT_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_listener.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_listener.h index 8d0498cebd8..7eb9664c305 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_listener.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_listener.h @@ -10,6 +10,8 @@ #include <aws/mqtt/v5/mqtt5_client.h> +AWS_PUSH_SANE_WARNING_LEVEL + /* * Callback signature for when an mqtt5 listener has completely destroyed itself. */ @@ -81,5 +83,6 @@ AWS_MQTT_API struct aws_mqtt5_listener *aws_mqtt5_listener_acquire(struct aws_mq AWS_MQTT_API struct aws_mqtt5_listener *aws_mqtt5_listener_release(struct aws_mqtt5_listener *listener); AWS_EXTERN_C_END +AWS_POP_SANE_WARNING_LEVEL #endif /* AWS_MQTT_MQTT5_LISTENER_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_packet_storage.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_packet_storage.h index 9a7028f4595..354990c8e33 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_packet_storage.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_packet_storage.h @@ -6,18 +6,12 @@ * SPDX-License-Identifier: Apache-2.0. */ -/** - * DEVELOPER PREVIEW DISCLAIMER - * - * MQTT5 support is currently in **developer preview**. We encourage feedback at all times, but feedback during the - * preview window is especially valuable in shaping the final product. During the preview period we may make - * backwards-incompatible changes to the public API, but in general, this is something we will try our best to avoid. - */ - #include <aws/mqtt/mqtt.h> #include <aws/mqtt/v5/mqtt5_types.h> +AWS_PUSH_SANE_WARNING_LEVEL + struct aws_mqtt5_user_property_set { struct aws_array_list properties; }; @@ -332,5 +326,6 @@ AWS_MQTT_API void aws_mqtt5_packet_unsuback_storage_clean_up( struct aws_mqtt5_packet_unsuback_storage *unsuback_storage); AWS_EXTERN_C_END +AWS_POP_SANE_WARNING_LEVEL #endif /* AWS_MQTT_MQTT5_PACKET_STORAGE_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_types.h b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_types.h index f8db39516b2..b9ff8464024 100644 --- a/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_types.h +++ b/contrib/restricted/aws/aws-c-mqtt/include/aws/mqtt/v5/mqtt5_types.h @@ -6,19 +6,13 @@ * SPDX-License-Identifier: Apache-2.0. */ -/** - * DEVELOPER PREVIEW DISCLAIMER - * - * MQTT5 support is currently in **developer preview**. We encourage feedback at all times, but feedback during the - * preview window is especially valuable in shaping the final product. During the preview period we may make - * backwards-incompatible changes to the public API, but in general, this is something we will try our best to avoid. - */ - #include <aws/mqtt/mqtt.h> #include <aws/common/array_list.h> #include <aws/common/byte_buf.h> +AWS_PUSH_SANE_WARNING_LEVEL + /** * Some artificial (non-MQTT spec specified) limits that we place on input packets (publish, subscribe, unsubscibe) * which lets us safely do the various packet size calculations with a bare minimum of checked arithmetic. @@ -482,5 +476,6 @@ struct aws_mqtt5_packet_unsuback_view { size_t reason_code_count; const enum aws_mqtt5_unsuback_reason_code *reason_codes; }; +AWS_POP_SANE_WARNING_LEVEL #endif /* AWS_MQTT_MQTT5_TYPES_H */ diff --git a/contrib/restricted/aws/aws-c-mqtt/source/client.c b/contrib/restricted/aws/aws-c-mqtt/source/client.c index 7aea13b727f..c332c4da864 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/client.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/client.c @@ -11,6 +11,8 @@ #include <aws/mqtt/private/topic_tree.h> #include <aws/http/proxy.h> +#include <aws/http/request_response.h> +#include <aws/http/websocket.h> #include <aws/io/channel_bootstrap.h> #include <aws/io/event_loop.h> @@ -23,13 +25,9 @@ #include <inttypes.h> -#ifdef AWS_MQTT_WITH_WEBSOCKETS -# include <aws/http/request_response.h> -# include <aws/http/websocket.h> -#endif - #ifdef _MSC_VER # pragma warning(disable : 4204) +# pragma warning(disable : 4996) /* allow strncpy() */ #endif /* 3 seconds */ @@ -38,21 +36,23 @@ static const uint64_t s_default_ping_timeout_ns = 3000000000; /* 20 minutes - This is the default (and max) for AWS IoT as of 2020.02.18 */ static const uint16_t s_default_keep_alive_sec = 1200; +#define DEFAULT_MQTT311_OPERATION_TABLE_SIZE 100 + static int s_mqtt_client_connect( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, aws_mqtt_client_on_connection_complete_fn *on_connection_complete, void *userdata); /******************************************************************************* * Helper functions ******************************************************************************/ -void mqtt_connection_lock_synced_data(struct aws_mqtt_client_connection *connection) { +void mqtt_connection_lock_synced_data(struct aws_mqtt_client_connection_311_impl *connection) { int err = aws_mutex_lock(&connection->synced_data.lock); AWS_ASSERT(!err); (void)err; } -void mqtt_connection_unlock_synced_data(struct aws_mqtt_client_connection *connection) { +void mqtt_connection_unlock_synced_data(struct aws_mqtt_client_connection_311_impl *connection) { ASSERT_SYNCED_DATA_LOCK_HELD(connection); int err = aws_mutex_unlock(&connection->synced_data.lock); @@ -60,7 +60,7 @@ void mqtt_connection_unlock_synced_data(struct aws_mqtt_client_connection *conne (void)err; } -static void s_aws_mqtt_schedule_reconnect_task(struct aws_mqtt_client_connection *connection) { +static void s_aws_mqtt_schedule_reconnect_task(struct aws_mqtt_client_connection_311_impl *connection) { uint64_t next_attempt_ns = 0; aws_high_res_clock_get_ticks(&next_attempt_ns); next_attempt_ns += aws_timestamp_convert( @@ -83,7 +83,7 @@ static void s_aws_mqtt_client_destroy(struct aws_mqtt_client *client) { } void mqtt_connection_set_state( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, enum aws_mqtt_client_connection_state state) { ASSERT_SYNCED_DATA_LOCK_HELD(connection); if (connection->synced_data.state == state) { @@ -98,7 +98,7 @@ struct request_timeout_wrapper; /* used for timeout task */ struct request_timeout_task_arg { uint16_t packet_id; - struct aws_mqtt_client_connection *connection; + struct aws_mqtt_client_connection_311_impl *connection; struct request_timeout_wrapper *task_arg_wrapper; }; @@ -115,7 +115,7 @@ struct request_timeout_wrapper { static void s_request_timeout(struct aws_channel_task *channel_task, void *arg, enum aws_task_status status) { (void)channel_task; struct request_timeout_task_arg *timeout_task_arg = arg; - struct aws_mqtt_client_connection *connection = timeout_task_arg->connection; + struct aws_mqtt_client_connection_311_impl *connection = timeout_task_arg->connection; if (status == AWS_TASK_STATUS_RUN_READY) { if (timeout_task_arg->task_arg_wrapper != NULL) { @@ -138,7 +138,7 @@ static void s_request_timeout(struct aws_channel_task *channel_task, void *arg, } static struct request_timeout_task_arg *s_schedule_timeout_task( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, uint16_t packet_id) { /* schedule a timeout task to run, in case server consider the publish is not received */ struct aws_channel_task *request_timeout_task = NULL; @@ -173,6 +173,44 @@ static void s_init_statistics(struct aws_mqtt_connection_operation_statistics_im aws_atomic_store_int(&stats->unacked_operation_size_atomic, 0); } +static bool s_is_topic_shared_topic(struct aws_byte_cursor *input) { + char *input_str = (char *)input->ptr; + if (strncmp("$share/", input_str, strlen("$share/")) == 0) { + return true; + } + return false; +} + +static struct aws_string *s_get_normal_topic_from_shared_topic(struct aws_string *input) { + const char *input_char_str = aws_string_c_str(input); + size_t input_char_length = strlen(input_char_str); + size_t split_position = 7; // Start at '$share/' since we know it has to exist + while (split_position < input_char_length) { + split_position += 1; + if (input_char_str[split_position] == '/') { + break; + } + } + // If we got all the way to the end, OR there is not at least a single character + // after the second /, then it's invalid input. + if (split_position + 1 >= input_char_length) { + AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "Cannot parse shared subscription topic: Topic is not formatted correctly"); + return NULL; + } + const size_t split_delta = input_char_length - split_position; + if (split_delta > 0) { + // Annoyingly, we cannot just use 'char result_char[split_delta];' because + // MSVC doesn't support it. + char *result_char = aws_mem_calloc(input->allocator, split_delta, sizeof(char)); + strncpy(result_char, input_char_str + split_position + 1, split_delta); + struct aws_string *result_string = aws_string_new_from_c_str(input->allocator, (const char *)result_char); + aws_mem_release(input->allocator, result_char); + return result_string; + } + AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "Cannot parse shared subscription topic: Topic is not formatted correctly"); + return NULL; +} + /******************************************************************************* * Client Init ******************************************************************************/ @@ -220,7 +258,7 @@ static void s_mqtt_client_shutdown( (void)bootstrap; (void)channel; - struct aws_mqtt_client_connection *connection = user_data; + struct aws_mqtt_client_connection_311_impl *connection = user_data; AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, "id=%p: Channel has been shutdown with error code %d", (void *)connection, error_code); @@ -327,7 +365,7 @@ static void s_mqtt_client_shutdown( struct aws_mqtt_request *request = AWS_CONTAINER_OF(current, struct aws_mqtt_request, list_node); if (request->on_complete) { request->on_complete( - connection, + &connection->base, request->packet_id, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION, request->on_complete_ud); @@ -419,12 +457,13 @@ static void s_mqtt_client_shutdown( "id=%p: Initial connection attempt failed, calling callback", (void *)connection); MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_connection_complete, error_code, 0, false); + MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_connection_failure, error_code); break; default: break; } /* The connection can die now. Release the refcount */ - aws_mqtt_client_connection_release(connection); + aws_mqtt_client_connection_release(&connection->base); } } @@ -436,7 +475,7 @@ static void s_mqtt_client_shutdown( * for a CONNACK, kill it off. In the case that the connection died between scheduling this task and it being executed * the status will always be CANCELED because this task will be canceled when the owning channel goes away. */ static void s_connack_received_timeout(struct aws_channel_task *channel_task, void *arg, enum aws_task_status status) { - struct aws_mqtt_client_connection *connection = arg; + struct aws_mqtt_client_connection_311_impl *connection = arg; if (status == AWS_TASK_STATUS_RUN_READY) { bool time_out = false; @@ -472,7 +511,7 @@ static void s_mqtt_client_init( /* Setup callback contract is: if error_code is non-zero then channel is NULL. */ AWS_FATAL_ASSERT((error_code != 0) == (channel == NULL)); - struct aws_mqtt_client_connection *connection = user_data; + struct aws_mqtt_client_connection_311_impl *connection = user_data; if (error_code != AWS_OP_SUCCESS) { /* client shutdown already handles this case, so just call that. */ @@ -503,7 +542,7 @@ static void s_mqtt_client_init( mqtt_connection_unlock_synced_data(connection); } /* END CRITICAL SECTION */ - /* intall the slot and handler */ + /* install the slot and handler */ if (failed_create_slot) { AWS_LOGF_ERROR( @@ -538,6 +577,8 @@ static void s_mqtt_client_init( goto handle_error; } + aws_mqtt311_decoder_reset_for_new_connection(&connection->thread_data.decoder); + AWS_LOGF_DEBUG( AWS_LS_MQTT_CLIENT, "id=%p: Connection successfully opened, sending CONNECT packet", (void *)connection); @@ -635,6 +676,7 @@ static void s_mqtt_client_init( handle_error: MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_connection_complete, aws_last_error(), 0, false); + MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_connection_failure, aws_last_error()); aws_channel_shutdown(channel, aws_last_error()); if (message) { @@ -647,13 +689,57 @@ static void s_attempt_reconnect(struct aws_task *task, void *userdata, enum aws_ (void)task; struct aws_mqtt_reconnect_task *reconnect = userdata; - struct aws_mqtt_client_connection *connection = aws_atomic_load_ptr(&reconnect->connection_ptr); + struct aws_mqtt_client_connection_311_impl *connection = aws_atomic_load_ptr(&reconnect->connection_ptr); + /* If the task is not cancelled and a connection has not succeeded, attempt reconnect */ if (status == AWS_TASK_STATUS_RUN_READY && connection) { - /* If the task is not cancelled and a connection has not succeeded, attempt reconnect */ - mqtt_connection_lock_synced_data(connection); + /** + * Check the state and if we are disconnecting (AWS_MQTT_CLIENT_STATE_DISCONNECTING) then we want to skip it + * and abort the reconnect task (or rather, just do not try to reconnect) + */ + if (connection->synced_data.state == AWS_MQTT_CLIENT_STATE_DISCONNECTING) { + AWS_LOGF_TRACE( + AWS_LS_MQTT_CLIENT, "id=%p: Skipping reconnect: Client is trying to disconnect", (void *)connection); + + /** + * There is the nasty world where the disconnect task/function is called right when we are "reconnecting" as + * our state but we have not reconnected. When this happens, the disconnect function doesn't do anything + * beyond setting the state to AWS_MQTT_CLIENT_STATE_DISCONNECTING (aws_mqtt_client_connection_disconnect), + * meaning the disconnect callback will NOT be called nor will we release memory. + * For this reason, we have to do the callback and release of the connection here otherwise the code + * will DEADLOCK forever and that is bad. + */ + bool perform_full_destroy = false; + if (!connection->slot) { + AWS_LOGF_TRACE( + AWS_LS_MQTT_CLIENT, + "id=%p: Reconnect task called but client is disconnecting and has no slot. Finishing disconnect", + (void *)connection); + mqtt_connection_set_state(connection, AWS_MQTT_CLIENT_STATE_DISCONNECTED); + perform_full_destroy = true; + } + + aws_mem_release(reconnect->allocator, reconnect); + connection->reconnect_task = NULL; + + /* Unlock the synced data, then potentially call the disconnect callback and release the connection */ + mqtt_connection_unlock_synced_data(connection); + if (perform_full_destroy) { + MQTT_CLIENT_CALL_CALLBACK(connection, on_disconnect); + MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_closed, NULL); + aws_mqtt_client_connection_release(&connection->base); + } + return; + } + + AWS_LOGF_TRACE( + AWS_LS_MQTT_CLIENT, + "id=%p: Attempting reconnect, if it fails next attempt will be in %" PRIu64 " seconds", + (void *)connection, + connection->reconnect_timeouts.current_sec); + /* Check before multiplying to avoid potential overflow */ if (connection->reconnect_timeouts.current_sec > connection->reconnect_timeouts.max_sec / 2) { connection->reconnect_timeouts.current_sec = connection->reconnect_timeouts.max_sec; @@ -682,7 +768,7 @@ static void s_attempt_reconnect(struct aws_task *task, void *userdata, enum aws_ } } -void aws_create_reconnect_task(struct aws_mqtt_client_connection *connection) { +void aws_create_reconnect_task(struct aws_mqtt_client_connection_311_impl *connection) { if (connection->reconnect_task == NULL) { connection->reconnect_task = aws_mem_calloc(connection->allocator, 1, sizeof(struct aws_mqtt_reconnect_task)); AWS_FATAL_ASSERT(connection->reconnect_task != NULL); @@ -694,15 +780,9 @@ void aws_create_reconnect_task(struct aws_mqtt_client_connection *connection) { } } -static uint64_t s_hash_uint16_t(const void *item) { - return *(uint16_t *)item; -} +static void s_mqtt_client_connection_destroy_final(struct aws_mqtt_client_connection *base_connection) { -static bool s_uint16_t_eq(const void *a, const void *b) { - return *(uint16_t *)a == *(uint16_t *)b; -} - -static void s_mqtt_client_connection_destroy_final(struct aws_mqtt_client_connection *connection) { + struct aws_mqtt_client_connection_311_impl *connection = base_connection->impl; AWS_PRECONDITION(!connection || connection->allocator); if (!connection) { return; @@ -714,6 +794,13 @@ static void s_mqtt_client_connection_destroy_final(struct aws_mqtt_client_connec AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: Destroying connection", (void *)connection); + aws_mqtt_client_on_connection_termination_fn *termination_handler = NULL; + void *termination_handler_user_data = NULL; + if (connection->on_termination != NULL) { + termination_handler = connection->on_termination; + termination_handler_user_data = connection->on_termination_ud; + } + /* If the reconnect_task isn't freed, free it */ if (connection->reconnect_task) { aws_mem_release(connection->reconnect_task->allocator, connection->reconnect_task); @@ -738,6 +825,8 @@ static void s_mqtt_client_connection_destroy_final(struct aws_mqtt_client_connec /* Free all of the active subscriptions */ aws_mqtt_topic_tree_clean_up(&connection->thread_data.subscriptions); + aws_mqtt311_decoder_clean_up(&connection->thread_data.decoder); + aws_hash_table_clean_up(&connection->synced_data.outstanding_requests_table); /* clean up the pending_requests if it's not empty */ while (!aws_linked_list_empty(&connection->synced_data.pending_requests_list)) { @@ -746,7 +835,7 @@ static void s_mqtt_client_connection_destroy_final(struct aws_mqtt_client_connec /* Fire the callback and clean up the memory, as the connection get destroyed. */ if (request->on_complete) { request->on_complete( - connection, request->packet_id, AWS_ERROR_MQTT_CONNECTION_DESTROYED, request->on_complete_ud); + &connection->base, request->packet_id, AWS_ERROR_MQTT_CONNECTION_DESTROYED, request->on_complete_ud); } aws_memory_pool_release(&connection->synced_data.requests_pool, request); } @@ -766,6 +855,10 @@ static void s_mqtt_client_connection_destroy_final(struct aws_mqtt_client_connec /* Frees all allocated memory */ aws_mem_release(connection->allocator, connection); + + if (termination_handler != NULL) { + (*termination_handler)(termination_handler_user_data); + } } static void s_on_final_disconnect(struct aws_mqtt_client_connection *connection, void *userdata) { @@ -774,7 +867,7 @@ static void s_on_final_disconnect(struct aws_mqtt_client_connection *connection, s_mqtt_client_connection_destroy_final(connection); } -static void s_mqtt_client_connection_start_destroy(struct aws_mqtt_client_connection *connection) { +static void s_mqtt_client_connection_start_destroy(struct aws_mqtt_client_connection_311_impl *connection) { bool call_destroy_final = false; AWS_LOGF_DEBUG( @@ -806,121 +899,7 @@ static void s_mqtt_client_connection_start_destroy(struct aws_mqtt_client_connec } /* END CRITICAL SECTION */ if (call_destroy_final) { - s_mqtt_client_connection_destroy_final(connection); - } -} - -struct aws_mqtt_client_connection *aws_mqtt_client_connection_new(struct aws_mqtt_client *client) { - AWS_PRECONDITION(client); - - struct aws_mqtt_client_connection *connection = - aws_mem_calloc(client->allocator, 1, sizeof(struct aws_mqtt_client_connection)); - if (!connection) { - return NULL; - } - - AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: Creating new connection", (void *)connection); - - /* Initialize the client */ - connection->allocator = client->allocator; - aws_ref_count_init( - &connection->ref_count, connection, (aws_simple_completion_callback *)s_mqtt_client_connection_start_destroy); - connection->client = aws_mqtt_client_acquire(client); - AWS_ZERO_STRUCT(connection->synced_data); - connection->synced_data.state = AWS_MQTT_CLIENT_STATE_DISCONNECTED; - connection->reconnect_timeouts.min_sec = 1; - connection->reconnect_timeouts.current_sec = 1; - connection->reconnect_timeouts.max_sec = 128; - aws_linked_list_init(&connection->synced_data.pending_requests_list); - aws_linked_list_init(&connection->thread_data.ongoing_requests_list); - s_init_statistics(&connection->operation_statistics_impl); - - if (aws_mutex_init(&connection->synced_data.lock)) { - AWS_LOGF_ERROR( - AWS_LS_MQTT_CLIENT, - "id=%p: Failed to initialize mutex, error %d (%s)", - (void *)connection, - aws_last_error(), - aws_error_name(aws_last_error())); - goto failed_init_mutex; - } - - if (aws_mqtt_topic_tree_init(&connection->thread_data.subscriptions, connection->allocator)) { - - AWS_LOGF_ERROR( - AWS_LS_MQTT_CLIENT, - "id=%p: Failed to initialize subscriptions topic_tree, error %d (%s)", - (void *)connection, - aws_last_error(), - aws_error_name(aws_last_error())); - goto failed_init_subscriptions; - } - - if (aws_memory_pool_init( - &connection->synced_data.requests_pool, connection->allocator, 32, sizeof(struct aws_mqtt_request))) { - - AWS_LOGF_ERROR( - AWS_LS_MQTT_CLIENT, - "id=%p: Failed to initialize request pool, error %d (%s)", - (void *)connection, - aws_last_error(), - aws_error_name(aws_last_error())); - goto failed_init_requests_pool; - } - - if (aws_hash_table_init( - &connection->synced_data.outstanding_requests_table, - connection->allocator, - sizeof(struct aws_mqtt_request *), - s_hash_uint16_t, - s_uint16_t_eq, - NULL, - NULL)) { - - AWS_LOGF_ERROR( - AWS_LS_MQTT_CLIENT, - "id=%p: Failed to initialize outstanding requests table, error %d (%s)", - (void *)connection, - aws_last_error(), - aws_error_name(aws_last_error())); - goto failed_init_outstanding_requests_table; - } - - connection->loop = aws_event_loop_group_get_next_loop(client->bootstrap->event_loop_group); - - /* Initialize the handler */ - connection->handler.alloc = connection->allocator; - connection->handler.vtable = aws_mqtt_get_client_channel_vtable(); - connection->handler.impl = connection; - - return connection; - -failed_init_outstanding_requests_table: - aws_memory_pool_clean_up(&connection->synced_data.requests_pool); - -failed_init_requests_pool: - aws_mqtt_topic_tree_clean_up(&connection->thread_data.subscriptions); - -failed_init_subscriptions: - aws_mutex_clean_up(&connection->synced_data.lock); - -failed_init_mutex: - aws_mem_release(client->allocator, connection); - - return NULL; -} - -struct aws_mqtt_client_connection *aws_mqtt_client_connection_acquire(struct aws_mqtt_client_connection *connection) { - if (connection != NULL) { - aws_ref_count_acquire(&connection->ref_count); - } - - return connection; -} - -void aws_mqtt_client_connection_release(struct aws_mqtt_client_connection *connection) { - if (connection != NULL) { - aws_ref_count_release(&connection->ref_count); + s_mqtt_client_connection_destroy_final(&connection->base); } } @@ -929,7 +908,7 @@ void aws_mqtt_client_connection_release(struct aws_mqtt_client_connection *conne ******************************************************************************/ /* To configure the connection, ensure the state is DISCONNECTED or CONNECTED */ -static int s_check_connection_state_for_configuration(struct aws_mqtt_client_connection *connection) { +static int s_check_connection_state_for_configuration(struct aws_mqtt_client_connection_311_impl *connection) { int result = AWS_OP_SUCCESS; { /* BEGIN CRITICAL SECTION */ mqtt_connection_lock_synced_data(connection); @@ -948,19 +927,31 @@ static int s_check_connection_state_for_configuration(struct aws_mqtt_client_con return result; } -int aws_mqtt_client_connection_set_will( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_set_will( + void *impl, const struct aws_byte_cursor *topic, enum aws_mqtt_qos qos, bool retain, const struct aws_byte_cursor *payload) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); AWS_PRECONDITION(topic); if (s_check_connection_state_for_configuration(connection)) { return aws_raise_error(AWS_ERROR_INVALID_STATE); } + if (!aws_mqtt_is_valid_topic(topic)) { + AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "id=%p: Will topic is invalid", (void *)connection); + return aws_raise_error(AWS_ERROR_MQTT_INVALID_TOPIC); + } + + if (qos > AWS_MQTT_QOS_EXACTLY_ONCE) { + AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "id=%p: Will qos is invalid", (void *)connection); + return aws_raise_error(AWS_ERROR_MQTT_INVALID_QOS); + } + int result = AWS_OP_ERR; AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, @@ -968,11 +959,6 @@ int aws_mqtt_client_connection_set_will( (void *)connection, AWS_BYTE_CURSOR_PRI(*topic)); - if (!aws_mqtt_is_valid_topic(topic)) { - AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "id=%p: Will topic is invalid", (void *)connection); - return aws_raise_error(AWS_ERROR_MQTT_INVALID_TOPIC); - } - struct aws_byte_buf local_topic_buf; struct aws_byte_buf local_payload_buf; AWS_ZERO_STRUCT(local_topic_buf); @@ -1013,17 +999,25 @@ cleanup: return result; } -int aws_mqtt_client_connection_set_login( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_set_login( + void *impl, const struct aws_byte_cursor *username, const struct aws_byte_cursor *password) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); AWS_PRECONDITION(username); if (s_check_connection_state_for_configuration(connection)) { return aws_raise_error(AWS_ERROR_INVALID_STATE); } + if (username != NULL && aws_mqtt_validate_utf8_text(*username) == AWS_OP_ERR) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_CLIENT, "id=%p: Invalid utf8 or forbidden codepoints in username", (void *)connection); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + int result = AWS_OP_ERR; AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: Setting username and password", (void *)connection); @@ -1066,11 +1060,13 @@ cleanup: return result; } -int aws_mqtt_client_connection_set_reconnect_timeout( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_set_reconnect_timeout( + void *impl, uint64_t min_timeout, uint64_t max_timeout) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); if (s_check_connection_state_for_configuration(connection)) { return aws_raise_error(AWS_ERROR_INVALID_STATE); @@ -1088,13 +1084,38 @@ int aws_mqtt_client_connection_set_reconnect_timeout( return AWS_OP_SUCCESS; } -int aws_mqtt_client_connection_set_connection_interruption_handlers( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_set_connection_result_handlers( + void *impl, + aws_mqtt_client_on_connection_success_fn *on_connection_success, + void *on_connection_success_ud, + aws_mqtt_client_on_connection_failure_fn *on_connection_failure, + void *on_connection_failure_ud) { + + struct aws_mqtt_client_connection_311_impl *connection = impl; + + AWS_PRECONDITION(connection); + if (s_check_connection_state_for_configuration(connection)) { + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: Setting connection success and failure handlers", (void *)connection); + + connection->on_connection_success = on_connection_success; + connection->on_connection_success_ud = on_connection_success_ud; + connection->on_connection_failure = on_connection_failure; + connection->on_connection_failure_ud = on_connection_failure_ud; + + return AWS_OP_SUCCESS; +} + +static int s_aws_mqtt_client_connection_311_set_connection_interruption_handlers( + void *impl, aws_mqtt_client_on_connection_interrupted_fn *on_interrupted, void *on_interrupted_ud, aws_mqtt_client_on_connection_resumed_fn *on_resumed, void *on_resumed_ud) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); if (s_check_connection_state_for_configuration(connection)) { return aws_raise_error(AWS_ERROR_INVALID_STATE); @@ -1110,11 +1131,13 @@ int aws_mqtt_client_connection_set_connection_interruption_handlers( return AWS_OP_SUCCESS; } -int aws_mqtt_client_connection_set_connection_closed_handler( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_set_connection_closed_handler( + void *impl, aws_mqtt_client_on_connection_closed_fn *on_closed, void *on_closed_ud) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); if (s_check_connection_state_for_configuration(connection)) { return aws_raise_error(AWS_ERROR_INVALID_STATE); @@ -1127,11 +1150,13 @@ int aws_mqtt_client_connection_set_connection_closed_handler( return AWS_OP_SUCCESS; } -int aws_mqtt_client_connection_set_on_any_publish_handler( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_set_on_any_publish_handler( + void *impl, aws_mqtt_client_publish_received_fn *on_any_publish, void *on_any_publish_ud) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); { /* BEGIN CRITICAL SECTION */ mqtt_connection_lock_synced_data(connection); @@ -1156,18 +1181,38 @@ int aws_mqtt_client_connection_set_on_any_publish_handler( return AWS_OP_SUCCESS; } +static int s_aws_mqtt_client_connection_311_set_connection_termination_handler( + void *impl, + aws_mqtt_client_on_connection_termination_fn *on_termination, + void *on_termination_ud) { + + struct aws_mqtt_client_connection_311_impl *connection = impl; + + AWS_PRECONDITION(connection); + if (s_check_connection_state_for_configuration(connection)) { + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: Setting connection termination handler", (void *)connection); + + connection->on_termination = on_termination; + connection->on_termination_ud = on_termination_ud; + + return AWS_OP_SUCCESS; +} + /******************************************************************************* * Websockets ******************************************************************************/ -#ifdef AWS_MQTT_WITH_WEBSOCKETS -int aws_mqtt_client_connection_use_websockets( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_use_websockets( + void *impl, aws_mqtt_transform_websocket_handshake_fn *transformer, void *transformer_ud, aws_mqtt_validate_websocket_handshake_fn *validator, void *validator_ud) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + connection->websocket.handshake_transformer = transformer; connection->websocket.handshake_transformer_ud = transformer_ud; connection->websocket.handshake_validator = validator; @@ -1179,10 +1224,12 @@ int aws_mqtt_client_connection_use_websockets( return AWS_OP_SUCCESS; } -int aws_mqtt_client_connection_set_http_proxy_options( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_set_http_proxy_options( + void *impl, struct aws_http_proxy_options *proxy_options) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + /* If there is existing proxy options, nuke em */ if (connection->http_proxy_config) { aws_http_proxy_config_destroy(connection->http_proxy_config); @@ -1195,8 +1242,19 @@ int aws_mqtt_client_connection_set_http_proxy_options( return connection->http_proxy_config != NULL ? AWS_OP_SUCCESS : AWS_OP_ERR; } +static int s_aws_mqtt_client_connection_311_set_host_resolution_options( + void *impl, + const struct aws_host_resolution_config *host_resolution_config) { + + struct aws_mqtt_client_connection_311_impl *connection = impl; + + connection->host_resolution_config = *host_resolution_config; + + return AWS_OP_SUCCESS; +} + static void s_on_websocket_shutdown(struct aws_websocket *websocket, int error_code, void *user_data) { - struct aws_mqtt_client_connection *connection = user_data; + struct aws_mqtt_client_connection_311_impl *connection = user_data; struct aws_channel *channel = connection->slot ? connection->slot->channel : NULL; @@ -1212,7 +1270,7 @@ static void s_on_websocket_setup(const struct aws_websocket_on_connection_setup_ /* Setup callback contract is: if error_code is non-zero then websocket is NULL. */ AWS_FATAL_ASSERT((setup->error_code != 0) == (setup->websocket == NULL)); - struct aws_mqtt_client_connection *connection = user_data; + struct aws_mqtt_client_connection_311_impl *connection = user_data; struct aws_channel *channel = NULL; if (connection->websocket.handshake_request) { @@ -1243,7 +1301,7 @@ static void s_on_websocket_setup(const struct aws_websocket_on_connection_setup_ AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: Validating websocket handshake response.", (void *)connection); if (connection->websocket.handshake_validator( - connection, + &connection->base, setup->handshake_response_header_array, setup->num_handshake_response_headers, connection->websocket.handshake_validator_ud)) { @@ -1270,7 +1328,7 @@ static void s_on_websocket_setup(const struct aws_websocket_on_connection_setup_ static aws_mqtt_transform_websocket_handshake_complete_fn s_websocket_handshake_transform_complete; /* fwd declare */ -static int s_websocket_connect(struct aws_mqtt_client_connection *connection) { +static int s_websocket_connect(struct aws_mqtt_client_connection_311_impl *connection) { AWS_ASSERT(connection->websocket.enabled); /* Build websocket handshake request */ @@ -1317,7 +1375,7 @@ static void s_websocket_handshake_transform_complete( int error_code, void *complete_ctx) { - struct aws_mqtt_client_connection *connection = complete_ctx; + struct aws_mqtt_client_connection_311_impl *connection = complete_ctx; if (error_code) { AWS_LOGF_ERROR( @@ -1346,6 +1404,7 @@ static void s_websocket_handshake_transform_complete( .on_connection_setup = s_on_websocket_setup, .on_connection_shutdown = s_on_websocket_shutdown, .requested_event_loop = connection->loop, + .host_resolution_config = &connection->host_resolution_config, }; struct aws_http_proxy_options proxy_options; @@ -1370,52 +1429,26 @@ error:; s_on_websocket_setup(&websocket_setup, connection); } -#else /* AWS_MQTT_WITH_WEBSOCKETS */ -int aws_mqtt_client_connection_use_websockets( - struct aws_mqtt_client_connection *connection, - aws_mqtt_transform_websocket_handshake_fn *transformer, - void *transformer_ud, - aws_mqtt_validate_websocket_handshake_fn *validator, - void *validator_ud) { - - (void)connection; - (void)transformer; - (void)transformer_ud; - (void)validator; - (void)validator_ud; - - AWS_LOGF_ERROR( - AWS_LS_MQTT_CLIENT, - "id=%p: Cannot use websockets unless library is built with MQTT_WITH_WEBSOCKETS option.", - (void *)connection); - - return aws_raise_error(AWS_ERROR_MQTT_BUILT_WITHOUT_WEBSOCKETS); -} - -int aws_mqtt_client_connection_set_websocket_proxy_options( - struct aws_mqtt_client_connection *connection, - struct aws_http_proxy_options *proxy_options) { - - (void)connection; - (void)proxy_options; - - AWS_LOGF_ERROR( - AWS_LS_MQTT_CLIENT, - "id=%p: Cannot use websockets unless library is built with MQTT_WITH_WEBSOCKETS option.", - (void *)connection); - - return aws_raise_error(AWS_ERROR_MQTT_BUILT_WITHOUT_WEBSOCKETS); -} -#endif /* AWS_MQTT_WITH_WEBSOCKETS */ - /******************************************************************************* * Connect ******************************************************************************/ -int aws_mqtt_client_connection_connect( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_connect( + void *impl, const struct aws_mqtt_connection_options *connection_options) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + + if (connection_options == NULL) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (aws_mqtt_validate_utf8_text(connection_options->client_id) == AWS_OP_ERR) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_CLIENT, "id=%p: Invalid utf8 or forbidden codepoints in client id", (void *)connection); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + /* TODO: Do we need to support resuming the connection if user connect to the same connection & endpoint and the * clean_session is false? * If not, the broker will resume the connection in this case, and we pretend we are making a new connection, which @@ -1455,6 +1488,9 @@ int aws_mqtt_client_connection_connect( if (!connection->keep_alive_time_secs) { connection->keep_alive_time_secs = s_default_keep_alive_sec; } + connection->keep_alive_time_ns = + aws_timestamp_convert(connection->keep_alive_time_secs, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + if (!connection_options->protocol_operation_timeout_ms) { connection->operation_timeout_ns = UINT64_MAX; } else { @@ -1473,16 +1509,15 @@ int aws_mqtt_client_connection_connect( } /* Keep alive time should always be greater than the timeouts. */ - if (AWS_UNLIKELY(connection->keep_alive_time_secs * (uint64_t)AWS_TIMESTAMP_NANOS <= connection->ping_timeout_ns)) { + if (AWS_UNLIKELY(connection->keep_alive_time_ns <= connection->ping_timeout_ns)) { AWS_LOGF_FATAL( AWS_LS_MQTT_CLIENT, "id=%p: Illegal configuration, Connection keep alive %" PRIu64 "ns must be greater than the request timeouts %" PRIu64 "ns.", (void *)connection, - (uint64_t)connection->keep_alive_time_secs * (uint64_t)AWS_TIMESTAMP_NANOS, + connection->keep_alive_time_ns, connection->ping_timeout_ns); - AWS_FATAL_ASSERT( - connection->keep_alive_time_secs * (uint64_t)AWS_TIMESTAMP_NANOS > connection->ping_timeout_ns); + AWS_FATAL_ASSERT(connection->keep_alive_time_ns > connection->ping_timeout_ns); } AWS_LOGF_INFO( @@ -1557,7 +1592,7 @@ int aws_mqtt_client_connection_connect( request->packet_id); if (request->on_complete) { request->on_complete( - connection, + &connection->base, request->packet_id, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION, request->on_complete_ud); @@ -1579,14 +1614,14 @@ int aws_mqtt_client_connection_connect( } /* Begin the connecting process, acquire the connection to keep it alive until we disconnected */ - aws_mqtt_client_connection_acquire(connection); + aws_mqtt_client_connection_acquire(&connection->base); if (s_mqtt_client_connect(connection, connection_options->on_connection_complete, connection_options->user_data)) { /* * An error calling s_mqtt_client_connect should (must) be mutually exclusive with s_mqtt_client_shutdown(). * So it should be safe and correct to call release now to undo the pinning we did a few lines above. */ - aws_mqtt_client_connection_release(connection); + aws_mqtt_client_connection_release(&connection->base); /* client_id has been updated with something but it will get cleaned up when the connection gets cleaned up * so we don't need to worry about it here*/ @@ -1611,19 +1646,16 @@ error: } static int s_mqtt_client_connect( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, aws_mqtt_client_on_connection_complete_fn *on_connection_complete, void *userdata) { connection->on_connection_complete = on_connection_complete; connection->on_connection_complete_ud = userdata; int result = 0; -#ifdef AWS_MQTT_WITH_WEBSOCKETS if (connection->websocket.enabled) { result = s_websocket_connect(connection); - } else -#endif /* AWS_MQTT_WITH_WEBSOCKETS */ - { + } else { struct aws_socket_channel_bootstrap_options channel_options; AWS_ZERO_STRUCT(channel_options); channel_options.bootstrap = connection->client->bootstrap; @@ -1635,6 +1667,7 @@ static int s_mqtt_client_connect( channel_options.shutdown_callback = &s_mqtt_client_shutdown; channel_options.user_data = connection; channel_options.requested_event_loop = connection->loop; + channel_options.host_resolution_override_config = &connection->host_resolution_config; if (connection->http_proxy_config == NULL) { result = aws_client_bootstrap_new_socket_channel(&channel_options); @@ -1665,11 +1698,11 @@ static int s_mqtt_client_connect( * Reconnect DEPRECATED ******************************************************************************/ -int aws_mqtt_client_connection_reconnect( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_reconnect( + void *impl, aws_mqtt_client_on_connection_complete_fn *on_connection_complete, void *userdata) { - (void)connection; + (void)impl; (void)on_connection_complete; (void)userdata; @@ -1682,11 +1715,13 @@ int aws_mqtt_client_connection_reconnect( * Disconnect ******************************************************************************/ -int aws_mqtt_client_connection_disconnect( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_disconnect( + void *impl, aws_mqtt_client_on_disconnect_fn *on_disconnect, void *userdata) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: user called disconnect.", (void *)connection); { /* BEGIN CRITICAL SECTION */ @@ -1734,7 +1769,7 @@ static void s_on_publish_client_wrapper( /* Call out to the user callback */ if (task_topic->request.on_publish) { task_topic->request.on_publish( - task_topic->connection, topic, payload, dup, qos, retain, task_topic->request.on_publish_ud); + &task_topic->connection->base, topic, payload, dup, qos, retain, task_topic->request.on_publish_ud); } } @@ -1801,16 +1836,41 @@ static enum aws_mqtt_client_request_state s_subscribe_send(uint16_t packet_id, b } if (!task_arg->tree_updated) { - if (aws_mqtt_topic_tree_transaction_insert( - &task_arg->connection->thread_data.subscriptions, - &transaction, - topic->filter, - topic->request.qos, - s_on_publish_client_wrapper, - s_task_topic_release, - topic)) { - goto handle_error; + struct aws_byte_cursor filter_cursor = aws_byte_cursor_from_string(topic->filter); + if (s_is_topic_shared_topic(&filter_cursor)) { + struct aws_string *normal_topic = s_get_normal_topic_from_shared_topic(topic->filter); + if (normal_topic == NULL) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_CLIENT, + "id=%p: Topic is shared subscription topic but topic could not be parsed from " + "shared subscription topic.", + (void *)task_arg->connection); + goto handle_error; + } + if (aws_mqtt_topic_tree_transaction_insert( + &task_arg->connection->thread_data.subscriptions, + &transaction, + normal_topic, + topic->request.qos, + s_on_publish_client_wrapper, + s_task_topic_release, + topic)) { + aws_string_destroy(normal_topic); + goto handle_error; + } + aws_string_destroy(normal_topic); + } else { + if (aws_mqtt_topic_tree_transaction_insert( + &task_arg->connection->thread_data.subscriptions, + &transaction, + topic->filter, + topic->request.qos, + s_on_publish_client_wrapper, + s_task_topic_release, + topic)) { + goto handle_error; + } } /* If insert succeed, acquire the refcount */ aws_ref_count_acquire(&topic->ref_count); @@ -1856,11 +1916,12 @@ handle_error: } static void s_subscribe_complete( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection *connection_base, uint16_t packet_id, int error_code, void *userdata) { + struct aws_mqtt_client_connection_311_impl *connection = connection_base->impl; struct subscribe_task_arg *task_arg = userdata; struct subscribe_task_topic *topic = NULL; @@ -1887,11 +1948,16 @@ static void s_subscribe_complete( err |= aws_array_list_push_back(&cb_list, &subscription); } AWS_ASSUME(!err); - task_arg->on_suback.multi(connection, packet_id, &cb_list, error_code, task_arg->on_suback_ud); + task_arg->on_suback.multi(&connection->base, packet_id, &cb_list, error_code, task_arg->on_suback_ud); aws_array_list_clean_up(&cb_list); } else if (task_arg->on_suback.single) { task_arg->on_suback.single( - connection, packet_id, &topic->request.topic, topic->request.qos, error_code, task_arg->on_suback_ud); + &connection->base, + packet_id, + &topic->request.topic, + topic->request.qos, + error_code, + task_arg->on_suback_ud); } for (size_t i = 0; i < list_len; i++) { aws_array_list_get_at(&task_arg->topics, &topic, i); @@ -1902,14 +1968,21 @@ static void s_subscribe_complete( aws_mem_release(task_arg->connection->allocator, task_arg); } -uint16_t aws_mqtt_client_connection_subscribe_multiple( - struct aws_mqtt_client_connection *connection, +static uint16_t s_aws_mqtt_client_connection_311_subscribe_multiple( + void *impl, const struct aws_array_list *topic_filters, aws_mqtt_suback_multi_fn *on_suback, void *on_suback_ud) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); + if (topic_filters == NULL || aws_array_list_length(topic_filters) == 0) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return 0; + } + struct subscribe_task_arg *task_arg = aws_mem_calloc(connection->allocator, 1, sizeof(struct subscribe_task_arg)); if (!task_arg) { return 0; @@ -2026,11 +2099,12 @@ handle_error: ******************************************************************************/ static void s_subscribe_single_complete( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection *connection_base, uint16_t packet_id, int error_code, void *userdata) { + struct aws_mqtt_client_connection_311_impl *connection = connection_base->impl; struct subscribe_task_arg *task_arg = userdata; AWS_LOGF_DEBUG( @@ -2047,7 +2121,13 @@ static void s_subscribe_single_complete( if (task_arg->on_suback.single) { AWS_ASSUME(aws_string_is_valid(topic->filter)); aws_mqtt_suback_fn *suback = task_arg->on_suback.single; - suback(connection, packet_id, &topic->request.topic, topic->request.qos, error_code, task_arg->on_suback_ud); + suback( + &connection->base, + packet_id, + &topic->request.topic, + topic->request.qos, + error_code, + task_arg->on_suback_ud); } s_task_topic_release(topic); aws_array_list_clean_up(&task_arg->topics); @@ -2055,8 +2135,8 @@ static void s_subscribe_single_complete( aws_mem_release(task_arg->connection->allocator, task_arg); } -uint16_t aws_mqtt_client_connection_subscribe( - struct aws_mqtt_client_connection *connection, +static uint16_t s_aws_mqtt_client_connection_311_subscribe( + void *impl, const struct aws_byte_cursor *topic_filter, enum aws_mqtt_qos qos, aws_mqtt_client_publish_received_fn *on_publish, @@ -2065,6 +2145,8 @@ uint16_t aws_mqtt_client_connection_subscribe( aws_mqtt_suback_fn *on_suback, void *on_suback_ud) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); if (!aws_mqtt_is_valid_topic_filter(topic_filter)) { @@ -2168,175 +2250,6 @@ handle_error: } /******************************************************************************* - * Subscribe Local - ******************************************************************************/ - -/* The lifetime of this struct is from subscribe -> suback */ -struct subscribe_local_task_arg { - - struct aws_mqtt_client_connection *connection; - - struct subscribe_task_topic *task_topic; - - aws_mqtt_suback_fn *on_suback; - void *on_suback_ud; -}; - -static enum aws_mqtt_client_request_state s_subscribe_local_send( - uint16_t packet_id, - bool is_first_attempt, - void *userdata) { - - (void)is_first_attempt; - - struct subscribe_local_task_arg *task_arg = userdata; - - AWS_LOGF_TRACE( - AWS_LS_MQTT_CLIENT, - "id=%p: Attempting save of local subscribe %" PRIu16 " (%s)", - (void *)task_arg->connection, - packet_id, - is_first_attempt ? "first attempt" : "redo"); - - struct subscribe_task_topic *topic = task_arg->task_topic; - if (aws_mqtt_topic_tree_insert( - &task_arg->connection->thread_data.subscriptions, - topic->filter, - topic->request.qos, - s_on_publish_client_wrapper, - s_task_topic_release, - topic)) { - - return AWS_MQTT_CLIENT_REQUEST_ERROR; - } - aws_ref_count_acquire(&topic->ref_count); - - return AWS_MQTT_CLIENT_REQUEST_COMPLETE; -} - -static void s_subscribe_local_complete( - struct aws_mqtt_client_connection *connection, - uint16_t packet_id, - int error_code, - void *userdata) { - - struct subscribe_local_task_arg *task_arg = userdata; - - AWS_LOGF_DEBUG( - AWS_LS_MQTT_CLIENT, - "id=%p: Local subscribe %" PRIu16 " completed with error code %d", - (void *)connection, - packet_id, - error_code); - - struct subscribe_task_topic *topic = task_arg->task_topic; - if (task_arg->on_suback) { - aws_mqtt_suback_fn *suback = task_arg->on_suback; - suback(connection, packet_id, &topic->request.topic, topic->request.qos, error_code, task_arg->on_suback_ud); - } - s_task_topic_release(topic); - - aws_mem_release(task_arg->connection->allocator, task_arg); -} - -uint16_t aws_mqtt_client_connection_subscribe_local( - struct aws_mqtt_client_connection *connection, - const struct aws_byte_cursor *topic_filter, - aws_mqtt_client_publish_received_fn *on_publish, - void *on_publish_ud, - aws_mqtt_userdata_cleanup_fn *on_ud_cleanup, - aws_mqtt_suback_fn *on_suback, - void *on_suback_ud) { - - AWS_PRECONDITION(connection); - - if (!aws_mqtt_is_valid_topic_filter(topic_filter)) { - aws_raise_error(AWS_ERROR_MQTT_INVALID_TOPIC); - return 0; - } - - struct subscribe_task_topic *task_topic = NULL; - - struct subscribe_local_task_arg *task_arg = - aws_mem_calloc(connection->allocator, 1, sizeof(struct subscribe_local_task_arg)); - - if (!task_arg) { - goto handle_error; - } - AWS_ZERO_STRUCT(*task_arg); - - task_arg->connection = connection; - task_arg->on_suback = on_suback; - task_arg->on_suback_ud = on_suback_ud; - task_topic = aws_mem_calloc(connection->allocator, 1, sizeof(struct subscribe_task_topic)); - if (!task_topic) { - goto handle_error; - } - aws_ref_count_init(&task_topic->ref_count, task_topic, (aws_simple_completion_callback *)s_task_topic_clean_up); - task_arg->task_topic = task_topic; - - task_topic->filter = aws_string_new_from_array(connection->allocator, topic_filter->ptr, topic_filter->len); - if (!task_topic->filter) { - goto handle_error; - } - - task_topic->connection = connection; - task_topic->is_local = true; - task_topic->request.topic = aws_byte_cursor_from_string(task_topic->filter); - task_topic->request.on_publish = on_publish; - task_topic->request.on_cleanup = on_ud_cleanup; - task_topic->request.on_publish_ud = on_publish_ud; - - /* Calculate the size of the (local) subscribe packet - * The fixed header is 2 bytes, the packet ID is 2 bytes - * the topic filter is always 3 bytes (1 for QoS, 2 for Length MSB/LSB) - * - plus the size of the topic filter */ - uint64_t subscribe_packet_size = 7 + topic_filter->len; - - uint16_t packet_id = mqtt_create_request( - task_arg->connection, - s_subscribe_local_send, - task_arg, - &s_subscribe_local_complete, - task_arg, - false, /* noRetry */ - subscribe_packet_size); - - if (packet_id == 0) { - AWS_LOGF_ERROR( - AWS_LS_MQTT_CLIENT, - "id=%p: Failed to start local subscribe on topic " PRInSTR " with error %s", - (void *)connection, - AWS_BYTE_CURSOR_PRI(task_topic->request.topic), - aws_error_debug_str(aws_last_error())); - goto handle_error; - } - - AWS_LOGF_DEBUG( - AWS_LS_MQTT_CLIENT, - "id=%p: Starting local subscribe %" PRIu16 " on topic " PRInSTR, - (void *)connection, - packet_id, - AWS_BYTE_CURSOR_PRI(task_topic->request.topic)); - return packet_id; - -handle_error: - - if (task_topic) { - if (task_topic->filter) { - aws_string_destroy(task_topic->filter); - } - aws_mem_release(connection->allocator, task_topic); - } - - if (task_arg) { - aws_mem_release(connection->allocator, task_arg); - } - - return 0; -} - -/******************************************************************************* * Resubscribe ******************************************************************************/ @@ -2452,11 +2365,13 @@ handle_error: } static void s_resubscribe_complete( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection *connection_base, uint16_t packet_id, int error_code, void *userdata) { + struct aws_mqtt_client_connection_311_impl *connection = connection_base->impl; + struct subscribe_task_arg *task_arg = userdata; const size_t list_len = aws_array_list_length(&task_arg->topics); @@ -2487,11 +2402,16 @@ static void s_resubscribe_complete( err |= aws_array_list_push_back(&cb_list, &subscription); } AWS_ASSUME(!err); - task_arg->on_suback.multi(connection, packet_id, &cb_list, error_code, task_arg->on_suback_ud); + task_arg->on_suback.multi(&connection->base, packet_id, &cb_list, error_code, task_arg->on_suback_ud); aws_array_list_clean_up(&cb_list); } else if (task_arg->on_suback.single) { task_arg->on_suback.single( - connection, packet_id, &topic->request.topic, topic->request.qos, error_code, task_arg->on_suback_ud); + &connection->base, + packet_id, + &topic->request.topic, + topic->request.qos, + error_code, + task_arg->on_suback_ud); } clean_up: @@ -2507,11 +2427,13 @@ clean_up: aws_mem_release(task_arg->connection->allocator, task_arg); } -uint16_t aws_mqtt_resubscribe_existing_topics( - struct aws_mqtt_client_connection *connection, +static uint16_t s_aws_mqtt_311_resubscribe_existing_topics( + void *impl, aws_mqtt_suback_multi_fn *on_suback, void *on_suback_ud) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + struct subscribe_task_arg *task_arg = aws_mem_calloc(connection->allocator, 1, sizeof(struct subscribe_task_arg)); if (!task_arg) { AWS_LOGF_ERROR( @@ -2569,10 +2491,10 @@ handle_error: ******************************************************************************/ struct unsubscribe_task_arg { - struct aws_mqtt_client_connection *connection; + struct aws_mqtt_client_connection_311_impl *connection; struct aws_string *filter_string; struct aws_byte_cursor filter; - bool is_local; + /* Packet to populate */ struct aws_mqtt_packet_unsubscribe unsubscribe; @@ -2611,62 +2533,89 @@ static enum aws_mqtt_client_request_state s_unsubscribe_send( if (!task_arg->tree_updated) { struct subscribe_task_topic *topic; - if (aws_mqtt_topic_tree_transaction_remove( - &task_arg->connection->thread_data.subscriptions, &transaction, &task_arg->filter, (void **)&topic)) { - goto handle_error; - } - task_arg->is_local = topic ? topic->is_local : false; - } - - if (!task_arg->is_local) { - if (task_arg->unsubscribe.fixed_header.packet_type == 0) { - /* If unsubscribe packet is uninitialized, init it */ - if (aws_mqtt_packet_unsubscribe_init(&task_arg->unsubscribe, task_arg->connection->allocator, packet_id)) { + if (s_is_topic_shared_topic(&task_arg->filter)) { + struct aws_string *shared_topic = + aws_string_new_from_cursor(task_arg->connection->allocator, &task_arg->filter); + struct aws_string *normal_topic = s_get_normal_topic_from_shared_topic(shared_topic); + if (normal_topic == NULL) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_CLIENT, + "id=%p: Topic is shared subscription topic but topic could not be parsed from " + "shared subscription topic.", + (void *)task_arg->connection); + aws_string_destroy(shared_topic); goto handle_error; } - if (aws_mqtt_packet_unsubscribe_add_topic(&task_arg->unsubscribe, task_arg->filter)) { + struct aws_byte_cursor normal_topic_cursor = aws_byte_cursor_from_string(normal_topic); + if (aws_mqtt_topic_tree_transaction_remove( + &task_arg->connection->thread_data.subscriptions, + &transaction, + &normal_topic_cursor, + (void **)&topic)) { + aws_string_destroy(shared_topic); + aws_string_destroy(normal_topic); + goto handle_error; + } + aws_string_destroy(shared_topic); + aws_string_destroy(normal_topic); + } else { + if (aws_mqtt_topic_tree_transaction_remove( + &task_arg->connection->thread_data.subscriptions, + &transaction, + &task_arg->filter, + (void **)&topic)) { goto handle_error; } } + } - message = mqtt_get_message_for_packet(task_arg->connection, &task_arg->unsubscribe.fixed_header); - if (!message) { + if (task_arg->unsubscribe.fixed_header.packet_type == 0) { + /* If unsubscribe packet is uninitialized, init it */ + if (aws_mqtt_packet_unsubscribe_init(&task_arg->unsubscribe, task_arg->connection->allocator, packet_id)) { goto handle_error; } - - if (aws_mqtt_packet_unsubscribe_encode(&message->message_data, &task_arg->unsubscribe)) { + if (aws_mqtt_packet_unsubscribe_add_topic(&task_arg->unsubscribe, task_arg->filter)) { goto handle_error; } + } - if (aws_channel_slot_send_message(task_arg->connection->slot, message, AWS_CHANNEL_DIR_WRITE)) { - goto handle_error; - } + message = mqtt_get_message_for_packet(task_arg->connection, &task_arg->unsubscribe.fixed_header); + if (!message) { + goto handle_error; + } - /* TODO: timing should start from the message written into the socket, which is aws_io_message->on_completion - * invoked, but there are bugs in the websocket handler (and maybe also the h1 handler?) where we don't properly - * fire the on_completion callbacks. */ - struct request_timeout_task_arg *timeout_task_arg = s_schedule_timeout_task(task_arg->connection, packet_id); - if (!timeout_task_arg) { - return AWS_MQTT_CLIENT_REQUEST_ERROR; - } + if (aws_mqtt_packet_unsubscribe_encode(&message->message_data, &task_arg->unsubscribe)) { + goto handle_error; + } - /* - * Set up mutual references between the operation task args and the timeout task args. Whoever runs first - * "wins", does its logic, and then breaks the connection between the two. - */ - task_arg->timeout_wrapper.timeout_task_arg = timeout_task_arg; - timeout_task_arg->task_arg_wrapper = &task_arg->timeout_wrapper; + if (aws_channel_slot_send_message(task_arg->connection->slot, message, AWS_CHANNEL_DIR_WRITE)) { + goto handle_error; + } + + /* TODO: timing should start from the message written into the socket, which is aws_io_message->on_completion + * invoked, but there are bugs in the websocket handler (and maybe also the h1 handler?) where we don't properly + * fire the on_completion callbacks. */ + struct request_timeout_task_arg *timeout_task_arg = s_schedule_timeout_task(task_arg->connection, packet_id); + if (!timeout_task_arg) { + return AWS_MQTT_CLIENT_REQUEST_ERROR; } + /* + * Set up mutual references between the operation task args and the timeout task args. Whoever runs first + * "wins", does its logic, and then breaks the connection between the two. + */ + task_arg->timeout_wrapper.timeout_task_arg = timeout_task_arg; + timeout_task_arg->task_arg_wrapper = &task_arg->timeout_wrapper; + if (!task_arg->tree_updated) { aws_mqtt_topic_tree_transaction_commit(&task_arg->connection->thread_data.subscriptions, &transaction); task_arg->tree_updated = true; } aws_array_list_clean_up(&transaction); - /* If the subscribe is local-only, don't wait for a SUBACK to come back. */ - return task_arg->is_local ? AWS_MQTT_CLIENT_REQUEST_COMPLETE : AWS_MQTT_CLIENT_REQUEST_ONGOING; + + return AWS_MQTT_CLIENT_REQUEST_ONGOING; handle_error: @@ -2682,11 +2631,13 @@ handle_error: } static void s_unsubscribe_complete( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection *connection_base, uint16_t packet_id, int error_code, void *userdata) { + struct aws_mqtt_client_connection_311_impl *connection = connection_base->impl; + struct unsubscribe_task_arg *task_arg = userdata; AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: Unsubscribe %" PRIu16 " complete", (void *)connection, packet_id); @@ -2703,7 +2654,7 @@ static void s_unsubscribe_complete( } if (task_arg->on_unsuback) { - task_arg->on_unsuback(connection, packet_id, error_code, task_arg->on_unsuback_ud); + task_arg->on_unsuback(&connection->base, packet_id, error_code, task_arg->on_unsuback_ud); } aws_string_destroy(task_arg->filter_string); @@ -2711,12 +2662,14 @@ static void s_unsubscribe_complete( aws_mem_release(task_arg->connection->allocator, task_arg); } -uint16_t aws_mqtt_client_connection_unsubscribe( - struct aws_mqtt_client_connection *connection, +static uint16_t s_aws_mqtt_client_connection_311_unsubscribe( + void *impl, const struct aws_byte_cursor *topic_filter, aws_mqtt_op_complete_fn *on_unsuback, void *on_unsuback_ud) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); if (!aws_mqtt_is_valid_topic_filter(topic_filter)) { @@ -2775,7 +2728,7 @@ handle_error: ******************************************************************************/ struct publish_task_arg { - struct aws_mqtt_client_connection *connection; + struct aws_mqtt_client_connection_311_impl *connection; struct aws_string *topic_string; struct aws_byte_cursor topic; enum aws_mqtt_qos qos; @@ -2794,7 +2747,7 @@ struct publish_task_arg { /* should only be called by tests */ static int s_get_stuff_from_outstanding_requests_table( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, uint16_t packet_id, struct aws_allocator *allocator, struct aws_byte_buf *result_buf, @@ -2829,29 +2782,29 @@ static int s_get_stuff_from_outstanding_requests_table( /* should only be called by tests */ int aws_mqtt_client_get_payload_for_outstanding_publish_packet( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection *connection_base, uint16_t packet_id, struct aws_allocator *allocator, struct aws_byte_buf *result) { AWS_ZERO_STRUCT(*result); - return s_get_stuff_from_outstanding_requests_table(connection, packet_id, allocator, result, NULL); + return s_get_stuff_from_outstanding_requests_table(connection_base->impl, packet_id, allocator, result, NULL); } /* should only be called by tests */ int aws_mqtt_client_get_topic_for_outstanding_publish_packet( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection *connection_base, uint16_t packet_id, struct aws_allocator *allocator, struct aws_string **result) { *result = NULL; - return s_get_stuff_from_outstanding_requests_table(connection, packet_id, allocator, NULL, result); + return s_get_stuff_from_outstanding_requests_table(connection_base->impl, packet_id, allocator, NULL, result); } static enum aws_mqtt_client_request_state s_publish_send(uint16_t packet_id, bool is_first_attempt, void *userdata) { struct publish_task_arg *task_arg = userdata; - struct aws_mqtt_client_connection *connection = task_arg->connection; + struct aws_mqtt_client_connection_311_impl *connection = task_arg->connection; AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, @@ -2877,6 +2830,8 @@ static enum aws_mqtt_client_request_state s_publish_send(uint16_t packet_id, boo return AWS_MQTT_CLIENT_REQUEST_ERROR; } + } else { + aws_mqtt_packet_publish_set_dup(&task_arg->publish); } struct aws_io_message *message = mqtt_get_message_for_packet(task_arg->connection, &task_arg->publish.fixed_header); @@ -2943,16 +2898,19 @@ static enum aws_mqtt_client_request_state s_publish_send(uint16_t packet_id, boo } static void s_publish_complete( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection *connection_base, uint16_t packet_id, int error_code, void *userdata) { + + struct aws_mqtt_client_connection_311_impl *connection = connection_base->impl; + struct publish_task_arg *task_arg = userdata; AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: Publish %" PRIu16 " complete", (void *)connection, packet_id); if (task_arg->on_complete) { - task_arg->on_complete(connection, packet_id, error_code, task_arg->userdata); + task_arg->on_complete(&connection->base, packet_id, error_code, task_arg->userdata); } /* @@ -2971,8 +2929,8 @@ static void s_publish_complete( aws_mem_release(connection->allocator, task_arg); } -uint16_t aws_mqtt_client_connection_publish( - struct aws_mqtt_client_connection *connection, +static uint16_t s_aws_mqtt_client_connection_311_publish( + void *impl, const struct aws_byte_cursor *topic, enum aws_mqtt_qos qos, bool retain, @@ -2980,6 +2938,8 @@ uint16_t aws_mqtt_client_connection_publish( aws_mqtt_op_complete_fn *on_complete, void *userdata) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + AWS_PRECONDITION(connection); if (!aws_mqtt_is_valid_topic(topic)) { @@ -2987,6 +2947,11 @@ uint16_t aws_mqtt_client_connection_publish( return 0; } + if (qos > AWS_MQTT_QOS_EXACTLY_ONCE) { + aws_raise_error(AWS_ERROR_MQTT_INVALID_QOS); + return 0; + } + struct publish_task_arg *arg = aws_mem_calloc(connection->allocator, 1, sizeof(struct publish_task_arg)); if (!arg) { return 0; @@ -2997,7 +2962,14 @@ uint16_t aws_mqtt_client_connection_publish( arg->topic = aws_byte_cursor_from_string(arg->topic_string); arg->qos = qos; arg->retain = retain; - if (aws_byte_buf_init_copy_from_cursor(&arg->payload_buf, connection->allocator, *payload)) { + + struct aws_byte_cursor payload_cursor; + AWS_ZERO_STRUCT(payload_cursor); + if (payload != NULL) { + payload_cursor = *payload; + } + + if (aws_byte_buf_init_copy_from_cursor(&arg->payload_buf, connection->allocator, payload_cursor)) { goto handle_error; } arg->payload = aws_byte_cursor_from_buf(&arg->payload_buf); @@ -3052,7 +3024,7 @@ handle_error: ******************************************************************************/ static void s_pingresp_received_timeout(struct aws_channel_task *channel_task, void *arg, enum aws_task_status status) { - struct aws_mqtt_client_connection *connection = arg; + struct aws_mqtt_client_connection_311_impl *connection = arg; if (status == AWS_TASK_STATUS_RUN_READY) { /* Check that a pingresp has been received since pingreq was sent */ @@ -3072,7 +3044,7 @@ static enum aws_mqtt_client_request_state s_pingreq_send(uint16_t packet_id, boo (void)is_first_attempt; AWS_PRECONDITION(is_first_attempt); - struct aws_mqtt_client_connection *connection = userdata; + struct aws_mqtt_client_connection_311_impl *connection = userdata; AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: pingreq send", (void *)connection); struct aws_mqtt_packet_connection pingreq; @@ -3115,7 +3087,7 @@ error: return AWS_MQTT_CLIENT_REQUEST_ERROR; } -int aws_mqtt_client_connection_ping(struct aws_mqtt_client_connection *connection) { +int aws_mqtt_client_connection_ping(struct aws_mqtt_client_connection_311_impl *connection) { AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: Starting ping", (void *)connection); @@ -3132,7 +3104,7 @@ int aws_mqtt_client_connection_ping(struct aws_mqtt_client_connection *connectio ******************************************************************************/ void aws_mqtt_connection_statistics_change_operation_statistic_state( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, struct aws_mqtt_request *request, enum aws_mqtt_operation_statistic_state_flags new_state_flags) { @@ -3190,9 +3162,12 @@ void aws_mqtt_connection_statistics_change_operation_statistic_state( } } -int aws_mqtt_client_connection_get_stats( - struct aws_mqtt_client_connection *connection, +static int s_aws_mqtt_client_connection_311_get_stats( + void *impl, struct aws_mqtt_connection_operation_statistics *stats) { + + struct aws_mqtt_client_connection_311_impl *connection = impl; + // Error checking if (!connection) { AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "Invalid MQTT311 connection used when trying to get operation statistics"); @@ -3219,7 +3194,7 @@ int aws_mqtt_client_connection_get_stats( } int aws_mqtt_client_connection_set_on_operation_statistics_handler( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, aws_mqtt_on_operation_statistics_fn *on_operation_statistics, void *on_operation_statistics_ud) { @@ -3230,3 +3205,158 @@ int aws_mqtt_client_connection_set_on_operation_statistics_handler( return AWS_OP_SUCCESS; } + +static struct aws_mqtt_client_connection *s_aws_mqtt_client_connection_311_acquire(void *impl) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + + aws_ref_count_acquire(&connection->ref_count); + + return &connection->base; +} + +static void s_aws_mqtt_client_connection_311_release(void *impl) { + struct aws_mqtt_client_connection_311_impl *connection = impl; + + aws_ref_count_release(&connection->ref_count); +} + +static struct aws_mqtt_client_connection_vtable s_aws_mqtt_client_connection_311_vtable = { + .acquire_fn = s_aws_mqtt_client_connection_311_acquire, + .release_fn = s_aws_mqtt_client_connection_311_release, + .set_will_fn = s_aws_mqtt_client_connection_311_set_will, + .set_login_fn = s_aws_mqtt_client_connection_311_set_login, + .use_websockets_fn = s_aws_mqtt_client_connection_311_use_websockets, + .set_http_proxy_options_fn = s_aws_mqtt_client_connection_311_set_http_proxy_options, + .set_host_resolution_options_fn = s_aws_mqtt_client_connection_311_set_host_resolution_options, + .set_reconnect_timeout_fn = s_aws_mqtt_client_connection_311_set_reconnect_timeout, + .set_connection_result_handlers = s_aws_mqtt_client_connection_311_set_connection_result_handlers, + .set_connection_interruption_handlers_fn = s_aws_mqtt_client_connection_311_set_connection_interruption_handlers, + .set_connection_closed_handler_fn = s_aws_mqtt_client_connection_311_set_connection_closed_handler, + .set_on_any_publish_handler_fn = s_aws_mqtt_client_connection_311_set_on_any_publish_handler, + .set_connection_termination_handler_fn = s_aws_mqtt_client_connection_311_set_connection_termination_handler, + .connect_fn = s_aws_mqtt_client_connection_311_connect, + .reconnect_fn = s_aws_mqtt_client_connection_311_reconnect, + .disconnect_fn = s_aws_mqtt_client_connection_311_disconnect, + .subscribe_multiple_fn = s_aws_mqtt_client_connection_311_subscribe_multiple, + .subscribe_fn = s_aws_mqtt_client_connection_311_subscribe, + .resubscribe_existing_topics_fn = s_aws_mqtt_311_resubscribe_existing_topics, + .unsubscribe_fn = s_aws_mqtt_client_connection_311_unsubscribe, + .publish_fn = s_aws_mqtt_client_connection_311_publish, + .get_stats_fn = s_aws_mqtt_client_connection_311_get_stats, +}; + +static struct aws_mqtt_client_connection_vtable *s_aws_mqtt_client_connection_311_vtable_ptr = + &s_aws_mqtt_client_connection_311_vtable; + +struct aws_mqtt_client_connection *aws_mqtt_client_connection_new(struct aws_mqtt_client *client) { + AWS_PRECONDITION(client); + + struct aws_mqtt_client_connection_311_impl *connection = + aws_mem_calloc(client->allocator, 1, sizeof(struct aws_mqtt_client_connection_311_impl)); + if (!connection) { + return NULL; + } + + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: Creating new mqtt 311 connection", (void *)connection); + + /* Initialize the client */ + connection->allocator = client->allocator; + connection->base.vtable = s_aws_mqtt_client_connection_311_vtable_ptr; + connection->base.impl = connection; + aws_ref_count_init( + &connection->ref_count, connection, (aws_simple_completion_callback *)s_mqtt_client_connection_start_destroy); + connection->client = aws_mqtt_client_acquire(client); + + AWS_ZERO_STRUCT(connection->synced_data); + connection->synced_data.state = AWS_MQTT_CLIENT_STATE_DISCONNECTED; + connection->reconnect_timeouts.min_sec = 1; + connection->reconnect_timeouts.current_sec = 1; + connection->reconnect_timeouts.max_sec = 128; + aws_linked_list_init(&connection->synced_data.pending_requests_list); + aws_linked_list_init(&connection->thread_data.ongoing_requests_list); + s_init_statistics(&connection->operation_statistics_impl); + + if (aws_mutex_init(&connection->synced_data.lock)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_CLIENT, + "id=%p: Failed to initialize mutex, error %d (%s)", + (void *)connection, + aws_last_error(), + aws_error_name(aws_last_error())); + goto failed_init_mutex; + } + + struct aws_mqtt311_decoder_options config = { + .packet_handlers = aws_mqtt311_get_default_packet_handlers(), + .handler_user_data = connection, + }; + aws_mqtt311_decoder_init(&connection->thread_data.decoder, client->allocator, &config); + + if (aws_mqtt_topic_tree_init(&connection->thread_data.subscriptions, connection->allocator)) { + + AWS_LOGF_ERROR( + AWS_LS_MQTT_CLIENT, + "id=%p: Failed to initialize subscriptions topic_tree, error %d (%s)", + (void *)connection, + aws_last_error(), + aws_error_name(aws_last_error())); + goto failed_init_subscriptions; + } + + if (aws_memory_pool_init( + &connection->synced_data.requests_pool, connection->allocator, 32, sizeof(struct aws_mqtt_request))) { + + AWS_LOGF_ERROR( + AWS_LS_MQTT_CLIENT, + "id=%p: Failed to initialize request pool, error %d (%s)", + (void *)connection, + aws_last_error(), + aws_error_name(aws_last_error())); + goto failed_init_requests_pool; + } + + if (aws_hash_table_init( + &connection->synced_data.outstanding_requests_table, + connection->allocator, + DEFAULT_MQTT311_OPERATION_TABLE_SIZE, + aws_mqtt_hash_uint16_t, + aws_mqtt_compare_uint16_t_eq, + NULL, + NULL)) { + + AWS_LOGF_ERROR( + AWS_LS_MQTT_CLIENT, + "id=%p: Failed to initialize outstanding requests table, error %d (%s)", + (void *)connection, + aws_last_error(), + aws_error_name(aws_last_error())); + goto failed_init_outstanding_requests_table; + } + + connection->loop = aws_event_loop_group_get_next_loop(client->bootstrap->event_loop_group); + + connection->host_resolution_config = aws_host_resolver_init_default_resolution_config(); + connection->host_resolution_config.resolve_frequency_ns = + aws_timestamp_convert(connection->reconnect_timeouts.max_sec, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + + /* Initialize the handler */ + connection->handler.alloc = connection->allocator; + connection->handler.vtable = aws_mqtt_get_client_channel_vtable(); + connection->handler.impl = connection; + + return &connection->base; + +failed_init_outstanding_requests_table: + aws_memory_pool_clean_up(&connection->synced_data.requests_pool); + +failed_init_requests_pool: + aws_mqtt_topic_tree_clean_up(&connection->thread_data.subscriptions); + +failed_init_subscriptions: + aws_mutex_clean_up(&connection->synced_data.lock); + +failed_init_mutex: + aws_mem_release(client->allocator, connection); + + return NULL; +} diff --git a/contrib/restricted/aws/aws-c-mqtt/source/client_channel_handler.c b/contrib/restricted/aws/aws-c-mqtt/source/client_channel_handler.c index f9c01cbd454..2719f41c6c3 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/client_channel_handler.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/client_channel_handler.c @@ -21,56 +21,135 @@ #endif /******************************************************************************* - * Packet State Machine + * Static Helper functions ******************************************************************************/ -typedef int(packet_handler_fn)(struct aws_mqtt_client_connection *connection, struct aws_byte_cursor message_cursor); +/* Caches the socket write time for ping scheduling purposes */ +static void s_update_next_ping_time(struct aws_mqtt_client_connection_311_impl *connection) { + if (connection->slot != NULL && connection->slot->channel != NULL) { + aws_channel_current_clock_time(connection->slot->channel, &connection->next_ping_time); + aws_add_u64_checked(connection->next_ping_time, connection->keep_alive_time_ns, &connection->next_ping_time); + } +} -static int s_packet_handler_default( - struct aws_mqtt_client_connection *connection, - struct aws_byte_cursor message_cursor) { - (void)connection; +/* Caches the request send time. The `request_send_timestamp` will be used to push off ping request on request complete. + */ +static void s_update_request_send_time(struct aws_mqtt_request *request) { + if (request->connection != NULL && request->connection->slot != NULL && + request->connection->slot->channel != NULL) { + aws_channel_current_clock_time(request->connection->slot->channel, &request->request_send_timestamp); + } +} + +/* push off next ping time on ack received to last_request_send_timestamp_ns + keep_alive_time_ns + * The function must be called in critical section. */ +static void s_pushoff_next_ping_time( + struct aws_mqtt_client_connection_311_impl *connection, + uint64_t last_request_send_timestamp_ns) { + ASSERT_SYNCED_DATA_LOCK_HELD(connection); + aws_add_u64_checked( + last_request_send_timestamp_ns, connection->keep_alive_time_ns, &last_request_send_timestamp_ns); + if (last_request_send_timestamp_ns > connection->next_ping_time) { + connection->next_ping_time = last_request_send_timestamp_ns; + } +} + +/******************************************************************************* + * Packet State Machine + ******************************************************************************/ + +static int s_packet_handler_default(struct aws_byte_cursor message_cursor, void *user_data) { (void)message_cursor; + struct aws_mqtt_client_connection_311_impl *connection = user_data; AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "id=%p: Unhandled packet type received", (void *)connection); return aws_raise_error(AWS_ERROR_MQTT_INVALID_PACKET_TYPE); } static void s_on_time_to_ping(struct aws_channel_task *channel_task, void *arg, enum aws_task_status status); -static void s_schedule_ping(struct aws_mqtt_client_connection *connection) { +static void s_schedule_ping(struct aws_mqtt_client_connection_311_impl *connection) { aws_channel_task_init(&connection->ping_task, s_on_time_to_ping, connection, "mqtt_ping"); uint64_t now = 0; aws_channel_current_clock_time(connection->slot->channel, &now); - AWS_LOGF_TRACE( - AWS_LS_MQTT_CLIENT, "id=%p: Scheduling PING. current timestamp is %" PRIu64, (void *)connection, now); - uint64_t schedule_time = - now + aws_timestamp_convert(connection->keep_alive_time_secs, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + AWS_LOGF_TRACE( + AWS_LS_MQTT_CLIENT, "id=%p: Scheduling PING task. current timestamp is %" PRIu64, (void *)connection, now); AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, - "id=%p: The next ping will be run at timestamp %" PRIu64, + "id=%p: The next PING task will be run at timestamp %" PRIu64, (void *)connection, - schedule_time); - aws_channel_schedule_task_future(connection->slot->channel, &connection->ping_task, schedule_time); + connection->next_ping_time); + + aws_channel_schedule_task_future(connection->slot->channel, &connection->ping_task, connection->next_ping_time); } static void s_on_time_to_ping(struct aws_channel_task *channel_task, void *arg, enum aws_task_status status) { (void)channel_task; if (status == AWS_TASK_STATUS_RUN_READY) { - struct aws_mqtt_client_connection *connection = arg; - AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: Sending PING", (void *)connection); - aws_mqtt_client_connection_ping(connection); + struct aws_mqtt_client_connection_311_impl *connection = arg; + + uint64_t now = 0; + aws_channel_current_clock_time(connection->slot->channel, &now); + if (now >= connection->next_ping_time) { + s_update_next_ping_time(connection); + AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: Sending PING", (void *)connection); + aws_mqtt_client_connection_ping(connection); + } else { + + AWS_LOGF_TRACE( + AWS_LS_MQTT_CLIENT, + "id=%p: Skipped sending PING because scheduled ping time %" PRIu64 + " has not elapsed yet. Current time is %" PRIu64 + ". Rescheduling ping to run at the scheduled ping time...", + (void *)connection, + connection->next_ping_time, + now); + } s_schedule_ping(connection); } } -static int s_packet_handler_connack( - struct aws_mqtt_client_connection *connection, - struct aws_byte_cursor message_cursor) { - AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: CONNACK received", (void *)connection); +static int s_validate_received_packet_type( + struct aws_mqtt_client_connection_311_impl *connection, + enum aws_mqtt_packet_type packet_type) { + { /* BEGIN CRITICAL SECTION */ + mqtt_connection_lock_synced_data(connection); + /* [MQTT-3.2.0-1] The first packet sent from the Server to the Client MUST be a CONNACK Packet */ + if (connection->synced_data.state == AWS_MQTT_CLIENT_STATE_CONNECTING && + packet_type != AWS_MQTT_PACKET_CONNACK) { + mqtt_connection_unlock_synced_data(connection); + AWS_LOGF_ERROR( + AWS_LS_MQTT_CLIENT, + "id=%p: First message received from the server was not a CONNACK. Terminating connection.", + (void *)connection); + return aws_raise_error(AWS_ERROR_MQTT_PROTOCOL_ERROR); + } + mqtt_connection_unlock_synced_data(connection); + } /* END CRITICAL SECTION */ + + if (AWS_UNLIKELY(packet_type > AWS_MQTT_PACKET_DISCONNECT || packet_type < AWS_MQTT_PACKET_CONNECT)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_CLIENT, + "id=%p: Invalid packet type received %d. Terminating connection.", + (void *)connection, + packet_type); + return aws_raise_error(AWS_ERROR_MQTT_INVALID_PACKET_TYPE); + } + + /* Handle the packet */ + return AWS_OP_SUCCESS; +} + +static int s_packet_handler_connack(struct aws_byte_cursor message_cursor, void *user_data) { + + struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: CONNACK received", (void *)connection); + if (s_validate_received_packet_type(connection, AWS_MQTT_PACKET_CONNACK)) { + return AWS_OP_ERR; + } struct aws_mqtt_packet_connack connack; if (aws_mqtt_packet_connack_decode(&message_cursor, &connack)) { @@ -173,15 +252,30 @@ static int s_packet_handler_connack( connection, on_connection_complete, AWS_OP_SUCCESS, connack.connect_return_code, connack.session_present); } + /* + * The on_connection_success would get triggered on the successful CONNACK. It invoked with both the first connect + * attempt and reconnection attempt as Mqtt5 does not have on_resume callback for reconnection. + */ + AWS_LOGF_TRACE( + AWS_LS_MQTT_CLIENT, + "id=%p: received a successful CONNACK, invoking on_connection_success callback", + (void *)connection); + MQTT_CLIENT_CALL_CALLBACK_ARGS( + connection, on_connection_success, connack.connect_return_code, connack.session_present); + AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: connection callback completed", (void *)connection); + s_update_next_ping_time(connection); s_schedule_ping(connection); return AWS_OP_SUCCESS; } -static int s_packet_handler_publish( - struct aws_mqtt_client_connection *connection, - struct aws_byte_cursor message_cursor) { +static int s_packet_handler_publish(struct aws_byte_cursor message_cursor, void *user_data) { + struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: PUBLISH received", (void *)connection); + if (s_validate_received_packet_type(connection, AWS_MQTT_PACKET_PUBLISH)) { + return AWS_OP_ERR; + } /* TODO: need to handle the QoS 2 message to avoid processing the message a second time */ struct aws_mqtt_packet_publish publish; @@ -251,7 +345,14 @@ static int s_packet_handler_publish( return AWS_OP_SUCCESS; } -static int s_packet_handler_ack(struct aws_mqtt_client_connection *connection, struct aws_byte_cursor message_cursor) { +static int s_packet_handler_puback(struct aws_byte_cursor message_cursor, void *user_data) { + + struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: received a PUBACK", (void *)connection); + if (s_validate_received_packet_type(connection, AWS_MQTT_PACKET_PUBACK)) { + return AWS_OP_ERR; + } + struct aws_mqtt_packet_ack ack; if (aws_mqtt_packet_ack_decode(&message_cursor, &ack)) { return AWS_OP_ERR; @@ -265,9 +366,14 @@ static int s_packet_handler_ack(struct aws_mqtt_client_connection *connection, s return AWS_OP_SUCCESS; } -static int s_packet_handler_suback( - struct aws_mqtt_client_connection *connection, - struct aws_byte_cursor message_cursor) { +static int s_packet_handler_suback(struct aws_byte_cursor message_cursor, void *user_data) { + + struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: received a SUBACK", (void *)connection); + if (s_validate_received_packet_type(connection, AWS_MQTT_PACKET_SUBACK)) { + return AWS_OP_ERR; + } + struct aws_mqtt_packet_suback suback; if (aws_mqtt_packet_suback_init(&suback, connection->allocator, 0 /* fake packet_id */)) { return AWS_OP_ERR; @@ -325,9 +431,33 @@ error: return AWS_OP_ERR; } -static int s_packet_handler_pubrec( - struct aws_mqtt_client_connection *connection, - struct aws_byte_cursor message_cursor) { +static int s_packet_handler_unsuback(struct aws_byte_cursor message_cursor, void *user_data) { + + struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: received a UNSUBACK", (void *)connection); + if (s_validate_received_packet_type(connection, AWS_MQTT_PACKET_UNSUBACK)) { + return AWS_OP_ERR; + } + + struct aws_mqtt_packet_ack ack; + if (aws_mqtt_packet_ack_decode(&message_cursor, &ack)) { + return AWS_OP_ERR; + } + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_CLIENT, "id=%p: received ack for message id %" PRIu16, (void *)connection, ack.packet_identifier); + + mqtt_request_complete(connection, AWS_ERROR_SUCCESS, ack.packet_identifier); + return AWS_OP_SUCCESS; +} + +static int s_packet_handler_pubrec(struct aws_byte_cursor message_cursor, void *user_data) { + + struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: received a PUBREC", (void *)connection); + if (s_validate_received_packet_type(connection, AWS_MQTT_PACKET_PUBREC)) { + return AWS_OP_ERR; + } struct aws_mqtt_packet_ack ack; if (aws_mqtt_packet_ack_decode(&message_cursor, &ack)) { @@ -363,9 +493,13 @@ on_error: return AWS_OP_ERR; } -static int s_packet_handler_pubrel( - struct aws_mqtt_client_connection *connection, - struct aws_byte_cursor message_cursor) { +static int s_packet_handler_pubrel(struct aws_byte_cursor message_cursor, void *user_data) { + + struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: received a PUBREL", (void *)connection); + if (s_validate_received_packet_type(connection, AWS_MQTT_PACKET_PUBREL)) { + return AWS_OP_ERR; + } struct aws_mqtt_packet_ack ack; if (aws_mqtt_packet_ack_decode(&message_cursor, &ack)) { @@ -398,13 +532,32 @@ on_error: return AWS_OP_ERR; } -static int s_packet_handler_pingresp( - struct aws_mqtt_client_connection *connection, - struct aws_byte_cursor message_cursor) { +static int s_packet_handler_pubcomp(struct aws_byte_cursor message_cursor, void *user_data) { + + struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: received a PUBCOMP", (void *)connection); + if (s_validate_received_packet_type(connection, AWS_MQTT_PACKET_PUBCOMP)) { + return AWS_OP_ERR; + } + + struct aws_mqtt_packet_ack ack; + if (aws_mqtt_packet_ack_decode(&message_cursor, &ack)) { + return AWS_OP_ERR; + } + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_CLIENT, "id=%p: received ack for message id %" PRIu16, (void *)connection, ack.packet_identifier); + + mqtt_request_complete(connection, AWS_ERROR_SUCCESS, ack.packet_identifier); + return AWS_OP_SUCCESS; +} + +static int s_packet_handler_pingresp(struct aws_byte_cursor message_cursor, void *user_data) { (void)message_cursor; - AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: PINGRESP received", (void *)connection); + struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_LOGF_DEBUG(AWS_LS_MQTT_CLIENT, "id=%p: PINGRESP received", (void *)connection); connection->thread_data.waiting_on_ping_response = false; @@ -412,60 +565,32 @@ static int s_packet_handler_pingresp( } /* Bake up a big ol' function table just like Gramma used to make */ -static packet_handler_fn *s_packet_handlers[] = { - [AWS_MQTT_PACKET_CONNECT] = &s_packet_handler_default, - [AWS_MQTT_PACKET_CONNACK] = &s_packet_handler_connack, - [AWS_MQTT_PACKET_PUBLISH] = &s_packet_handler_publish, - [AWS_MQTT_PACKET_PUBACK] = &s_packet_handler_ack, - [AWS_MQTT_PACKET_PUBREC] = &s_packet_handler_pubrec, - [AWS_MQTT_PACKET_PUBREL] = &s_packet_handler_pubrel, - [AWS_MQTT_PACKET_PUBCOMP] = &s_packet_handler_ack, - [AWS_MQTT_PACKET_SUBSCRIBE] = &s_packet_handler_default, - [AWS_MQTT_PACKET_SUBACK] = &s_packet_handler_suback, - [AWS_MQTT_PACKET_UNSUBSCRIBE] = &s_packet_handler_default, - [AWS_MQTT_PACKET_UNSUBACK] = &s_packet_handler_ack, - [AWS_MQTT_PACKET_PINGREQ] = &s_packet_handler_default, - [AWS_MQTT_PACKET_PINGRESP] = &s_packet_handler_pingresp, - [AWS_MQTT_PACKET_DISCONNECT] = &s_packet_handler_default, -}; +static struct aws_mqtt_client_connection_packet_handlers s_default_packet_handlers = { + .handlers_by_packet_type = { + [AWS_MQTT_PACKET_CONNECT] = &s_packet_handler_default, + [AWS_MQTT_PACKET_CONNACK] = &s_packet_handler_connack, + [AWS_MQTT_PACKET_PUBLISH] = &s_packet_handler_publish, + [AWS_MQTT_PACKET_PUBACK] = &s_packet_handler_puback, + [AWS_MQTT_PACKET_PUBREC] = &s_packet_handler_pubrec, + [AWS_MQTT_PACKET_PUBREL] = &s_packet_handler_pubrel, + [AWS_MQTT_PACKET_PUBCOMP] = &s_packet_handler_pubcomp, + [AWS_MQTT_PACKET_SUBSCRIBE] = &s_packet_handler_default, + [AWS_MQTT_PACKET_SUBACK] = &s_packet_handler_suback, + [AWS_MQTT_PACKET_UNSUBSCRIBE] = &s_packet_handler_default, + [AWS_MQTT_PACKET_UNSUBACK] = &s_packet_handler_unsuback, + [AWS_MQTT_PACKET_PINGREQ] = &s_packet_handler_default, + [AWS_MQTT_PACKET_PINGRESP] = &s_packet_handler_pingresp, + [AWS_MQTT_PACKET_DISCONNECT] = &s_packet_handler_default, + }}; + +const struct aws_mqtt_client_connection_packet_handlers *aws_mqtt311_get_default_packet_handlers(void) { + return &s_default_packet_handlers; +} /******************************************************************************* * Channel Handler ******************************************************************************/ -static int s_process_mqtt_packet( - struct aws_mqtt_client_connection *connection, - enum aws_mqtt_packet_type packet_type, - struct aws_byte_cursor packet) { - { /* BEGIN CRITICAL SECTION */ - mqtt_connection_lock_synced_data(connection); - /* [MQTT-3.2.0-1] The first packet sent from the Server to the Client MUST be a CONNACK Packet */ - if (connection->synced_data.state == AWS_MQTT_CLIENT_STATE_CONNECTING && - packet_type != AWS_MQTT_PACKET_CONNACK) { - mqtt_connection_unlock_synced_data(connection); - AWS_LOGF_ERROR( - AWS_LS_MQTT_CLIENT, - "id=%p: First message received from the server was not a CONNACK. Terminating connection.", - (void *)connection); - aws_channel_shutdown(connection->slot->channel, AWS_ERROR_MQTT_PROTOCOL_ERROR); - return aws_raise_error(AWS_ERROR_MQTT_PROTOCOL_ERROR); - } - mqtt_connection_unlock_synced_data(connection); - } /* END CRITICAL SECTION */ - - if (AWS_UNLIKELY(packet_type > AWS_MQTT_PACKET_DISCONNECT || packet_type < AWS_MQTT_PACKET_CONNECT)) { - AWS_LOGF_ERROR( - AWS_LS_MQTT_CLIENT, - "id=%p: Invalid packet type received %d. Terminating connection.", - (void *)connection, - packet_type); - return aws_raise_error(AWS_ERROR_MQTT_INVALID_PACKET_TYPE); - } - - /* Handle the packet */ - return s_packet_handlers[packet_type](connection, packet); -} - /** * Handles incoming messages from the server. */ @@ -474,10 +599,10 @@ static int s_process_read_message( struct aws_channel_slot *slot, struct aws_io_message *message) { - struct aws_mqtt_client_connection *connection = handler->impl; + struct aws_mqtt_client_connection_311_impl *connection = handler->impl; if (message->message_type != AWS_IO_MESSAGE_APPLICATION_DATA || message->message_data.len < 1) { - return AWS_OP_ERR; + return aws_raise_error(AWS_ERROR_INVALID_STATE); } AWS_LOGF_TRACE( @@ -489,103 +614,18 @@ static int s_process_read_message( /* This cursor will be updated as we read through the message. */ struct aws_byte_cursor message_cursor = aws_byte_cursor_from_buf(&message->message_data); - /* If there's pending packet left over from last time, attempt to complete it. */ - if (connection->thread_data.pending_packet.len) { - int result = AWS_OP_SUCCESS; - - /* This determines how much to read from the message (min(expected_remaining, message.len)) */ - size_t to_read = connection->thread_data.pending_packet.capacity - connection->thread_data.pending_packet.len; - /* This will be set to false if this message still won't complete the packet object. */ - bool packet_complete = true; - if (to_read > message_cursor.len) { - to_read = message_cursor.len; - packet_complete = false; - } - - /* Write the chunk to the buffer. - * This will either complete the packet, or be the entirety of message if more data is required. */ - struct aws_byte_cursor chunk = aws_byte_cursor_advance(&message_cursor, to_read); - AWS_ASSERT(chunk.ptr); /* Guaranteed to be in bounds */ - result = (int)aws_byte_buf_write_from_whole_cursor(&connection->thread_data.pending_packet, chunk) - 1; - if (result) { - goto handle_error; - } - - /* If the packet is still incomplete, don't do anything with the data. */ - if (!packet_complete) { - AWS_LOGF_TRACE( - AWS_LS_MQTT_CLIENT, - "id=%p: partial message is still incomplete, waiting on another read.", - (void *)connection); - - goto cleanup; - } - - /* Handle the completed pending packet */ - struct aws_byte_cursor packet_data = aws_byte_cursor_from_buf(&connection->thread_data.pending_packet); - AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: full mqtt packet re-assembled, dispatching.", (void *)connection); - result = s_process_mqtt_packet(connection, aws_mqtt_get_packet_type(packet_data.ptr), packet_data); - - handle_error: - /* Clean up the pending packet */ - aws_byte_buf_clean_up(&connection->thread_data.pending_packet); - AWS_ZERO_STRUCT(connection->thread_data.pending_packet); - - if (result) { - return AWS_OP_ERR; - } - } + int result = aws_mqtt311_decoder_on_bytes_received(&connection->thread_data.decoder, message_cursor); - while (message_cursor.len) { - - /* Temp byte cursor so we can decode the header without advancing message_cursor. */ - struct aws_byte_cursor header_decode = message_cursor; - - struct aws_mqtt_fixed_header packet_header; - AWS_ZERO_STRUCT(packet_header); - int result = aws_mqtt_fixed_header_decode(&header_decode, &packet_header); - - /* Calculate how much data was read. */ - const size_t fixed_header_size = message_cursor.len - header_decode.len; - - if (result) { - if (aws_last_error() == AWS_ERROR_SHORT_BUFFER) { - /* Message data too short, store data and come back later. */ - AWS_LOGF_TRACE( - AWS_LS_MQTT_CLIENT, "id=%p: message is incomplete, waiting on another read.", (void *)connection); - if (aws_byte_buf_init( - &connection->thread_data.pending_packet, - connection->allocator, - fixed_header_size + packet_header.remaining_length)) { - - return AWS_OP_ERR; - } - - /* Write the partial packet. */ - if (!aws_byte_buf_write_from_whole_cursor(&connection->thread_data.pending_packet, message_cursor)) { - aws_byte_buf_clean_up(&connection->thread_data.pending_packet); - return AWS_OP_ERR; - } - - aws_reset_error(); - goto cleanup; - } else { - return AWS_OP_ERR; - } - } - - struct aws_byte_cursor packet_data = - aws_byte_cursor_advance(&message_cursor, fixed_header_size + packet_header.remaining_length); - AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: full mqtt packet read, dispatching.", (void *)connection); - s_process_mqtt_packet(connection, packet_header.packet_type, packet_data); + if (result == AWS_OP_SUCCESS) { + /* Do cleanup */ + size_t message_data_length = message->message_data.len; + aws_mem_release(message->allocator, message); + aws_channel_slot_increment_read_window(slot, message_data_length); + } else { + aws_channel_shutdown(connection->slot->channel, aws_last_error()); } -cleanup: - /* Do cleanup */ - aws_channel_slot_increment_read_window(slot, message->message_data.len); - aws_mem_release(message->allocator, message); - - return AWS_OP_SUCCESS; + return result; } static int s_shutdown( @@ -595,7 +635,7 @@ static int s_shutdown( int error_code, bool free_scarce_resources_immediately) { - struct aws_mqtt_client_connection *connection = handler->impl; + struct aws_mqtt_client_connection_311_impl *connection = handler->impl; if (dir == AWS_CHANNEL_DIR_WRITE) { /* On closing write direction, send out disconnect packet before closing connection. */ @@ -650,7 +690,7 @@ static size_t s_initial_window_size(struct aws_channel_handler *handler) { static void s_destroy(struct aws_channel_handler *handler) { - struct aws_mqtt_client_connection *connection = handler->impl; + struct aws_mqtt_client_connection_311_impl *connection = handler->impl; (void)connection; } @@ -679,7 +719,7 @@ struct aws_channel_handler_vtable *aws_mqtt_get_client_channel_vtable(void) { ******************************************************************************/ struct aws_io_message *mqtt_get_message_for_packet( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, struct aws_mqtt_fixed_header *header) { const size_t required_length = 3 + header->remaining_length; @@ -704,7 +744,7 @@ struct aws_io_message *mqtt_get_message_for_packet( static void s_request_outgoing_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) { struct aws_mqtt_request *request = arg; - struct aws_mqtt_client_connection *connection = request->connection; + struct aws_mqtt_client_connection_311_impl *connection = request->connection; if (status == AWS_TASK_STATUS_CANCELED) { /* Connection lost before the request ever get send, check the request needs to be retried or not */ @@ -739,7 +779,7 @@ static void s_request_outgoing_task(struct aws_channel_task *task, void *arg, en /* Fire the callback and clean up the memory, as the connection get destroyed. */ if (request->on_complete) { request->on_complete( - connection, request->packet_id, AWS_ERROR_MQTT_NOT_CONNECTED, request->on_complete_ud); + &connection->base, request->packet_id, AWS_ERROR_MQTT_NOT_CONNECTED, request->on_complete_ud); } { /* BEGIN CRITICAL SECTION */ @@ -760,6 +800,8 @@ static void s_request_outgoing_task(struct aws_channel_task *task, void *arg, en /* Send the request */ enum aws_mqtt_client_request_state state = request->send_request(request->packet_id, !request->initiated, request->send_request_ud); + /* Update the request send time.*/ + s_update_request_send_time(request); request->initiated = true; int error_code = AWS_ERROR_SUCCESS; switch (state) { @@ -783,7 +825,7 @@ static void s_request_outgoing_task(struct aws_channel_task *task, void *arg, en /* If the send_request function reports the request is complete, * remove from the hash table and call the callback. */ if (request->on_complete) { - request->on_complete(connection, request->packet_id, error_code, request->on_complete_ud); + request->on_complete(&connection->base, request->packet_id, error_code, request->on_complete_ud); } { /* BEGIN CRITICAL SECTION */ @@ -824,7 +866,7 @@ static void s_request_outgoing_task(struct aws_channel_task *task, void *arg, en } uint16_t mqtt_create_request( - struct aws_mqtt_client_connection *connection, + struct aws_mqtt_client_connection_311_impl *connection, aws_mqtt_send_request_fn *send_request, void *send_request_ud, aws_mqtt_op_complete_fn *on_complete, @@ -962,7 +1004,7 @@ uint16_t mqtt_create_request( return next_request->packet_id; } -void mqtt_request_complete(struct aws_mqtt_client_connection *connection, int error_code, uint16_t packet_id) { +void mqtt_request_complete(struct aws_mqtt_client_connection_311_impl *connection, int error_code, uint16_t packet_id) { AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, @@ -990,6 +1032,9 @@ void mqtt_request_complete(struct aws_mqtt_client_connection *connection, int er aws_mqtt_connection_statistics_change_operation_statistic_state( request->connection, request, AWS_MQTT_OSS_NONE); + if (error_code == AWS_OP_SUCCESS) { + s_pushoff_next_ping_time(connection, request->request_send_timestamp); + } /* clean up request resources */ aws_hash_table_remove_element(&connection->synced_data.outstanding_requests_table, elem); /* remove the request from the list, which is thread_data.ongoing_requests_list */ @@ -1012,7 +1057,7 @@ void mqtt_request_complete(struct aws_mqtt_client_connection *connection, int er /* Invoke the complete callback. */ if (on_complete) { - on_complete(connection, packet_id, error_code, on_complete_ud); + on_complete(&connection->base, packet_id, error_code, on_complete_ud); } } @@ -1026,7 +1071,7 @@ static void s_mqtt_disconnect_task(struct aws_channel_task *channel_task, void * (void)status; struct mqtt_shutdown_task *task = AWS_CONTAINER_OF(channel_task, struct mqtt_shutdown_task, task); - struct aws_mqtt_client_connection *connection = arg; + struct aws_mqtt_client_connection_311_impl *connection = arg; AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: Doing disconnect", (void *)connection); { /* BEGIN CRITICAL SECTION */ @@ -1050,12 +1095,14 @@ static void s_mqtt_disconnect_task(struct aws_channel_task *channel_task, void * aws_mem_release(connection->allocator, task); } -void mqtt_disconnect_impl(struct aws_mqtt_client_connection *connection, int error_code) { +void mqtt_disconnect_impl(struct aws_mqtt_client_connection_311_impl *connection, int error_code) { if (connection->slot) { struct mqtt_shutdown_task *shutdown_task = aws_mem_calloc(connection->allocator, 1, sizeof(struct mqtt_shutdown_task)); shutdown_task->error_code = error_code; aws_channel_task_init(&shutdown_task->task, s_mqtt_disconnect_task, connection, "mqtt_disconnect"); aws_channel_schedule_task_now(connection->slot->channel, &shutdown_task->task); + } else { + AWS_LOGF_TRACE(AWS_LS_MQTT_CLIENT, "id=%p: Client currently has no slot to disconnect", (void *)connection); } } diff --git a/contrib/restricted/aws/aws-c-mqtt/source/client_impl_shared.c b/contrib/restricted/aws/aws-c-mqtt/source/client_impl_shared.c new file mode 100644 index 00000000000..6f65eb88e34 --- /dev/null +++ b/contrib/restricted/aws/aws-c-mqtt/source/client_impl_shared.c @@ -0,0 +1,220 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include <aws/mqtt/client.h> +#include <aws/mqtt/private/client_impl_shared.h> + +struct aws_mqtt_client_connection *aws_mqtt_client_connection_acquire(struct aws_mqtt_client_connection *connection) { + if (connection != NULL) { + return (*connection->vtable->acquire_fn)(connection->impl); + } + + return NULL; +} + +void aws_mqtt_client_connection_release(struct aws_mqtt_client_connection *connection) { + if (connection != NULL) { + (*connection->vtable->release_fn)(connection->impl); + } +} + +int aws_mqtt_client_connection_set_will( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + bool retain, + const struct aws_byte_cursor *payload) { + + return (*connection->vtable->set_will_fn)(connection->impl, topic, qos, retain, payload); +} + +int aws_mqtt_client_connection_set_login( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *username, + const struct aws_byte_cursor *password) { + + return (*connection->vtable->set_login_fn)(connection->impl, username, password); +} + +int aws_mqtt_client_connection_use_websockets( + struct aws_mqtt_client_connection *connection, + aws_mqtt_transform_websocket_handshake_fn *transformer, + void *transformer_ud, + aws_mqtt_validate_websocket_handshake_fn *validator, + void *validator_ud) { + + return (*connection->vtable->use_websockets_fn)( + connection->impl, transformer, transformer_ud, validator, validator_ud); +} + +int aws_mqtt_client_connection_set_http_proxy_options( + struct aws_mqtt_client_connection *connection, + struct aws_http_proxy_options *proxy_options) { + + return (*connection->vtable->set_http_proxy_options_fn)(connection->impl, proxy_options); +} + +int aws_mqtt_client_connection_set_host_resolution_options( + struct aws_mqtt_client_connection *connection, + const struct aws_host_resolution_config *host_resolution_config) { + + return (*connection->vtable->set_host_resolution_options_fn)(connection->impl, host_resolution_config); +} + +int aws_mqtt_client_connection_set_reconnect_timeout( + struct aws_mqtt_client_connection *connection, + uint64_t min_timeout, + uint64_t max_timeout) { + + return (*connection->vtable->set_reconnect_timeout_fn)(connection->impl, min_timeout, max_timeout); +} + +int aws_mqtt_client_connection_set_connection_result_handlers( + struct aws_mqtt_client_connection *connection, + aws_mqtt_client_on_connection_success_fn *on_connection_success, + void *on_connection_success_ud, + aws_mqtt_client_on_connection_failure_fn *on_connection_failure, + void *on_connection_failure_ud) { + + return (*connection->vtable->set_connection_result_handlers)( + connection->impl, + on_connection_success, + on_connection_success_ud, + on_connection_failure, + on_connection_failure_ud); +} + +int aws_mqtt_client_connection_set_connection_interruption_handlers( + struct aws_mqtt_client_connection *connection, + aws_mqtt_client_on_connection_interrupted_fn *on_interrupted, + void *on_interrupted_ud, + aws_mqtt_client_on_connection_resumed_fn *on_resumed, + void *on_resumed_ud) { + + return (*connection->vtable->set_connection_interruption_handlers_fn)( + connection->impl, on_interrupted, on_interrupted_ud, on_resumed, on_resumed_ud); +} + +int aws_mqtt_client_connection_set_connection_closed_handler( + struct aws_mqtt_client_connection *connection, + aws_mqtt_client_on_connection_closed_fn *on_closed, + void *on_closed_ud) { + + return (*connection->vtable->set_connection_closed_handler_fn)(connection->impl, on_closed, on_closed_ud); +} + +int aws_mqtt_client_connection_set_on_any_publish_handler( + struct aws_mqtt_client_connection *connection, + aws_mqtt_client_publish_received_fn *on_any_publish, + void *on_any_publish_ud) { + + return (*connection->vtable->set_on_any_publish_handler_fn)(connection->impl, on_any_publish, on_any_publish_ud); +} + +int aws_mqtt_client_connection_set_connection_termination_handler( + struct aws_mqtt_client_connection *connection, + aws_mqtt_client_on_connection_termination_fn *on_termination, + void *on_termination_ud) { + + return (*connection->vtable->set_connection_termination_handler_fn)( + connection->impl, on_termination, on_termination_ud); +} + +int aws_mqtt_client_connection_connect( + struct aws_mqtt_client_connection *connection, + const struct aws_mqtt_connection_options *connection_options) { + + return (*connection->vtable->connect_fn)(connection->impl, connection_options); +} + +int aws_mqtt_client_connection_reconnect( + struct aws_mqtt_client_connection *connection, + aws_mqtt_client_on_connection_complete_fn *on_connection_complete, + void *userdata) { + + return (*connection->vtable->reconnect_fn)(connection->impl, on_connection_complete, userdata); +} + +int aws_mqtt_client_connection_disconnect( + struct aws_mqtt_client_connection *connection, + aws_mqtt_client_on_disconnect_fn *on_disconnect, + void *userdata) { + + return (*connection->vtable->disconnect_fn)(connection->impl, on_disconnect, userdata); +} + +uint16_t aws_mqtt_client_connection_subscribe_multiple( + struct aws_mqtt_client_connection *connection, + const struct aws_array_list *topic_filters, + aws_mqtt_suback_multi_fn *on_suback, + void *on_suback_ud) { + + return (*connection->vtable->subscribe_multiple_fn)(connection->impl, topic_filters, on_suback, on_suback_ud); +} + +uint16_t aws_mqtt_client_connection_subscribe( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic_filter, + enum aws_mqtt_qos qos, + aws_mqtt_client_publish_received_fn *on_publish, + void *on_publish_ud, + aws_mqtt_userdata_cleanup_fn *on_ud_cleanup, + aws_mqtt_suback_fn *on_suback, + void *on_suback_ud) { + + return (*connection->vtable->subscribe_fn)( + connection->impl, topic_filter, qos, on_publish, on_publish_ud, on_ud_cleanup, on_suback, on_suback_ud); +} + +uint16_t aws_mqtt_resubscribe_existing_topics( + struct aws_mqtt_client_connection *connection, + aws_mqtt_suback_multi_fn *on_suback, + void *on_suback_ud) { + + return (*connection->vtable->resubscribe_existing_topics_fn)(connection->impl, on_suback, on_suback_ud); +} + +uint16_t aws_mqtt_client_connection_unsubscribe( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic_filter, + aws_mqtt_op_complete_fn *on_unsuback, + void *on_unsuback_ud) { + + return (*connection->vtable->unsubscribe_fn)(connection->impl, topic_filter, on_unsuback, on_unsuback_ud); +} + +uint16_t aws_mqtt_client_connection_publish( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + bool retain, + const struct aws_byte_cursor *payload, + aws_mqtt_op_complete_fn *on_complete, + void *userdata) { + + return (*connection->vtable->publish_fn)(connection->impl, topic, qos, retain, payload, on_complete, userdata); +} + +int aws_mqtt_client_connection_get_stats( + struct aws_mqtt_client_connection *connection, + struct aws_mqtt_connection_operation_statistics *stats) { + + return (*connection->vtable->get_stats_fn)(connection->impl, stats); +} + +uint64_t aws_mqtt_hash_uint16_t(const void *item) { + return *(uint16_t *)item; +} + +bool aws_mqtt_compare_uint16_t_eq(const void *a, const void *b) { + return *(uint16_t *)a == *(uint16_t *)b; +} + +bool aws_mqtt_byte_cursor_hash_equality(const void *a, const void *b) { + const struct aws_byte_cursor *a_cursor = a; + const struct aws_byte_cursor *b_cursor = b; + + return aws_byte_cursor_eq(a_cursor, b_cursor); +} diff --git a/contrib/restricted/aws/aws-c-mqtt/source/fixed_header.c b/contrib/restricted/aws/aws-c-mqtt/source/fixed_header.c index 22372f4194a..670ae9780ea 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/fixed_header.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/fixed_header.c @@ -29,7 +29,8 @@ static int s_encode_remaining_length(struct aws_byte_buf *buf, size_t remaining_ return AWS_OP_SUCCESS; } -static int s_decode_remaining_length(struct aws_byte_cursor *cur, size_t *remaining_length_out) { + +int aws_mqtt311_decode_remaining_length(struct aws_byte_cursor *cur, size_t *remaining_length_out) { AWS_PRECONDITION(cur); @@ -128,7 +129,7 @@ int aws_mqtt_fixed_header_decode(struct aws_byte_cursor *cur, struct aws_mqtt_fi header->flags = byte_1 & 0xF; /* Read remaining length */ - if (s_decode_remaining_length(cur, &header->remaining_length)) { + if (aws_mqtt311_decode_remaining_length(cur, &header->remaining_length)) { return AWS_OP_ERR; } if (cur->len < header->remaining_length) { diff --git a/contrib/restricted/aws/aws-c-mqtt/source/mqtt.c b/contrib/restricted/aws/aws-c-mqtt/source/mqtt.c index fd39a82747f..9caeaa9bbaa 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/mqtt.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/mqtt.c @@ -5,23 +5,28 @@ #include <aws/mqtt/mqtt.h> +#include <aws/common/encoding.h> +#include <aws/http/http.h> #include <aws/io/logging.h> -#ifdef AWS_MQTT_WITH_WEBSOCKETS -# include <aws/http/http.h> -#endif - /******************************************************************************* * Topic Validation ******************************************************************************/ static bool s_is_valid_topic(const struct aws_byte_cursor *topic, bool is_filter) { + if (topic == NULL) { + return false; + } /* [MQTT-4.7.3-1] Check existance and length */ if (!topic->ptr || !topic->len) { return false; } + if (aws_mqtt_validate_utf8_text(*topic) == AWS_OP_ERR) { + return false; + } + /* [MQTT-4.7.3-2] Check for the null character */ if (memchr(topic->ptr, 0, topic->len)) { return false; @@ -216,6 +221,21 @@ bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter) AWS_DEFINE_ERROR_INFO_MQTT( AWS_ERROR_MQTT5_INVALID_OUTBOUND_TOPIC_ALIAS, "Outgoing publish contained an invalid (too large or unknown) topic alias"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT5_INVALID_UTF8_STRING, + "Outbound packet contains invalid utf-8 data in a field that must be utf-8"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT, + "Mqtt5 connection was reset by the Mqtt3 adapter in order to guarantee correct connection configuration"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_CONNECTION_RESUBSCRIBE_NO_TOPICS, + "Resubscribe was called when there were no subscriptions"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_CONNECTION_SUBSCRIBE_FAILURE, + "MQTT subscribe operation failed"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_ACK_REASON_CODE_FAILURE, + "MQTT ack packet received with a failing reason code"), }; /* clang-format on */ #undef AWS_DEFINE_ERROR_INFO_MQTT @@ -233,6 +253,7 @@ static struct aws_error_info_list s_error_list = { DEFINE_LOG_SUBJECT_INFO(AWS_LS_MQTT5_GENERAL, "mqtt5-general", "Misc MQTT5 logging"), DEFINE_LOG_SUBJECT_INFO(AWS_LS_MQTT5_CLIENT, "mqtt5-client", "MQTT5 client and connections"), DEFINE_LOG_SUBJECT_INFO(AWS_LS_MQTT5_CANARY, "mqtt5-canary", "MQTT5 canary logging"), + DEFINE_LOG_SUBJECT_INFO(AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "mqtt5-to-mqtt3-adapter", "MQTT5-To-MQTT3 adapter logging"), }; /* clang-format on */ @@ -250,9 +271,8 @@ void aws_mqtt_library_init(struct aws_allocator *allocator) { if (!s_mqtt_library_initialized) { s_mqtt_library_initialized = true; aws_io_library_init(allocator); -#ifdef AWS_MQTT_WITH_WEBSOCKETS aws_http_library_init(allocator); -#endif + aws_register_error_info(&s_error_list); aws_register_log_subject_info_list(&s_logging_subjects_list); } @@ -264,9 +284,8 @@ void aws_mqtt_library_clean_up(void) { aws_thread_join_all_managed(); aws_unregister_error_info(&s_error_list); aws_unregister_log_subject_info_list(&s_logging_subjects_list); -#ifdef AWS_MQTT_WITH_WEBSOCKETS + aws_http_library_clean_up(); -#endif aws_io_library_clean_up(); } } @@ -280,3 +299,37 @@ void aws_mqtt_fatal_assert_library_initialized(void) { AWS_FATAL_ASSERT(s_mqtt_library_initialized); } } + +/* UTF-8 encoded string validation respect to [MQTT-1.5.3-2]. */ +static int aws_mqtt_utf8_decoder(uint32_t codepoint, void *user_data) { + (void)user_data; + /* U+0000 - A UTF-8 Encoded String MUST NOT include an encoding of the null character U+0000. [MQTT-1.5.4-2] + * U+0001..U+001F control characters are not valid + */ + if (AWS_UNLIKELY(codepoint <= 0x001F)) { + return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING); + } + + /* U+007F..U+009F control characters are not valid */ + if (AWS_UNLIKELY((codepoint >= 0x007F) && (codepoint <= 0x009F))) { + return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING); + } + + /* Unicode non-characters are not valid: https://www.unicode.org/faq/private_use.html#nonchar1 */ + if (AWS_UNLIKELY((codepoint & 0x00FFFF) >= 0x00FFFE)) { + return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING); + } + if (AWS_UNLIKELY(codepoint >= 0xFDD0 && codepoint <= 0xFDEF)) { + return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING); + } + + return AWS_OP_SUCCESS; +} + +static struct aws_utf8_decoder_options s_aws_mqtt_utf8_decoder_options = { + .on_codepoint = aws_mqtt_utf8_decoder, +}; + +int aws_mqtt_validate_utf8_text(struct aws_byte_cursor text) { + return aws_decode_utf8(text, &s_aws_mqtt_utf8_decoder_options); +} diff --git a/contrib/restricted/aws/aws-c-mqtt/source/mqtt311_decoder.c b/contrib/restricted/aws/aws-c-mqtt/source/mqtt311_decoder.c new file mode 100644 index 00000000000..cb1f26c47e2 --- /dev/null +++ b/contrib/restricted/aws/aws-c-mqtt/source/mqtt311_decoder.c @@ -0,0 +1,211 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include <aws/mqtt/private/mqtt311_decoder.h> + +#include <aws/mqtt/private/fixed_header.h> + +static void s_aws_mqtt311_decoder_reset(struct aws_mqtt311_decoder *decoder) { + decoder->state = AWS_MDST_READ_FIRST_BYTE; + decoder->total_packet_length = 0; + aws_byte_buf_reset(&decoder->packet_buffer, false); +} + +void aws_mqtt311_decoder_init( + struct aws_mqtt311_decoder *decoder, + struct aws_allocator *allocator, + const struct aws_mqtt311_decoder_options *options) { + + aws_byte_buf_init(&decoder->packet_buffer, allocator, 5); + decoder->config = *options; + + s_aws_mqtt311_decoder_reset(decoder); +} + +void aws_mqtt311_decoder_clean_up(struct aws_mqtt311_decoder *decoder) { + aws_byte_buf_clean_up(&decoder->packet_buffer); +} + +static void s_aws_mqtt311_decoder_reset_for_new_packet(struct aws_mqtt311_decoder *decoder) { + if (decoder->state != AWS_MDST_PROTOCOL_ERROR) { + s_aws_mqtt311_decoder_reset(decoder); + } +} + +enum aws_mqtt311_decoding_directive { AWS_MDD_CONTINUE, AWS_MDD_OUT_OF_DATA, AWS_MDD_PROTOCOL_ERROR }; + +static enum aws_mqtt311_decoding_directive aws_result_to_mqtt311_decoding_directive(int result) { + return (result == AWS_OP_SUCCESS) ? AWS_MDD_CONTINUE : AWS_MDD_PROTOCOL_ERROR; +} + +static int s_aws_mqtt311_decoder_safe_packet_handle( + struct aws_mqtt311_decoder *decoder, + enum aws_mqtt_packet_type packet_type, + struct aws_byte_cursor packet_cursor) { + packet_handler_fn *handler = decoder->config.packet_handlers->handlers_by_packet_type[packet_type]; + if (handler != NULL) { + return handler(packet_cursor, decoder->config.handler_user_data); + } else { + return aws_raise_error(AWS_ERROR_MQTT_PROTOCOL_ERROR); + } +} + +static enum aws_mqtt311_decoding_directive s_handle_decoder_read_first_byte( + struct aws_mqtt311_decoder *decoder, + struct aws_byte_cursor *data) { + AWS_FATAL_ASSERT(decoder->packet_buffer.len == 0); + if (data->len == 0) { + return AWS_MDD_OUT_OF_DATA; + } + + /* + * Do a greedy check to see if the whole MQTT packet is contained within the received data. If it is, decode it + * directly from the incoming data cursor without buffering it first. Otherwise, the packet is fragmented + * across multiple received data calls, and so we must use the packet buffer and copy everything first via the + * full decoder state machine. + * + * A corollary of this is that the decoder is only ever in the AWS_MDST_READ_REMAINING_LENGTH or AWS_MDST_READ_BODY + * states if the current MQTT packet was received in a fragmented manner. + */ + struct aws_byte_cursor temp_cursor = *data; + struct aws_mqtt_fixed_header packet_header; + AWS_ZERO_STRUCT(packet_header); + if (!aws_mqtt_fixed_header_decode(&temp_cursor, &packet_header) && + temp_cursor.len >= packet_header.remaining_length) { + + /* figure out the cursor that spans the full packet */ + size_t fixed_header_length = temp_cursor.ptr - data->ptr; + struct aws_byte_cursor whole_packet_cursor = *data; + whole_packet_cursor.len = fixed_header_length + packet_header.remaining_length; + + /* advance the external, mutable data cursor to the start of the next packet */ + aws_byte_cursor_advance(data, whole_packet_cursor.len); + + /* + * if this fails, the decoder goes into an error state. If it succeeds we'll loop again into the same state + * because we'll be back at the beginning of the next packet (if it exists). + */ + enum aws_mqtt_packet_type packet_type = aws_mqtt_get_packet_type(whole_packet_cursor.ptr); + return aws_result_to_mqtt311_decoding_directive( + s_aws_mqtt311_decoder_safe_packet_handle(decoder, packet_type, whole_packet_cursor)); + } + + /* + * The packet is fragmented, spanning more than this io message. So we buffer it and use the + * simple state machine to decode. + */ + uint8_t byte = *data->ptr; + aws_byte_cursor_advance(data, 1); + aws_byte_buf_append_byte_dynamic(&decoder->packet_buffer, byte); + + decoder->state = AWS_MDST_READ_REMAINING_LENGTH; + + return AWS_MDD_CONTINUE; +} + +static enum aws_mqtt311_decoding_directive s_handle_decoder_read_remaining_length( + struct aws_mqtt311_decoder *decoder, + struct aws_byte_cursor *data) { + AWS_FATAL_ASSERT(decoder->total_packet_length == 0); + if (data->len == 0) { + return AWS_MDD_OUT_OF_DATA; + } + + uint8_t byte = *data->ptr; + aws_byte_cursor_advance(data, 1); + aws_byte_buf_append_byte_dynamic(&decoder->packet_buffer, byte); + + struct aws_byte_cursor vli_cursor = aws_byte_cursor_from_buf(&decoder->packet_buffer); + aws_byte_cursor_advance(&vli_cursor, 1); + + size_t remaining_length = 0; + if (aws_mqtt311_decode_remaining_length(&vli_cursor, &remaining_length) == AWS_OP_ERR) { + /* anything other than a short buffer error (not enough data yet) is a terminal error */ + if (aws_last_error() == AWS_ERROR_SHORT_BUFFER) { + return AWS_MDD_CONTINUE; + } else { + return AWS_MDD_PROTOCOL_ERROR; + } + } + + /* + * If we successfully decoded a variable-length integer, we now know exactly how many bytes we need to receive in + * order to have the full packet. + */ + decoder->total_packet_length = remaining_length + decoder->packet_buffer.len; + AWS_FATAL_ASSERT(decoder->total_packet_length > 0); + decoder->state = AWS_MDST_READ_BODY; + + return AWS_MDD_CONTINUE; +} + +static enum aws_mqtt311_decoding_directive s_handle_decoder_read_body( + struct aws_mqtt311_decoder *decoder, + struct aws_byte_cursor *data) { + AWS_FATAL_ASSERT(decoder->total_packet_length > 0); + + size_t buffer_length = decoder->packet_buffer.len; + size_t amount_to_read = aws_min_size(decoder->total_packet_length - buffer_length, data->len); + + struct aws_byte_cursor copy_cursor = aws_byte_cursor_advance(data, amount_to_read); + aws_byte_buf_append_dynamic(&decoder->packet_buffer, ©_cursor); + + if (decoder->packet_buffer.len == decoder->total_packet_length) { + + /* We have the full packet in the scratch buffer, invoke the correct handler to decode and process it */ + struct aws_byte_cursor packet_data = aws_byte_cursor_from_buf(&decoder->packet_buffer); + enum aws_mqtt_packet_type packet_type = aws_mqtt_get_packet_type(packet_data.ptr); + if (s_aws_mqtt311_decoder_safe_packet_handle(decoder, packet_type, packet_data) == AWS_OP_ERR) { + return AWS_MDD_PROTOCOL_ERROR; + } + + s_aws_mqtt311_decoder_reset_for_new_packet(decoder); + return AWS_MDD_CONTINUE; + } + + return AWS_MDD_OUT_OF_DATA; +} + +int aws_mqtt311_decoder_on_bytes_received(struct aws_mqtt311_decoder *decoder, struct aws_byte_cursor data) { + struct aws_byte_cursor data_cursor = data; + + enum aws_mqtt311_decoding_directive decode_directive = AWS_MDD_CONTINUE; + while (decode_directive == AWS_MDD_CONTINUE) { + switch (decoder->state) { + case AWS_MDST_READ_FIRST_BYTE: + decode_directive = s_handle_decoder_read_first_byte(decoder, &data_cursor); + break; + + case AWS_MDST_READ_REMAINING_LENGTH: + decode_directive = s_handle_decoder_read_remaining_length(decoder, &data_cursor); + break; + + case AWS_MDST_READ_BODY: + decode_directive = s_handle_decoder_read_body(decoder, &data_cursor); + break; + + default: + decode_directive = AWS_MDD_PROTOCOL_ERROR; + break; + } + + /* + * Protocol error is a terminal failure state until aws_mqtt311_decoder_reset_for_new_connection() is called. + */ + if (decode_directive == AWS_MDD_PROTOCOL_ERROR) { + decoder->state = AWS_MDST_PROTOCOL_ERROR; + if (aws_last_error() == AWS_ERROR_SUCCESS) { + aws_raise_error(AWS_ERROR_MQTT_PROTOCOL_ERROR); + } + return AWS_OP_ERR; + } + } + + return AWS_OP_SUCCESS; +} + +void aws_mqtt311_decoder_reset_for_new_connection(struct aws_mqtt311_decoder *decoder) { + s_aws_mqtt311_decoder_reset(decoder); +} diff --git a/contrib/restricted/aws/aws-c-mqtt/source/mqtt_subscription_set.c b/contrib/restricted/aws/aws-c-mqtt/source/mqtt_subscription_set.c new file mode 100644 index 00000000000..1e946fc5fed --- /dev/null +++ b/contrib/restricted/aws/aws-c-mqtt/source/mqtt_subscription_set.c @@ -0,0 +1,431 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include "aws/mqtt/private/mqtt_subscription_set.h" + +#include "aws/mqtt/private/client_impl_shared.h" + +#define SUBSCRIPTION_SET_DEFAULT_BRANCH_FACTOR 10 +#define SUBSCRIPTION_SET_DEFAULT_ENTRY_COUNT 50 + +struct aws_mqtt_subscription_set_subscription_record *aws_mqtt_subscription_set_subscription_record_new( + struct aws_allocator *allocator, + const struct aws_mqtt_subscription_set_subscription_options *subscription) { + struct aws_mqtt_subscription_set_subscription_record *record = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_subscription_set_subscription_record)); + + record->allocator = allocator; + aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, subscription->topic_filter); + record->subscription_view = *subscription; + record->subscription_view.topic_filter = aws_byte_cursor_from_buf(&record->topic_filter); + + return record; +} + +void aws_mqtt_subscription_set_subscription_record_destroy( + struct aws_mqtt_subscription_set_subscription_record *record) { + if (record == NULL) { + return; + } + + aws_byte_buf_clean_up(&record->topic_filter); + aws_mem_release(record->allocator, record); +} + +static void s_aws_mqtt_subscription_set_subscription_record_hash_destroy(void *element) { + struct aws_mqtt_subscription_set_subscription_record *record = element; + + aws_mqtt_subscription_set_subscription_record_destroy(record); +} + +static struct aws_mqtt_subscription_set_topic_tree_node *s_aws_mqtt_subscription_set_node_new( + struct aws_allocator *allocator, + struct aws_mqtt_subscription_set_topic_tree_node *parent) { + + struct aws_mqtt_subscription_set_topic_tree_node *node = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_subscription_set_topic_tree_node)); + node->allocator = allocator; + aws_hash_table_init( + &node->children, + allocator, + SUBSCRIPTION_SET_DEFAULT_BRANCH_FACTOR, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + NULL); + node->ref_count = 1; + node->parent = parent; + + return node; +} + +struct aws_mqtt_subscription_set *aws_mqtt_subscription_set_new(struct aws_allocator *allocator) { + + struct aws_mqtt_subscription_set *subscription_set = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_subscription_set)); + + subscription_set->allocator = allocator; + subscription_set->root = s_aws_mqtt_subscription_set_node_new(allocator, NULL); + + aws_hash_table_init( + &subscription_set->subscriptions, + allocator, + SUBSCRIPTION_SET_DEFAULT_ENTRY_COUNT, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + s_aws_mqtt_subscription_set_subscription_record_hash_destroy); + + return subscription_set; +} + +static int s_subscription_set_node_destroy_hash_foreach_wrap(void *context, struct aws_hash_element *elem); + +static void s_aws_mqtt_subscription_set_node_destroy_node(struct aws_mqtt_subscription_set_topic_tree_node *node) { + aws_hash_table_foreach(&node->children, s_subscription_set_node_destroy_hash_foreach_wrap, NULL); + aws_hash_table_clean_up(&node->children); + + if (node->on_cleanup && node->callback_user_data) { + node->on_cleanup(node->callback_user_data); + } + + aws_byte_buf_clean_up(&node->topic_segment); + + aws_mem_release(node->allocator, node); +} + +static void s_aws_mqtt_subscription_set_node_destroy_tree(struct aws_mqtt_subscription_set_topic_tree_node *tree) { + if (tree == NULL) { + return; + } + + if (tree->parent != NULL) { + aws_hash_table_remove(&tree->parent->children, &tree->topic_segment, NULL, NULL); + } + + s_aws_mqtt_subscription_set_node_destroy_node(tree); +} + +static int s_subscription_set_node_destroy_hash_foreach_wrap(void *context, struct aws_hash_element *elem) { + (void)context; + + s_aws_mqtt_subscription_set_node_destroy_node(elem->value); + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; +} + +void aws_mqtt_subscription_set_destroy(struct aws_mqtt_subscription_set *subscription_set) { + if (subscription_set == NULL) { + return; + } + + s_aws_mqtt_subscription_set_node_destroy_tree(subscription_set->root); + + aws_hash_table_clean_up(&subscription_set->subscriptions); + + aws_mem_release(subscription_set->allocator, subscription_set); +} + +static struct aws_mqtt_subscription_set_topic_tree_node *s_aws_mqtt_subscription_set_get_existing_subscription_node( + const struct aws_mqtt_subscription_set *subscription_set, + struct aws_byte_cursor topic_filter) { + + struct aws_mqtt_subscription_set_topic_tree_node *current_node = subscription_set->root; + + struct aws_byte_cursor topic_segment; + AWS_ZERO_STRUCT(topic_segment); + while (aws_byte_cursor_next_split(&topic_filter, '/', &topic_segment)) { + struct aws_hash_element *hash_element = NULL; + aws_hash_table_find(¤t_node->children, &topic_segment, &hash_element); + + if (hash_element == NULL) { + return NULL; + } else { + current_node = hash_element->value; + } + } + + if (!current_node->is_subscription) { + return NULL; + } + + return current_node; +} + +bool aws_mqtt_subscription_set_is_subscribed( + const struct aws_mqtt_subscription_set *subscription_set, + struct aws_byte_cursor topic_filter) { + + struct aws_hash_element *element = NULL; + aws_hash_table_find(&subscription_set->subscriptions, &topic_filter, &element); + + return element && (element->value != NULL); +} + +bool aws_mqtt_subscription_set_is_in_topic_tree( + const struct aws_mqtt_subscription_set *subscription_set, + struct aws_byte_cursor topic_filter) { + struct aws_mqtt_subscription_set_topic_tree_node *existing_node = + s_aws_mqtt_subscription_set_get_existing_subscription_node(subscription_set, topic_filter); + + return existing_node != NULL; +} + +/* + * Walks the existing tree creating nodes as necessary to reach the subscription leaf implied by the topic filter. + * Returns the node representing the final level of the topic filter. Each existing node has its ref count increased by + * one. Newly-created nodes start with a ref count of one. Given that the topic filter has been validated, the only + * possible error is a memory allocation error which is a crash anyways. + * + * If the leaf node already exists and has a cleanup callback, it will be invoked and both the callback and its user + * data will be cleared . The returned node will always have is_subscription set to true. + */ +static struct aws_mqtt_subscription_set_topic_tree_node * + s_aws_mqtt_subscription_set_create_or_reference_topic_filter_path( + struct aws_mqtt_subscription_set_topic_tree_node *root, + struct aws_byte_cursor topic_filter) { + + struct aws_mqtt_subscription_set_topic_tree_node *current_node = root; + ++root->ref_count; + + /* + * Invariants: + * (1) No failure allowed (allocation failure = crash) + * (2) The ref count of current_node is always correct *before* the loop condition is evaluated + */ + struct aws_byte_cursor topic_segment; + AWS_ZERO_STRUCT(topic_segment); + while (aws_byte_cursor_next_split(&topic_filter, '/', &topic_segment)) { + + struct aws_hash_element *hash_element = NULL; + aws_hash_table_find(¤t_node->children, &topic_segment, &hash_element); + + if (hash_element == NULL) { + struct aws_mqtt_subscription_set_topic_tree_node *new_node = + s_aws_mqtt_subscription_set_node_new(current_node->allocator, current_node); + + aws_byte_buf_init_copy_from_cursor(&new_node->topic_segment, new_node->allocator, topic_segment); + new_node->topic_segment_cursor = aws_byte_cursor_from_buf(&new_node->topic_segment); + + aws_hash_table_put(¤t_node->children, &new_node->topic_segment_cursor, new_node, NULL); + + current_node = new_node; + } else { + current_node = hash_element->value; + ++current_node->ref_count; + } + } + + return current_node; +} + +void aws_mqtt_subscription_set_add_subscription( + struct aws_mqtt_subscription_set *subscription_set, + const struct aws_mqtt_subscription_set_subscription_options *subscription_options) { + + AWS_FATAL_ASSERT(aws_mqtt_is_valid_topic_filter(&subscription_options->topic_filter)); + + aws_hash_table_remove(&subscription_set->subscriptions, &subscription_options->topic_filter, NULL, NULL); + + struct aws_mqtt_subscription_set_subscription_record *record = + aws_mqtt_subscription_set_subscription_record_new(subscription_set->allocator, subscription_options); + aws_hash_table_put(&subscription_set->subscriptions, &record->topic_filter, record, NULL); + + struct aws_mqtt_subscription_set_topic_tree_node *subscription_node = + s_aws_mqtt_subscription_set_get_existing_subscription_node( + subscription_set, subscription_options->topic_filter); + if (subscription_node == NULL) { + subscription_node = s_aws_mqtt_subscription_set_create_or_reference_topic_filter_path( + subscription_set->root, subscription_options->topic_filter); + } + + if (subscription_node->on_cleanup) { + (*subscription_node->on_cleanup)(subscription_node->callback_user_data); + subscription_node->on_cleanup = NULL; + } + + subscription_node->is_subscription = true; + + subscription_node->on_publish_received = subscription_options->on_publish_received; + subscription_node->on_cleanup = subscription_options->on_cleanup; + subscription_node->callback_user_data = subscription_options->callback_user_data; +} + +void aws_mqtt_subscription_set_remove_subscription( + struct aws_mqtt_subscription_set *subscription_set, + struct aws_byte_cursor topic_filter) { + + aws_hash_table_remove(&subscription_set->subscriptions, &topic_filter, NULL, NULL); + + if (!aws_mqtt_subscription_set_is_in_topic_tree(subscription_set, topic_filter)) { + return; + } + + struct aws_mqtt_subscription_set_topic_tree_node *current_node = subscription_set->root; + + struct aws_byte_cursor topic_segment; + AWS_ZERO_STRUCT(topic_segment); + while (aws_byte_cursor_next_split(&topic_filter, '/', &topic_segment)) { + --current_node->ref_count; + + if (current_node->ref_count == 0) { + s_aws_mqtt_subscription_set_node_destroy_tree(current_node); + return; + } + + struct aws_hash_element *hash_element = NULL; + aws_hash_table_find(¤t_node->children, &topic_segment, &hash_element); + + /* We previously validated the full path; this must exist */ + current_node = hash_element->value; + } + + --current_node->ref_count; + if (current_node->ref_count == 0) { + s_aws_mqtt_subscription_set_node_destroy_tree(current_node); + return; + } + + if (current_node->on_cleanup) { + (*current_node->on_cleanup)(current_node->callback_user_data); + current_node->on_cleanup = NULL; + } + + current_node->on_publish_received = NULL; + current_node->is_subscription = false; +} + +struct aws_mqtt_subscription_set_path_continuation { + struct aws_byte_cursor current_fragment; + struct aws_mqtt_subscription_set_topic_tree_node *current_node; +}; + +static void s_add_subscription_set_path_continuation( + struct aws_array_list *paths, + struct aws_byte_cursor fragment, + struct aws_mqtt_subscription_set_topic_tree_node *node) { + if (node == NULL) { + return; + } + + struct aws_mqtt_subscription_set_path_continuation path = { + .current_fragment = fragment, + .current_node = node, + }; + + aws_array_list_push_back(paths, &path); +} + +#define SUBSCRIPTION_SET_PATH_FRAGMENT_DEFAULT 10 + +AWS_STATIC_STRING_FROM_LITERAL(s_single_level_wildcard, "+"); +AWS_STATIC_STRING_FROM_LITERAL(s_multi_level_wildcard, "#"); + +static struct aws_mqtt_subscription_set_topic_tree_node *s_aws_mqtt_subscription_set_node_find_child( + struct aws_mqtt_subscription_set_topic_tree_node *node, + struct aws_byte_cursor fragment) { + struct aws_hash_element *element = NULL; + aws_hash_table_find(&node->children, &fragment, &element); + + if (element == NULL) { + return NULL; + } + + return element->value; +} + +static void s_invoke_on_publish_received( + struct aws_mqtt_subscription_set_topic_tree_node *node, + const struct aws_mqtt_subscription_set_publish_received_options *publish_options) { + if (node == NULL || !node->is_subscription || node->on_publish_received == NULL) { + return; + } + + (*node->on_publish_received)( + publish_options->connection, + &publish_options->topic, + &publish_options->payload, + publish_options->dup, + publish_options->qos, + publish_options->retain, + node->callback_user_data); +} + +void aws_mqtt_subscription_set_on_publish_received( + const struct aws_mqtt_subscription_set *subscription_set, + const struct aws_mqtt_subscription_set_publish_received_options *publish_options) { + + struct aws_byte_cursor slw_cursor = aws_byte_cursor_from_string(s_single_level_wildcard); + struct aws_byte_cursor mlw_cursor = aws_byte_cursor_from_string(s_multi_level_wildcard); + + struct aws_array_list tree_paths; + aws_array_list_init_dynamic( + &tree_paths, + subscription_set->allocator, + SUBSCRIPTION_SET_PATH_FRAGMENT_DEFAULT, + sizeof(struct aws_mqtt_subscription_set_path_continuation)); + + struct aws_byte_cursor empty_cursor; + AWS_ZERO_STRUCT(empty_cursor); + s_add_subscription_set_path_continuation(&tree_paths, empty_cursor, subscription_set->root); + + while (aws_array_list_length(&tree_paths) > 0) { + struct aws_mqtt_subscription_set_path_continuation path_continuation; + AWS_ZERO_STRUCT(path_continuation); + + size_t path_count = aws_array_list_length(&tree_paths); + aws_array_list_get_at(&tree_paths, &path_continuation, path_count - 1); + aws_array_list_pop_back(&tree_paths); + + /* + * Invoke multi-level wildcard check before checking split result; this allows a subscription like + * 'a/b/#' to match an incoming 'a/b' + */ + struct aws_mqtt_subscription_set_topic_tree_node *mlw_node = + s_aws_mqtt_subscription_set_node_find_child(path_continuation.current_node, mlw_cursor); + s_invoke_on_publish_received(mlw_node, publish_options); + + struct aws_byte_cursor next_fragment = path_continuation.current_fragment; + if (!aws_byte_cursor_next_split(&publish_options->topic, '/', &next_fragment)) { + s_invoke_on_publish_received(path_continuation.current_node, publish_options); + continue; + } + + struct aws_mqtt_subscription_set_topic_tree_node *slw_node = + s_aws_mqtt_subscription_set_node_find_child(path_continuation.current_node, slw_cursor); + s_add_subscription_set_path_continuation(&tree_paths, next_fragment, slw_node); + + struct aws_mqtt_subscription_set_topic_tree_node *fragment_node = + s_aws_mqtt_subscription_set_node_find_child(path_continuation.current_node, next_fragment); + s_add_subscription_set_path_continuation(&tree_paths, next_fragment, fragment_node); + } + + aws_array_list_clean_up(&tree_paths); +} + +static int s_subscription_set_subscriptions_hash_get_wrap(void *context, struct aws_hash_element *elem) { + struct aws_array_list *subscriptions = context; + struct aws_mqtt_subscription_set_subscription_record *record = elem->value; + + aws_array_list_push_back(subscriptions, &record->subscription_view); + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; +} + +void aws_mqtt_subscription_set_get_subscriptions( + struct aws_mqtt_subscription_set *subscription_set, + struct aws_array_list *subscriptions) { + AWS_ZERO_STRUCT(*subscriptions); + + size_t subscription_count = aws_hash_table_get_entry_count(&subscription_set->subscriptions); + aws_array_list_init_dynamic( + subscriptions, + subscription_set->allocator, + subscription_count, + sizeof(struct aws_mqtt_subscription_set_subscription_options)); + + aws_hash_table_foreach( + &subscription_set->subscriptions, s_subscription_set_subscriptions_hash_get_wrap, subscriptions); +} diff --git a/contrib/restricted/aws/aws-c-mqtt/source/packets.c b/contrib/restricted/aws/aws-c-mqtt/source/packets.c index 7af2af2673e..1170bcca9c6 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/packets.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/packets.c @@ -595,6 +595,10 @@ bool aws_mqtt_packet_publish_get_dup(const struct aws_mqtt_packet_publish *packe return packet->fixed_header.flags & (1 << 3); /* bit 3 */ } +void aws_mqtt_packet_publish_set_dup(struct aws_mqtt_packet_publish *packet) { + packet->fixed_header.flags |= 0x08; +} + enum aws_mqtt_qos aws_mqtt_packet_publish_get_qos(const struct aws_mqtt_packet_publish *packet) { return (packet->fixed_header.flags >> 1) & 0x3; /* bits 2,1 */ } diff --git a/contrib/restricted/aws/aws-c-mqtt/source/topic_tree.c b/contrib/restricted/aws/aws-c-mqtt/source/topic_tree.c index ca75c9028e4..d2104ed322c 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/topic_tree.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/topic_tree.c @@ -433,6 +433,8 @@ static void s_topic_tree_action_commit(struct topic_tree_action *action, struct /* Clean up and delete */ s_topic_node_destroy(node, tree->allocator); } else { + // We do not delete the current node immediately as we would like to use + // it to update the topic filter of the remaining nodes destroy_current = true; } } else { @@ -450,8 +452,9 @@ static void s_topic_tree_action_commit(struct topic_tree_action *action, struct } } - /* If current owns the full string, go fixup the pointer references. */ - if (nodes_left > 0) { + /* If at least one node is destroyed and there is node(s) remaining in the branch, + * go fixup the topic filter reference . */ + if (nodes_left > 0 && destroy_current) { /* If a new viable topic filter is found once, it can be used for all parents. */ const struct aws_string *new_topic_filter = NULL; @@ -465,7 +468,7 @@ static void s_topic_tree_action_commit(struct topic_tree_action *action, struct size_t topic_offset = parent->topic.ptr - aws_string_bytes(parent->topic_filter) + parent->topic.len + 1; - /* -1 to avoid touching current */ + /* Loop through all remaining nodes to update the topic filters */ for (size_t i = nodes_left; i > 0; --i) { aws_array_list_get_at(&action->to_remove, &parent, i); AWS_ASSUME(parent); /* Must be in bounds */ @@ -493,7 +496,8 @@ static void s_topic_tree_action_commit(struct topic_tree_action *action, struct &parent->subtopics, s_topic_node_string_finder, (void *)&new_topic_filter); /* This would only happen if there is only one topic in subtopics (current's) and - * it has no children (in which case it should have been removed above). */ + * it has no children (in which case it should have been removed above as + `destroy_current` is set to true). */ AWS_ASSERT(new_topic_filter != old_topic_filter); /* Now that the new string has been found, the old one can be destroyed. */ diff --git a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_client.c b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_client.c index 27af76038e6..e6da6d3d397 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_client.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_client.c @@ -12,6 +12,7 @@ #include <aws/http/websocket.h> #include <aws/io/channel_bootstrap.h> #include <aws/io/event_loop.h> +#include <aws/mqtt/private/client_impl_shared.h> #include <aws/mqtt/private/shared_constants.h> #include <aws/mqtt/private/v5/mqtt5_client_impl.h> #include <aws/mqtt/private/v5/mqtt5_options_storage.h> @@ -26,6 +27,7 @@ #define AWS_MQTT5_IO_MESSAGE_DEFAULT_LENGTH 4096 #define AWS_MQTT5_DEFAULT_CONNACK_PACKET_TIMEOUT_MS 10000 +#define DEFAULT_MQTT5_OPERATION_TABLE_SIZE 200 const char *aws_mqtt5_client_state_to_c_string(enum aws_mqtt5_client_state state) { switch (state) { @@ -156,14 +158,6 @@ static int s_aws_mqtt5_client_change_desired_state( enum aws_mqtt5_client_state desired_state, struct aws_mqtt5_operation_disconnect *disconnect_operation); -static uint64_t s_hash_uint16_t(const void *item) { - return *(uint16_t *)item; -} - -static bool s_uint16_t_eq(const void *a, const void *b) { - return *(uint16_t *)a == *(uint16_t *)b; -} - static uint64_t s_aws_mqtt5_client_compute_operational_state_service_time( const struct aws_mqtt5_client_operational_state *client_operational_state, uint64_t now); @@ -178,6 +172,13 @@ static void s_complete_operation( const void *view) { if (client != NULL) { aws_mqtt5_client_statistics_change_operation_statistic_state(client, operation, AWS_MQTT5_OSS_NONE); + if (aws_priority_queue_node_is_in_queue(&operation->priority_queue_node)) { + struct aws_mqtt5_operation *queued_operation = NULL; + aws_priority_queue_remove( + &client->operational_state.operations_by_ack_timeout, + &queued_operation, + &operation->priority_queue_node); + } } aws_mqtt5_operation_complete(operation, error_code, packet_type, view); @@ -203,72 +204,47 @@ static void s_complete_operation_list( } static void s_check_timeouts(struct aws_mqtt5_client *client, uint64_t now) { - if (client->config->ack_timeout_seconds == 0) { - return; - } + struct aws_priority_queue *timeout_queue = &client->operational_state.operations_by_ack_timeout; - struct aws_linked_list_node *node = aws_linked_list_begin(&client->operational_state.unacked_operations); - while (node != aws_linked_list_end(&client->operational_state.unacked_operations)) { - struct aws_mqtt5_operation *operation = AWS_CONTAINER_OF(node, struct aws_mqtt5_operation, node); - node = aws_linked_list_next(node); - if (operation->ack_timeout_timepoint_ns < now) { - /* Timeout for this packet has been reached */ - aws_mqtt5_packet_id_t packet_id = aws_mqtt5_operation_get_packet_id(operation); - - switch (operation->packet_type) { - case AWS_MQTT5_PT_SUBSCRIBE: - /* SUBSCRIBE has timed out. */ - AWS_LOGF_INFO( - AWS_LS_MQTT5_CLIENT, - "id=%p: SUBSCRIBE packet with id:%d has timed out", - (void *)client, - packet_id); - break; + bool done = aws_priority_queue_size(timeout_queue) == 0; + while (!done) { + struct aws_mqtt5_operation **next_operation_by_timeout_ptr = NULL; + aws_priority_queue_top(timeout_queue, (void **)&next_operation_by_timeout_ptr); + AWS_FATAL_ASSERT(next_operation_by_timeout_ptr != NULL); + struct aws_mqtt5_operation *next_operation_by_timeout = *next_operation_by_timeout_ptr; + AWS_FATAL_ASSERT(next_operation_by_timeout != NULL); - case AWS_MQTT5_PT_UNSUBSCRIBE: - /* UNSUBSCRIBE has timed out. */ - AWS_LOGF_INFO( - AWS_LS_MQTT5_CLIENT, - "id=%p: UNSUBSCRIBE packet with id:%d has timed out", - (void *)client, - packet_id); - break; + // If the top of the heap hasn't timed out than nothing has + if (next_operation_by_timeout->ack_timeout_timepoint_ns > now) { + break; + } - case AWS_MQTT5_PT_PUBLISH: - /* PUBLISH has timed out. */ - AWS_LOGF_INFO( - AWS_LS_MQTT5_CLIENT, - "id=%p: PUBLISH packet with id:%d has timed out", - (void *)client, - packet_id); + /* Ack timeout for this operation has been reached */ + aws_priority_queue_pop(timeout_queue, &next_operation_by_timeout); - aws_mqtt5_client_flow_control_state_on_puback(client); - break; + aws_mqtt5_packet_id_t packet_id = aws_mqtt5_operation_get_packet_id(next_operation_by_timeout); + AWS_LOGF_INFO( + AWS_LS_MQTT5_CLIENT, + "id=%p: %s packet with id:%d has timed out", + (void *)client, + aws_mqtt5_packet_type_to_c_string(next_operation_by_timeout->packet_type), + (int)packet_id); - default: - /* something is wrong, there should be no other packet type in this linked list */ - break; - } + struct aws_hash_element *elem = NULL; + aws_hash_table_find(&client->operational_state.unacked_operations_table, &packet_id, &elem); - struct aws_hash_element *elem = NULL; - aws_hash_table_find(&client->operational_state.unacked_operations_table, &packet_id, &elem); + if (elem == NULL || elem->value == NULL) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_CLIENT, "id=%p: timeout for unknown operation with id %d", (void *)client, (int)packet_id); + return; + } - if (elem == NULL || elem->value == NULL) { - AWS_LOGF_ERROR( - AWS_LS_MQTT5_CLIENT, - "id=%p: timeout for unknown operation with id %d", - (void *)client, - (int)packet_id); - return; - } + aws_linked_list_remove(&next_operation_by_timeout->node); + aws_hash_table_remove(&client->operational_state.unacked_operations_table, &packet_id, NULL, NULL); - aws_linked_list_remove(&operation->node); - aws_hash_table_remove(&client->operational_state.unacked_operations_table, &packet_id, NULL, NULL); + s_complete_operation(client, next_operation_by_timeout, AWS_ERROR_MQTT_TIMEOUT, AWS_MQTT5_PT_NONE, NULL); - s_complete_operation(client, operation, AWS_ERROR_MQTT_TIMEOUT, AWS_MQTT5_PT_NONE, NULL); - } else { - break; - } + done = aws_priority_queue_size(timeout_queue) == 0; } } @@ -447,7 +423,11 @@ static uint64_t s_compute_next_service_time_client_mqtt_connect(struct aws_mqtt5 return aws_min_u64(client->next_mqtt_connect_packet_timeout_time, operation_processing_time); } -static uint64_t s_min_non_0_64(uint64_t a, uint64_t b) { +/* + * Returns the minimum of two numbers, ignoring zero. Zero is returned only if both are zero. Useful when we're + * computing (next service) timepoints and zero means "no timepoint" + */ +static uint64_t s_min_non_zero_u64(uint64_t a, uint64_t b) { if (a == 0) { return b; } @@ -459,6 +439,19 @@ static uint64_t s_min_non_0_64(uint64_t a, uint64_t b) { return aws_min_u64(a, b); } +/* + * If there are unacked operations, returns the earliest point in time that one could timeout. + */ +static uint64_t s_get_unacked_operation_timeout_for_next_service_time(struct aws_mqtt5_client *client) { + if (aws_priority_queue_size(&client->operational_state.operations_by_ack_timeout) > 0) { + struct aws_mqtt5_operation **operation = NULL; + aws_priority_queue_top(&client->operational_state.operations_by_ack_timeout, (void **)&operation); + return (*operation)->ack_timeout_timepoint_ns; + } + + return 0; +} + static uint64_t s_compute_next_service_time_client_connected(struct aws_mqtt5_client *client, uint64_t now) { /* ping and ping timeout */ @@ -467,13 +460,8 @@ static uint64_t s_compute_next_service_time_client_connected(struct aws_mqtt5_cl next_service_time = aws_min_u64(next_service_time, client->next_ping_timeout_time); } - /* unacked operations timeout */ - if (client->config->ack_timeout_seconds != 0 && - !aws_linked_list_empty(&client->operational_state.unacked_operations)) { - struct aws_linked_list_node *node = aws_linked_list_begin(&client->operational_state.unacked_operations); - struct aws_mqtt5_operation *operation = AWS_CONTAINER_OF(node, struct aws_mqtt5_operation, node); - next_service_time = aws_min_u64(next_service_time, operation->ack_timeout_timepoint_ns); - } + next_service_time = + s_min_non_zero_u64(next_service_time, s_get_unacked_operation_timeout_for_next_service_time(client)); if (client->desired_state != AWS_MCS_CONNECTED) { next_service_time = now; @@ -482,29 +470,21 @@ static uint64_t s_compute_next_service_time_client_connected(struct aws_mqtt5_cl uint64_t operation_processing_time = s_aws_mqtt5_client_compute_operational_state_service_time(&client->operational_state, now); - next_service_time = s_min_non_0_64(operation_processing_time, next_service_time); + next_service_time = s_min_non_zero_u64(operation_processing_time, next_service_time); /* reset reconnect delay interval */ - next_service_time = s_min_non_0_64(client->next_reconnect_delay_reset_time_ns, next_service_time); + next_service_time = s_min_non_zero_u64(client->next_reconnect_delay_reset_time_ns, next_service_time); return next_service_time; } static uint64_t s_compute_next_service_time_client_clean_disconnect(struct aws_mqtt5_client *client, uint64_t now) { - uint64_t ack_timeout_time = 0; - - /* unacked operations timeout */ - if (client->config->ack_timeout_seconds != 0 && - !aws_linked_list_empty(&client->operational_state.unacked_operations)) { - struct aws_linked_list_node *node = aws_linked_list_begin(&client->operational_state.unacked_operations); - struct aws_mqtt5_operation *operation = AWS_CONTAINER_OF(node, struct aws_mqtt5_operation, node); - ack_timeout_time = operation->ack_timeout_timepoint_ns; - } + uint64_t ack_timeout_time = s_get_unacked_operation_timeout_for_next_service_time(client); uint64_t operation_processing_time = s_aws_mqtt5_client_compute_operational_state_service_time(&client->operational_state, now); - return s_min_non_0_64(ack_timeout_time, operation_processing_time); + return s_min_non_zero_u64(ack_timeout_time, operation_processing_time); } static uint64_t s_compute_next_service_time_client_channel_shutdown(struct aws_mqtt5_client *client, uint64_t now) { @@ -622,8 +602,10 @@ static void s_aws_mqtt5_client_operational_state_reset( s_complete_operation_list(client, &client_operational_state->unacked_operations, completion_error_code); if (is_final) { + aws_priority_queue_clean_up(&client_operational_state->operations_by_ack_timeout); aws_hash_table_clean_up(&client_operational_state->unacked_operations_table); } else { + aws_priority_queue_clear(&client->operational_state.operations_by_ack_timeout); aws_hash_table_clear(&client_operational_state->unacked_operations_table); } } @@ -715,30 +697,6 @@ static void s_aws_mqtt5_client_shutdown_channel_clean( aws_mqtt5_operation_disconnect_release(disconnect_op); } -struct aws_mqtt5_shutdown_task { - struct aws_task task; - struct aws_allocator *allocator; - int error_code; - struct aws_mqtt5_client *client; -}; - -static void s_mqtt5_client_shutdown_final(int error_code, struct aws_mqtt5_client *client); - -static void s_shutdown_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { - (void)task; - - struct aws_mqtt5_shutdown_task *shutdown_task = arg; - if (status != AWS_TASK_STATUS_RUN_READY) { - goto done; - } - - s_mqtt5_client_shutdown_final(shutdown_task->error_code, shutdown_task->client); - -done: - - aws_mem_release(shutdown_task->allocator, shutdown_task); -} - static void s_mqtt5_client_shutdown_final(int error_code, struct aws_mqtt5_client *client) { AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(client->loop)); @@ -782,19 +740,8 @@ static void s_mqtt5_client_shutdown( error_code = AWS_ERROR_MQTT_UNEXPECTED_HANGUP; } - if (aws_event_loop_thread_is_callers_thread(client->loop)) { - s_mqtt5_client_shutdown_final(error_code, client); - return; - } - - struct aws_mqtt5_shutdown_task *shutdown_task = - aws_mem_calloc(client->allocator, 1, sizeof(struct aws_mqtt5_shutdown_task)); - - aws_task_init(&shutdown_task->task, s_shutdown_task_fn, (void *)shutdown_task, "ShutdownTask"); - shutdown_task->allocator = client->allocator; - shutdown_task->client = client; - shutdown_task->error_code = error_code; - aws_event_loop_schedule_task_now(client->loop, &shutdown_task->task); + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(client->loop)); + s_mqtt5_client_shutdown_final(error_code, client); } static void s_mqtt5_client_setup( @@ -939,7 +886,7 @@ void s_websocket_transform_complete_task_fn(struct aws_task *task, void *arg, en .on_connection_setup = s_on_websocket_setup, .on_connection_shutdown = s_on_websocket_shutdown, .requested_event_loop = client->loop, - }; + .host_resolution_config = &client->config->host_resolution_override}; if (client->config->http_proxy_config != NULL) { websocket_options.proxy_options = &client->config->http_proxy_options; @@ -991,8 +938,6 @@ static void s_websocket_handshake_transform_complete( task->handshake = handshake_request; aws_event_loop_schedule_task_now(client->loop, &task->task); - - aws_mqtt5_client_release(client); } static int s_websocket_connect(struct aws_mqtt5_client *client) { @@ -1016,7 +961,11 @@ static int s_websocket_connect(struct aws_mqtt5_client *client) { AWS_LOGF_TRACE(AWS_LS_MQTT5_CLIENT, "id=%p: Transforming websocket handshake request.", (void *)client); - aws_mqtt5_client_acquire(client); + /* + * There is no need to inc the client's ref count here since this state (AWS_MCS_CONNECTING) is uninterruptible by + * the async destruction process. Only a completion of the chain of connection establishment callbacks can cause + * this state to be left by the client. + */ client->config->websocket_handshake_transform( handshake, client->config->websocket_handshake_transform_user_data, @@ -1037,6 +986,7 @@ static void s_change_current_state_to_connecting(struct aws_mqtt5_client *client client->current_state = AWS_MCS_CONNECTING; client->clean_disconnect_error_code = AWS_ERROR_SUCCESS; + client->should_reset_connection = false; s_aws_mqtt5_client_emit_connecting_lifecycle_event(client); @@ -1055,6 +1005,7 @@ static void s_change_current_state_to_connecting(struct aws_mqtt5_client *client channel_options.shutdown_callback = &s_mqtt5_client_shutdown; channel_options.user_data = client; channel_options.requested_event_loop = client->loop; + channel_options.host_resolution_override_config = &client->config->host_resolution_override; if (client->config->http_proxy_config == NULL) { result = (*client->vtable->client_bootstrap_new_socket_channel_fn)(&channel_options); @@ -1118,7 +1069,11 @@ static void s_reset_ping(struct aws_mqtt5_client *client) { uint64_t keep_alive_interval_nanos = aws_timestamp_convert(keep_alive_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); - client->next_ping_time = aws_add_u64_saturating(now, keep_alive_interval_nanos); + if (keep_alive_interval_nanos == 0) { + client->next_ping_time = UINT64_MAX; + } else { + client->next_ping_time = aws_add_u64_saturating(now, keep_alive_interval_nanos); + } AWS_LOGF_DEBUG( AWS_LS_MQTT5_CLIENT, "id=%p: next PINGREQ scheduled for time %" PRIu64, (void *)client, client->next_ping_time); @@ -1197,13 +1152,18 @@ static void s_change_current_state_to_mqtt_connect(struct aws_mqtt5_client *clie AWS_FATAL_ASSERT(client->operational_state.current_operation == NULL); client->current_state = AWS_MCS_MQTT_CONNECT; + if (client->should_reset_connection) { + s_aws_mqtt5_client_shutdown_channel(client, AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT); + return; + } + client->operational_state.pending_write_completion = false; aws_mqtt5_encoder_reset(&client->encoder); aws_mqtt5_decoder_reset(&client->decoder); bool resume_session = s_should_resume_session(client); - struct aws_mqtt5_packet_connect_view connect_view = client->config->connect.storage_view; + struct aws_mqtt5_packet_connect_view connect_view = client->config->connect->storage_view; connect_view.clean_start = !resume_session; if (aws_mqtt5_inbound_topic_alias_behavior_type_to_non_default( @@ -1635,7 +1595,7 @@ static int s_process_read_message( if (message->message_type != AWS_IO_MESSAGE_APPLICATION_DATA) { AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "id=%p: unexpected io message data", (void *)client); - return AWS_OP_ERR; + return aws_raise_error(AWS_ERROR_INVALID_STATE); } AWS_LOGF_TRACE( @@ -2146,15 +2106,11 @@ struct aws_mqtt_change_desired_state_task { struct aws_mqtt5_operation_disconnect *disconnect_operation; }; -static void s_change_state_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { - (void)task; - - struct aws_mqtt_change_desired_state_task *change_state_task = arg; - struct aws_mqtt5_client *client = change_state_task->client; - enum aws_mqtt5_client_state desired_state = change_state_task->desired_state; - if (status != AWS_TASK_STATUS_RUN_READY) { - goto done; - } +void aws_mqtt5_client_change_desired_state( + struct aws_mqtt5_client *client, + enum aws_mqtt5_client_state desired_state, + struct aws_mqtt5_operation_disconnect *disconnect_op) { + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(client->loop)); if (client->desired_state != desired_state) { AWS_LOGF_INFO( @@ -2166,7 +2122,6 @@ static void s_change_state_task_fn(struct aws_task *task, void *arg, enum aws_ta client->desired_state = desired_state; - struct aws_mqtt5_operation_disconnect *disconnect_op = change_state_task->disconnect_operation; if (desired_state == AWS_MCS_STOPPED && disconnect_op != NULL) { s_aws_mqtt5_client_shutdown_channel_with_disconnect( client, AWS_ERROR_MQTT5_USER_REQUESTED_STOP, disconnect_op); @@ -2174,6 +2129,19 @@ static void s_change_state_task_fn(struct aws_task *task, void *arg, enum aws_ta s_reevaluate_service_task(client); } +} + +static void s_change_state_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_change_desired_state_task *change_state_task = arg; + struct aws_mqtt5_client *client = change_state_task->client; + enum aws_mqtt5_client_state desired_state = change_state_task->desired_state; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + aws_mqtt5_client_change_desired_state(client, desired_state, change_state_task->disconnect_operation); done: @@ -2297,51 +2265,51 @@ struct aws_mqtt5_submit_operation_task { struct aws_mqtt5_operation *operation; }; -static void s_mqtt5_submit_operation_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { - (void)task; - - int completion_error_code = AWS_ERROR_MQTT5_CLIENT_TERMINATED; - struct aws_mqtt5_submit_operation_task *submit_operation_task = arg; +void aws_mqtt5_client_submit_operation_internal( + struct aws_mqtt5_client *client, + struct aws_mqtt5_operation *operation, + bool is_terminated) { /* * Take a ref to the operation that represents the client taking ownership * If we subsequently reject it (task cancel or offline queue policy), then the operation completion * will undo this ref acquisition. */ - aws_mqtt5_operation_acquire(submit_operation_task->operation); + aws_mqtt5_operation_acquire(operation); - if (status != AWS_TASK_STATUS_RUN_READY) { - goto error; + if (is_terminated) { + s_complete_operation(NULL, operation, AWS_ERROR_MQTT5_CLIENT_TERMINATED, AWS_MQTT5_PT_NONE, NULL); + return; } /* * If we're offline and this operation doesn't meet the requirements of the offline queue retention policy, * fail it immediately. */ - struct aws_mqtt5_client *client = submit_operation_task->client; - struct aws_mqtt5_operation *operation = submit_operation_task->operation; if (client->current_state != AWS_MCS_CONNECTED) { if (!s_aws_mqtt5_operation_satisfies_offline_queue_retention_policy( operation, client->config->offline_queue_behavior)) { - completion_error_code = AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY; - goto error; + s_complete_operation( + NULL, operation, AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY, AWS_MQTT5_PT_NONE, NULL); + return; } } /* newly-submitted operations must have a 0 packet id */ - aws_mqtt5_operation_set_packet_id(submit_operation_task->operation, 0); - - s_enqueue_operation_back(submit_operation_task->client, submit_operation_task->operation); - aws_mqtt5_client_statistics_change_operation_statistic_state( - submit_operation_task->client, submit_operation_task->operation, AWS_MQTT5_OSS_INCOMPLETE); + aws_mqtt5_operation_set_packet_id(operation, 0); - goto done; + s_enqueue_operation_back(client, operation); + aws_mqtt5_client_statistics_change_operation_statistic_state(client, operation, AWS_MQTT5_OSS_INCOMPLETE); +} -error: +static void s_mqtt5_submit_operation_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; - s_complete_operation(NULL, submit_operation_task->operation, completion_error_code, AWS_MQTT5_PT_NONE, NULL); + struct aws_mqtt5_submit_operation_task *submit_operation_task = arg; + struct aws_mqtt5_client *client = submit_operation_task->client; + struct aws_mqtt5_operation *operation = submit_operation_task->operation; -done: + aws_mqtt5_client_submit_operation_internal(client, operation, status != AWS_TASK_STATUS_RUN_READY); aws_mqtt5_operation_release(submit_operation_task->operation); aws_mqtt5_client_release(submit_operation_task->client); @@ -2515,6 +2483,25 @@ int aws_mqtt5_operation_bind_packet_id( return AWS_OP_ERR; } +/* + * Priority queue comparison function for ack timeout processing + */ +static int s_compare_operation_timeouts(const void *a, const void *b) { + const struct aws_mqtt5_operation **operation_a_ptr = (void *)a; + const struct aws_mqtt5_operation *operation_a = *operation_a_ptr; + + const struct aws_mqtt5_operation **operation_b_ptr = (void *)b; + const struct aws_mqtt5_operation *operation_b = *operation_b_ptr; + + if (operation_a->ack_timeout_timepoint_ns < operation_b->ack_timeout_timepoint_ns) { + return -1; + } else if (operation_a->ack_timeout_timepoint_ns > operation_b->ack_timeout_timepoint_ns) { + return 1; + } else { + return 0; + } +} + int aws_mqtt5_client_operational_state_init( struct aws_mqtt5_client_operational_state *client_operational_state, struct aws_allocator *allocator, @@ -2527,14 +2514,23 @@ int aws_mqtt5_client_operational_state_init( if (aws_hash_table_init( &client_operational_state->unacked_operations_table, allocator, - sizeof(struct aws_mqtt5_operation *), - s_hash_uint16_t, - s_uint16_t_eq, + DEFAULT_MQTT5_OPERATION_TABLE_SIZE, + aws_mqtt_hash_uint16_t, + aws_mqtt_compare_uint16_t_eq, NULL, NULL)) { return AWS_OP_ERR; } + if (aws_priority_queue_init_dynamic( + &client_operational_state->operations_by_ack_timeout, + allocator, + 100, + sizeof(struct aws_mqtt5_operation *), + s_compare_operation_timeouts)) { + return AWS_OP_ERR; + } + client_operational_state->next_mqtt_packet_id = 1; client_operational_state->current_operation = NULL; client_operational_state->client = client; @@ -2649,6 +2645,7 @@ void aws_mqtt5_client_on_disconnection_update_operational_state(struct aws_mqtt5 client, &operations_to_fail, AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY); aws_hash_table_clear(&client->operational_state.unacked_operations_table); + aws_priority_queue_clear(&client->operational_state.operations_by_ack_timeout); /* * Prevents inbound resolution on the highly unlikely, illegal server behavior of sending a PUBLISH before @@ -2947,7 +2944,20 @@ static void s_on_pingreq_send(struct aws_mqtt5_client *client) { uint64_t now = client->vtable->get_current_time_fn(); uint64_t ping_timeout_nanos = aws_timestamp_convert(client->config->ping_timeout_ms, AWS_TIMESTAMP_MILLIS, AWS_TIMESTAMP_NANOS, NULL); - client->next_ping_timeout_time = aws_add_u64_saturating(now, ping_timeout_nanos); + uint64_t half_keep_alive_nanos = + aws_timestamp_convert( + client->negotiated_settings.server_keep_alive, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL) / + 2; + + uint64_t connection_ping_timeout = ping_timeout_nanos; + if (connection_ping_timeout == 0 || connection_ping_timeout > half_keep_alive_nanos) { + connection_ping_timeout = half_keep_alive_nanos; + } + + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_CLIENT, "id=%p: dynamic ping timeout: %" PRIu64 " ns", (void *)client, connection_ping_timeout); + + client->next_ping_timeout_time = aws_add_u64_saturating(now, connection_ping_timeout); } static int s_apply_throughput_flow_control(struct aws_mqtt5_client *client) { @@ -3014,14 +3024,18 @@ int aws_mqtt5_client_service_operational_state(struct aws_mqtt5_client_operation struct aws_mqtt5_operation *next_operation = NULL; while (!aws_linked_list_empty(&client_operational_state->queued_operations)) { struct aws_linked_list_node *next_operation_node = - aws_linked_list_pop_front(&client_operational_state->queued_operations); + aws_linked_list_front(&client_operational_state->queued_operations); struct aws_mqtt5_operation *operation = AWS_CONTAINER_OF(next_operation_node, struct aws_mqtt5_operation, node); + /* If this is a publish and we're throttled, just quit out of the loop. */ if (s_apply_publish_tps_flow_control(client, operation)) { break; } + /* Wait until flow control has passed before actually dequeuing the operation. */ + aws_linked_list_pop_front(&client_operational_state->queued_operations); + if (!aws_mqtt5_operation_validate_vs_connection_settings(operation, client)) { next_operation = operation; break; @@ -3079,10 +3093,24 @@ int aws_mqtt5_client_service_operational_state(struct aws_mqtt5_client_operation break; } - if (client->config->ack_timeout_seconds != 0) { + uint32_t ack_timeout_seconds = aws_mqtt5_operation_get_ack_timeout_override(current_operation); + if (ack_timeout_seconds == 0) { + ack_timeout_seconds = client->config->ack_timeout_seconds; + } + + if (ack_timeout_seconds > 0) { current_operation->ack_timeout_timepoint_ns = - now + aws_timestamp_convert( - client->config->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + now + aws_timestamp_convert(ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + } else { + current_operation->ack_timeout_timepoint_ns = UINT64_MAX; + } + + if (aws_priority_queue_push_ref( + &client_operational_state->operations_by_ack_timeout, + (void *)¤t_operation, + ¤t_operation->priority_queue_node)) { + operational_error_code = aws_last_error(); + break; } aws_linked_list_push_back(&client_operational_state->unacked_operations, ¤t_operation->node); @@ -3342,3 +3370,25 @@ void aws_mqtt5_client_get_stats(struct aws_mqtt5_client *client, struct aws_mqtt stats->unacked_operation_size = (uint64_t)aws_atomic_load_int(&client->operation_statistics_impl.unacked_operation_size_atomic); } + +bool aws_mqtt5_client_reset_connection(struct aws_mqtt5_client *client) { + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(client->loop)); + + client->current_reconnect_delay_ms = client->config->min_reconnect_delay_ms; + + switch (client->current_state) { + case AWS_MCS_MQTT_CONNECT: + case AWS_MCS_CONNECTED: + s_aws_mqtt5_client_shutdown_channel(client, AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT); + return true; + + case AWS_MCS_CONNECTING: + client->should_reset_connection = true; + return true; + + default: + break; + } + + return false; +} diff --git a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_encoder.c b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_encoder.c index b9a3ec56ec0..fe1ab3aba07 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_encoder.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_encoder.c @@ -800,7 +800,25 @@ static int s_aws_mqtt5_encoder_begin_publish(struct aws_mqtt5_encoder *encoder, local_publish_view.topic = outbound_topic; if (outbound_topic_alias != 0) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_GENERAL, + "(%p) mqtt5 client encoder - PUBLISH packet using topic alias value %" PRIu16, + (void *)encoder->config.client, + outbound_topic_alias); + if (outbound_topic.len == 0) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_GENERAL, + "(%p) mqtt5 client encoder - PUBLISH packet dropping topic field for previously established alias", + (void *)encoder->config.client); + } local_publish_view.topic_alias = &outbound_topic_alias; + } else { + AWS_FATAL_ASSERT(local_publish_view.topic.len > 0); + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_GENERAL, + "(%p) mqtt5 client encoder - PUBLISH packet not using a topic alias", + (void *)encoder->config.client); + local_publish_view.topic_alias = NULL; } } diff --git a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_options_storage.c b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_options_storage.c index b8b566e910b..6510c6ab21f 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_options_storage.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_options_storage.c @@ -80,19 +80,6 @@ size_t aws_mqtt5_user_property_set_size(const struct aws_mqtt5_user_property_set return aws_array_list_length(&property_set->properties); } -int aws_mqtt5_user_property_set_get_property( - const struct aws_mqtt5_user_property_set *property_set, - size_t index, - struct aws_mqtt5_user_property *property_out) { - return aws_array_list_get_at(&property_set->properties, property_out, index); -} - -int aws_mqtt5_user_property_set_add_stored_property( - struct aws_mqtt5_user_property_set *property_set, - struct aws_mqtt5_user_property *property) { - return aws_array_list_push_back(&property_set->properties, property); -} - static void s_aws_mqtt5_user_property_set_log( struct aws_logger *log_handle, const struct aws_mqtt5_user_property *properties, @@ -185,7 +172,7 @@ static int s_aws_mqtt5_user_property_set_validate( return aws_raise_error(AWS_ERROR_MQTT5_USER_PROPERTY_VALIDATION); } - if (aws_mqtt5_validate_utf8_text(property->name)) { + if (aws_mqtt_validate_utf8_text(property->name)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: %s - user property #%zu name not valid UTF8", log_context, log_prefix, i); return aws_raise_error(AWS_ERROR_MQTT5_USER_PROPERTY_VALIDATION); @@ -200,7 +187,7 @@ static int s_aws_mqtt5_user_property_set_validate( property->value.len); return aws_raise_error(AWS_ERROR_MQTT5_USER_PROPERTY_VALIDATION); } - if (aws_mqtt5_validate_utf8_text(property->value)) { + if (aws_mqtt_validate_utf8_text(property->value)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: %s - user property #%zu value not valid UTF8", @@ -322,11 +309,20 @@ int aws_mqtt5_operation_validate_vs_connection_settings( return AWS_OP_SUCCESS; } +uint32_t aws_mqtt5_operation_get_ack_timeout_override(const struct aws_mqtt5_operation *operation) { + if (operation->vtable->aws_mqtt5_operation_get_ack_timeout_override_fn != NULL) { + return (*operation->vtable->aws_mqtt5_operation_get_ack_timeout_override_fn)(operation); + } + + return 0; +} + static struct aws_mqtt5_operation_vtable s_empty_operation_vtable = { .aws_mqtt5_operation_completion_fn = NULL, .aws_mqtt5_operation_set_packet_id_fn = NULL, .aws_mqtt5_operation_get_packet_id_address_fn = NULL, .aws_mqtt5_operation_validate_vs_connection_settings_fn = NULL, + .aws_mqtt5_operation_get_ack_timeout_override_fn = NULL, }; /********************************************************************************************************************* @@ -345,7 +341,7 @@ int aws_mqtt5_packet_connect_view_validate(const struct aws_mqtt5_packet_connect return aws_raise_error(AWS_ERROR_MQTT5_CONNECT_OPTIONS_VALIDATION); } - if (aws_mqtt5_validate_utf8_text(connect_options->client_id)) { + if (aws_mqtt_validate_utf8_text(connect_options->client_id)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_connect_view - client id not valid UTF-8", @@ -362,7 +358,7 @@ int aws_mqtt5_packet_connect_view_validate(const struct aws_mqtt5_packet_connect return aws_raise_error(AWS_ERROR_MQTT5_CONNECT_OPTIONS_VALIDATION); } - if (aws_mqtt5_validate_utf8_text(*connect_options->username)) { + if (aws_mqtt_validate_utf8_text(*connect_options->username)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_connect_view - username not valid UTF-8", @@ -833,6 +829,7 @@ struct aws_mqtt5_operation_connect *aws_mqtt5_operation_connect_new( connect_op->base.vtable = &s_empty_operation_vtable; connect_op->base.packet_type = AWS_MQTT5_PT_CONNECT; aws_ref_count_init(&connect_op->base.ref_count, connect_op, s_destroy_operation_connect); + aws_priority_queue_node_init(&connect_op->base.priority_queue_node); connect_op->base.impl = connect_op; if (aws_mqtt5_packet_connect_storage_init(&connect_op->options_storage, allocator, connect_options)) { @@ -1272,7 +1269,7 @@ int aws_mqtt5_packet_disconnect_view_validate(const struct aws_mqtt5_packet_disc return aws_raise_error(AWS_ERROR_MQTT5_DISCONNECT_OPTIONS_VALIDATION); } - if (aws_mqtt5_validate_utf8_text(*disconnect_view->reason_string)) { + if (aws_mqtt_validate_utf8_text(*disconnect_view->reason_string)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_disconnect_view - reason string not valid UTF-8", @@ -1313,7 +1310,7 @@ static int s_aws_mqtt5_packet_disconnect_view_validate_vs_connection_settings( * cannot set a non-zero value here if you sent a 0-value or no value in the CONNECT (presumably allows * the server to skip tracking session state, and we can't undo that now) */ - const uint32_t *session_expiry_ptr = client->config->connect.storage_view.session_expiry_interval_seconds; + const uint32_t *session_expiry_ptr = client->config->connect->storage_view.session_expiry_interval_seconds; if (*disconnect_view->session_expiry_interval_seconds > 0 && (session_expiry_ptr == NULL || *session_expiry_ptr == 0)) { AWS_LOGF_ERROR( @@ -1515,6 +1512,7 @@ static struct aws_mqtt5_operation_vtable s_disconnect_operation_vtable = { .aws_mqtt5_operation_get_packet_id_address_fn = NULL, .aws_mqtt5_operation_validate_vs_connection_settings_fn = s_aws_mqtt5_packet_disconnect_view_validate_vs_connection_settings, + .aws_mqtt5_operation_get_ack_timeout_override_fn = NULL, }; struct aws_mqtt5_operation_disconnect *aws_mqtt5_operation_disconnect_new( @@ -1538,6 +1536,7 @@ struct aws_mqtt5_operation_disconnect *aws_mqtt5_operation_disconnect_new( disconnect_op->base.vtable = &s_disconnect_operation_vtable; disconnect_op->base.packet_type = AWS_MQTT5_PT_DISCONNECT; aws_ref_count_init(&disconnect_op->base.ref_count, disconnect_op, s_destroy_operation_disconnect); + aws_priority_queue_node_init(&disconnect_op->base.priority_queue_node); disconnect_op->base.impl = disconnect_op; if (aws_mqtt5_packet_disconnect_storage_init(&disconnect_op->options_storage, allocator, disconnect_options)) { @@ -1604,7 +1603,7 @@ int aws_mqtt5_packet_publish_view_validate(const struct aws_mqtt5_packet_publish AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_publish_view - missing topic", (void *)publish_view); return aws_raise_error(AWS_ERROR_MQTT5_PUBLISH_OPTIONS_VALIDATION); - } else if (aws_mqtt5_validate_utf8_text(publish_view->topic)) { + } else if (aws_mqtt_validate_utf8_text(publish_view->topic)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_publish_view - topic not valid UTF-8", (void *)publish_view); return aws_raise_error(AWS_ERROR_MQTT5_PUBLISH_OPTIONS_VALIDATION); @@ -1639,7 +1638,7 @@ int aws_mqtt5_packet_publish_view_validate(const struct aws_mqtt5_packet_publish // Make sure the payload data is UTF-8 if the payload_format set to UTF8 if (*publish_view->payload_format == AWS_MQTT5_PFI_UTF8) { - if (aws_mqtt5_validate_utf8_text(publish_view->payload)) { + if (aws_mqtt_validate_utf8_text(publish_view->payload)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_publish_view - payload value is not valid UTF-8 while payload format " @@ -1659,7 +1658,7 @@ int aws_mqtt5_packet_publish_view_validate(const struct aws_mqtt5_packet_publish return aws_raise_error(AWS_ERROR_MQTT5_PUBLISH_OPTIONS_VALIDATION); } - if (aws_mqtt5_validate_utf8_text(*publish_view->response_topic)) { + if (aws_mqtt_validate_utf8_text(*publish_view->response_topic)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_publish_view - response topic not valid UTF-8", @@ -1705,7 +1704,7 @@ int aws_mqtt5_packet_publish_view_validate(const struct aws_mqtt5_packet_publish return aws_raise_error(AWS_ERROR_MQTT5_PUBLISH_OPTIONS_VALIDATION); } - if (aws_mqtt5_validate_utf8_text(*publish_view->content_type)) { + if (aws_mqtt_validate_utf8_text(*publish_view->content_type)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_publish_view - content type not valid UTF-8", @@ -1725,19 +1724,6 @@ int aws_mqtt5_packet_publish_view_validate(const struct aws_mqtt5_packet_publish return AWS_OP_SUCCESS; } -int aws_mqtt5_packet_publish_view_validate_vs_iot_core(const struct aws_mqtt5_packet_publish_view *publish_view) { - if (!aws_mqtt_is_valid_topic_for_iot_core(publish_view->topic)) { - AWS_LOGF_ERROR( - AWS_LS_MQTT5_GENERAL, - "id=%p: aws_mqtt5_packet_publish_view - topic not valid for AWS Iot Core limits: \"" PRInSTR "\"", - (void *)publish_view, - AWS_BYTE_CURSOR_PRI(publish_view->topic)); - return AWS_OP_ERR; - } - - return AWS_OP_SUCCESS; -} - static int s_aws_mqtt5_packet_publish_view_validate_vs_connection_settings( const void *packet_view, const struct aws_mqtt5_client *client) { @@ -1757,30 +1743,6 @@ static int s_aws_mqtt5_packet_publish_view_validate_vs_connection_settings( return aws_raise_error(AWS_ERROR_MQTT5_PUBLISH_OPTIONS_VALIDATION); } - if (publish_view->topic_alias != NULL) { - const struct aws_mqtt5_client_options_storage *client_options = client->config; - if (client_options->topic_aliasing_options.outbound_topic_alias_behavior != AWS_MQTT5_COTABT_USER) { - AWS_LOGF_ERROR( - AWS_LS_MQTT5_GENERAL, - "id=%p: aws_mqtt5_packet_publish_view - topic alias set but outbound topic alias behavior has not " - "been set to user controlled", - (void *)publish_view); - return aws_raise_error(AWS_ERROR_MQTT5_PUBLISH_OPTIONS_VALIDATION); - } - - if (*publish_view->topic_alias > settings->topic_alias_maximum_to_server) { - AWS_LOGF_ERROR( - AWS_LS_MQTT5_GENERAL, - "id=%p: aws_mqtt5_packet_publish_view - outbound topic alias (%d) exceeds server's topic alias " - "maximum " - "(%d)", - (void *)publish_view, - (int)(*publish_view->topic_alias), - (int)settings->topic_alias_maximum_to_server); - return aws_raise_error(AWS_ERROR_MQTT5_PUBLISH_OPTIONS_VALIDATION); - } - } - if (publish_view->retain && settings->retain_available == false) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, @@ -2089,13 +2051,18 @@ static aws_mqtt5_packet_id_t *s_aws_mqtt5_operation_publish_get_packet_id_addres return &publish_op->options_storage.storage_view.packet_id; } +static uint32_t s_aws_mqtt5_operation_publish_get_ack_timeout_override(const struct aws_mqtt5_operation *operation) { + struct aws_mqtt5_operation_publish *publish_op = operation->impl; + return publish_op->completion_options.ack_timeout_seconds_override; +} + static struct aws_mqtt5_operation_vtable s_publish_operation_vtable = { .aws_mqtt5_operation_completion_fn = s_aws_mqtt5_operation_publish_complete, .aws_mqtt5_operation_set_packet_id_fn = s_aws_mqtt5_operation_publish_set_packet_id, .aws_mqtt5_operation_get_packet_id_address_fn = s_aws_mqtt5_operation_publish_get_packet_id_address, .aws_mqtt5_operation_validate_vs_connection_settings_fn = s_aws_mqtt5_packet_publish_view_validate_vs_connection_settings, -}; + .aws_mqtt5_operation_get_ack_timeout_override_fn = s_aws_mqtt5_operation_publish_get_ack_timeout_override}; static void s_destroy_operation_publish(void *object) { if (object == NULL) { @@ -2114,6 +2081,7 @@ struct aws_mqtt5_operation_publish *aws_mqtt5_operation_publish_new( const struct aws_mqtt5_client *client, const struct aws_mqtt5_packet_publish_view *publish_options, const struct aws_mqtt5_publish_completion_options *completion_options) { + (void)client; AWS_PRECONDITION(allocator != NULL); AWS_PRECONDITION(publish_options != NULL); @@ -2130,12 +2098,6 @@ struct aws_mqtt5_operation_publish *aws_mqtt5_operation_publish_new( return NULL; } - if (client != NULL && client->config->extended_validation_and_flow_control_options != AWS_MQTT5_EVAFCO_NONE) { - if (aws_mqtt5_packet_publish_view_validate_vs_iot_core(publish_options)) { - return NULL; - } - } - struct aws_mqtt5_operation_publish *publish_op = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_operation_publish)); if (publish_op == NULL) { @@ -2146,6 +2108,7 @@ struct aws_mqtt5_operation_publish *aws_mqtt5_operation_publish_new( publish_op->base.vtable = &s_publish_operation_vtable; publish_op->base.packet_type = AWS_MQTT5_PT_PUBLISH; aws_ref_count_init(&publish_op->base.ref_count, publish_op, s_destroy_operation_publish); + aws_priority_queue_node_init(&publish_op->base.priority_queue_node); publish_op->base.impl = publish_op; if (aws_mqtt5_packet_publish_storage_init(&publish_op->options_storage, allocator, publish_options)) { @@ -2316,6 +2279,7 @@ struct aws_mqtt5_operation_puback *aws_mqtt5_operation_puback_new( puback_op->base.vtable = &s_empty_operation_vtable; puback_op->base.packet_type = AWS_MQTT5_PT_PUBACK; aws_ref_count_init(&puback_op->base.ref_count, puback_op, s_destroy_operation_puback); + aws_priority_queue_node_init(&puback_op->base.priority_queue_node); puback_op->base.impl = puback_op; if (aws_mqtt5_packet_puback_storage_init(&puback_op->options_storage, allocator, puback_options)) { @@ -2363,7 +2327,7 @@ int aws_mqtt5_packet_unsubscribe_view_validate(const struct aws_mqtt5_packet_uns for (size_t i = 0; i < unsubscribe_view->topic_filter_count; ++i) { const struct aws_byte_cursor *topic_filter = &unsubscribe_view->topic_filters[i]; - if (aws_mqtt5_validate_utf8_text(*topic_filter)) { + if (aws_mqtt_validate_utf8_text(*topic_filter)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_unsubscribe_view - topic filter not valid UTF-8: \"" PRInSTR "\"", @@ -2392,25 +2356,6 @@ int aws_mqtt5_packet_unsubscribe_view_validate(const struct aws_mqtt5_packet_uns return AWS_OP_SUCCESS; } -AWS_MQTT_API int aws_mqtt5_packet_unsubscribe_view_validate_vs_iot_core( - const struct aws_mqtt5_packet_unsubscribe_view *unsubscribe_view) { - - for (size_t i = 0; i < unsubscribe_view->topic_filter_count; ++i) { - const struct aws_byte_cursor *topic_filter = &unsubscribe_view->topic_filters[i]; - if (!aws_mqtt_is_valid_topic_filter_for_iot_core(*topic_filter)) { - AWS_LOGF_ERROR( - AWS_LS_MQTT5_GENERAL, - "id=%p: aws_mqtt5_packet_unsubscribe_view - topic filter not valid for AWS Iot Core limits: \"" PRInSTR - "\"", - (void *)unsubscribe_view, - AWS_BYTE_CURSOR_PRI(*topic_filter)); - return aws_raise_error(AWS_ERROR_MQTT5_UNSUBSCRIBE_OPTIONS_VALIDATION); - } - } - - return AWS_OP_SUCCESS; -} - void aws_mqtt5_packet_unsubscribe_view_log( const struct aws_mqtt5_packet_unsubscribe_view *unsubscribe_view, enum aws_log_level level) { @@ -2573,11 +2518,18 @@ static aws_mqtt5_packet_id_t *s_aws_mqtt5_operation_unsubscribe_get_packet_id_ad return &unsubscribe_op->options_storage.storage_view.packet_id; } +static uint32_t s_aws_mqtt5_operation_unsubscribe_get_ack_timeout_override( + const struct aws_mqtt5_operation *operation) { + struct aws_mqtt5_operation_unsubscribe *unsubscribe_op = operation->impl; + return unsubscribe_op->completion_options.ack_timeout_seconds_override; +} + static struct aws_mqtt5_operation_vtable s_unsubscribe_operation_vtable = { .aws_mqtt5_operation_completion_fn = s_aws_mqtt5_operation_unsubscribe_complete, .aws_mqtt5_operation_set_packet_id_fn = s_aws_mqtt5_operation_unsubscribe_set_packet_id, .aws_mqtt5_operation_get_packet_id_address_fn = s_aws_mqtt5_operation_unsubscribe_get_packet_id_address, .aws_mqtt5_operation_validate_vs_connection_settings_fn = NULL, + .aws_mqtt5_operation_get_ack_timeout_override_fn = s_aws_mqtt5_operation_unsubscribe_get_ack_timeout_override, }; static void s_destroy_operation_unsubscribe(void *object) { @@ -2597,6 +2549,7 @@ struct aws_mqtt5_operation_unsubscribe *aws_mqtt5_operation_unsubscribe_new( const struct aws_mqtt5_client *client, const struct aws_mqtt5_packet_unsubscribe_view *unsubscribe_options, const struct aws_mqtt5_unsubscribe_completion_options *completion_options) { + (void)client; AWS_PRECONDITION(allocator != NULL); AWS_PRECONDITION(unsubscribe_options != NULL); @@ -2613,12 +2566,6 @@ struct aws_mqtt5_operation_unsubscribe *aws_mqtt5_operation_unsubscribe_new( return NULL; } - if (client != NULL && client->config->extended_validation_and_flow_control_options != AWS_MQTT5_EVAFCO_NONE) { - if (aws_mqtt5_packet_unsubscribe_view_validate_vs_iot_core(unsubscribe_options)) { - return NULL; - } - } - struct aws_mqtt5_operation_unsubscribe *unsubscribe_op = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_operation_unsubscribe)); if (unsubscribe_op == NULL) { @@ -2629,6 +2576,7 @@ struct aws_mqtt5_operation_unsubscribe *aws_mqtt5_operation_unsubscribe_new( unsubscribe_op->base.vtable = &s_unsubscribe_operation_vtable; unsubscribe_op->base.packet_type = AWS_MQTT5_PT_UNSUBSCRIBE; aws_ref_count_init(&unsubscribe_op->base.ref_count, unsubscribe_op, s_destroy_operation_unsubscribe); + aws_priority_queue_node_init(&unsubscribe_op->base.priority_queue_node); unsubscribe_op->base.impl = unsubscribe_op; if (aws_mqtt5_packet_unsubscribe_storage_init(&unsubscribe_op->options_storage, allocator, unsubscribe_options)) { @@ -2658,7 +2606,7 @@ static int s_aws_mqtt5_validate_subscription( const struct aws_mqtt5_subscription_view *subscription, void *log_context) { - if (aws_mqtt5_validate_utf8_text(subscription->topic_filter)) { + if (aws_mqtt_validate_utf8_text(subscription->topic_filter)) { AWS_LOGF_ERROR( AWS_LS_MQTT5_GENERAL, "id=%p: aws_mqtt5_packet_subscribe_view - topic filter \"" PRInSTR "\" not valid UTF-8 in subscription", @@ -2774,37 +2722,6 @@ int aws_mqtt5_packet_subscribe_view_validate(const struct aws_mqtt5_packet_subsc return AWS_OP_SUCCESS; } -AWS_MQTT_API int aws_mqtt5_packet_subscribe_view_validate_vs_iot_core( - const struct aws_mqtt5_packet_subscribe_view *subscribe_view) { - - if (subscribe_view->subscription_count > AWS_IOT_CORE_MAXIMUM_SUSBCRIPTIONS_PER_SUBSCRIBE) { - AWS_LOGF_ERROR( - AWS_LS_MQTT5_GENERAL, - "id=%p: aws_mqtt5_packet_subscribe_view - number of subscriptions (%zu) exceeds default AWS IoT Core limit " - "(%d)", - (void *)subscribe_view, - subscribe_view->subscription_count, - (int)AWS_IOT_CORE_MAXIMUM_SUSBCRIPTIONS_PER_SUBSCRIBE); - return AWS_OP_ERR; - } - - for (size_t i = 0; i < subscribe_view->subscription_count; ++i) { - const struct aws_mqtt5_subscription_view *subscription = &subscribe_view->subscriptions[i]; - const struct aws_byte_cursor *topic_filter = &subscription->topic_filter; - if (!aws_mqtt_is_valid_topic_filter_for_iot_core(*topic_filter)) { - AWS_LOGF_ERROR( - AWS_LS_MQTT5_GENERAL, - "id=%p: aws_mqtt5_packet_subscribe_view - topic filter not valid for AWS Iot Core limits: \"" PRInSTR - "\"", - (void *)subscribe_view, - AWS_BYTE_CURSOR_PRI(*topic_filter)); - return aws_raise_error(AWS_ERROR_MQTT5_UNSUBSCRIBE_OPTIONS_VALIDATION); - } - } - - return AWS_OP_SUCCESS; -} - void aws_mqtt5_packet_subscribe_view_log( const struct aws_mqtt5_packet_subscribe_view *subscribe_view, enum aws_log_level level) { @@ -2992,11 +2909,17 @@ static aws_mqtt5_packet_id_t *s_aws_mqtt5_operation_subscribe_get_packet_id_addr return &subscribe_op->options_storage.storage_view.packet_id; } +static uint32_t s_aws_mqtt5_operation_subscribe_get_ack_timeout_override(const struct aws_mqtt5_operation *operation) { + struct aws_mqtt5_operation_subscribe *subscribe_op = operation->impl; + return subscribe_op->completion_options.ack_timeout_seconds_override; +} + static struct aws_mqtt5_operation_vtable s_subscribe_operation_vtable = { .aws_mqtt5_operation_completion_fn = s_aws_mqtt5_operation_subscribe_complete, .aws_mqtt5_operation_set_packet_id_fn = s_aws_mqtt5_operation_subscribe_set_packet_id, .aws_mqtt5_operation_get_packet_id_address_fn = s_aws_mqtt5_operation_subscribe_get_packet_id_address, .aws_mqtt5_operation_validate_vs_connection_settings_fn = NULL, + .aws_mqtt5_operation_get_ack_timeout_override_fn = s_aws_mqtt5_operation_subscribe_get_ack_timeout_override, }; static void s_destroy_operation_subscribe(void *object) { @@ -3016,6 +2939,7 @@ struct aws_mqtt5_operation_subscribe *aws_mqtt5_operation_subscribe_new( const struct aws_mqtt5_client *client, const struct aws_mqtt5_packet_subscribe_view *subscribe_options, const struct aws_mqtt5_subscribe_completion_options *completion_options) { + (void)client; AWS_PRECONDITION(allocator != NULL); AWS_PRECONDITION(subscribe_options != NULL); @@ -3032,12 +2956,6 @@ struct aws_mqtt5_operation_subscribe *aws_mqtt5_operation_subscribe_new( return NULL; } - if (client != NULL && client->config->extended_validation_and_flow_control_options != AWS_MQTT5_EVAFCO_NONE) { - if (aws_mqtt5_packet_subscribe_view_validate_vs_iot_core(subscribe_options)) { - return NULL; - } - } - struct aws_mqtt5_operation_subscribe *subscribe_op = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_operation_subscribe)); if (subscribe_op == NULL) { @@ -3048,6 +2966,7 @@ struct aws_mqtt5_operation_subscribe *aws_mqtt5_operation_subscribe_new( subscribe_op->base.vtable = &s_subscribe_operation_vtable; subscribe_op->base.packet_type = AWS_MQTT5_PT_SUBSCRIBE; aws_ref_count_init(&subscribe_op->base.ref_count, subscribe_op, s_destroy_operation_subscribe); + aws_priority_queue_node_init(&subscribe_op->base.priority_queue_node); subscribe_op->base.impl = subscribe_op; if (aws_mqtt5_packet_subscribe_storage_init(&subscribe_op->options_storage, allocator, subscribe_options)) { @@ -3383,6 +3302,7 @@ struct aws_mqtt5_operation_pingreq *aws_mqtt5_operation_pingreq_new(struct aws_a pingreq_op->base.vtable = &s_empty_operation_vtable; pingreq_op->base.packet_type = AWS_MQTT5_PT_PINGREQ; aws_ref_count_init(&pingreq_op->base.ref_count, pingreq_op, s_destroy_operation_pingreq); + aws_priority_queue_node_init(&pingreq_op->base.priority_queue_node); pingreq_op->base.impl = pingreq_op; return pingreq_op; @@ -3416,14 +3336,20 @@ int aws_mqtt5_client_options_validate(const struct aws_mqtt5_client_options *opt } } + if (aws_socket_validate_port_for_connect( + options->port, options->socket_options ? options->socket_options->domain : AWS_SOCKET_IPV4)) { + AWS_LOGF_ERROR(AWS_LS_MQTT5_GENERAL, "invalid port in mqtt5 client configuration"); + return aws_raise_error(AWS_ERROR_MQTT5_CLIENT_OPTIONS_VALIDATION); + } + if (options->http_proxy_options != NULL) { if (options->http_proxy_options->host.len == 0) { AWS_LOGF_ERROR(AWS_LS_MQTT5_GENERAL, "proxy host name not set in mqtt5 client configuration"); return aws_raise_error(AWS_ERROR_MQTT5_CLIENT_OPTIONS_VALIDATION); } - if (options->http_proxy_options->port == 0) { - AWS_LOGF_ERROR(AWS_LS_MQTT5_GENERAL, "proxy port not set in mqtt5 client configuration"); + if (aws_socket_validate_port_for_connect(options->http_proxy_options->port, AWS_SOCKET_IPV4)) { + AWS_LOGF_ERROR(AWS_LS_MQTT5_GENERAL, "invalid proxy port in mqtt5 client configuration"); return aws_raise_error(AWS_ERROR_MQTT5_CLIENT_OPTIONS_VALIDATION); } } @@ -3441,32 +3367,20 @@ int aws_mqtt5_client_options_validate(const struct aws_mqtt5_client_options *opt if (aws_mqtt5_packet_connect_view_validate(options->connect_options)) { AWS_LOGF_ERROR(AWS_LS_MQTT5_GENERAL, "invalid CONNECT options in mqtt5 client configuration"); + /* connect validation failure will have already raised the appropriate error */ return AWS_OP_ERR; } - /* The client will not behave properly if ping timeout is not significantly shorter than the keep alive interval */ - if (options->connect_options->keep_alive_interval_seconds > 0) { - uint64_t keep_alive_ms = aws_timestamp_convert( - options->connect_options->keep_alive_interval_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_MILLIS, NULL); - uint64_t one_second_ms = aws_timestamp_convert(1, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_MILLIS, NULL); - - uint64_t ping_timeout_ms = options->ping_timeout_ms; - if (ping_timeout_ms == 0) { - ping_timeout_ms = AWS_MQTT5_CLIENT_DEFAULT_PING_TIMEOUT_MS; - } - - if (ping_timeout_ms + one_second_ms > keep_alive_ms) { - AWS_LOGF_ERROR(AWS_LS_MQTT5_GENERAL, "keep alive interval is too small relative to ping timeout interval"); - return AWS_OP_ERR; + if (options->topic_aliasing_options != NULL) { + if (!aws_mqtt5_outbound_topic_alias_behavior_type_validate( + options->topic_aliasing_options->outbound_topic_alias_behavior)) { + AWS_LOGF_ERROR(AWS_LS_MQTT5_GENERAL, "invalid outbound topic alias behavior type value"); + return aws_raise_error(AWS_ERROR_MQTT5_CLIENT_OPTIONS_VALIDATION); } - } - if (options->extended_validation_and_flow_control_options != AWS_MQTT5_EVAFCO_NONE) { - if (options->connect_options->client_id.len > AWS_IOT_CORE_MAXIMUM_CLIENT_ID_LENGTH) { - AWS_LOGF_ERROR( - AWS_LS_MQTT5_GENERAL, - "AWS IoT Core limits client_id to be less than or equal to %d bytes in length", - (int)AWS_IOT_CORE_MAXIMUM_CLIENT_ID_LENGTH); + if (!aws_mqtt5_inbound_topic_alias_behavior_type_validate( + options->topic_aliasing_options->inbound_topic_alias_behavior)) { + AWS_LOGF_ERROR(AWS_LS_MQTT5_GENERAL, "invalid inbound topic alias behavior type value"); return aws_raise_error(AWS_ERROR_MQTT5_CLIENT_OPTIONS_VALIDATION); } } @@ -3607,7 +3521,7 @@ void aws_mqtt5_client_options_storage_log( log_handle, level, AWS_LS_MQTT5_GENERAL, - "id=%p: aws_mqtt5_client_options_storage port set to %" PRIu16, + "id=%p: aws_mqtt5_client_options_storage port set to %" PRIu32, (void *)options_storage, options_storage->port); @@ -3668,7 +3582,7 @@ void aws_mqtt5_client_options_storage_log( log_handle, level, AWS_LS_MQTT5_GENERAL, - "id=%p: aws_mqtt5_client_options_storage http proxy port set to %" PRIu16, + "id=%p: aws_mqtt5_client_options_storage http proxy port set to %" PRIu32, (void *)options_storage, options_storage->http_proxy_options.port); @@ -3794,7 +3708,7 @@ void aws_mqtt5_client_options_storage_log( "id=%p: aws_mqtt5_client_options_storage connect options:", (void *)options_storage); - aws_mqtt5_packet_connect_view_log(&options_storage->connect.storage_view, level); + aws_mqtt5_packet_connect_view_log(&options_storage->connect->storage_view, level); AWS_LOGUF( log_handle, @@ -3816,7 +3730,8 @@ void aws_mqtt5_client_options_storage_destroy(struct aws_mqtt5_client_options_st aws_tls_connection_options_clean_up(&options_storage->tls_options); aws_http_proxy_config_destroy(options_storage->http_proxy_config); - aws_mqtt5_packet_connect_storage_clean_up(&options_storage->connect); + aws_mqtt5_packet_connect_storage_clean_up(options_storage->connect); + aws_mem_release(options_storage->connect->allocator, options_storage->connect); aws_mem_release(options_storage->allocator, options_storage); } @@ -3944,7 +3859,8 @@ struct aws_mqtt5_client_options_storage *aws_mqtt5_client_options_storage_new( options_storage->topic_aliasing_options = *options->topic_aliasing_options; } - if (aws_mqtt5_packet_connect_storage_init(&options_storage->connect, allocator, options->connect_options)) { + options_storage->connect = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_packet_connect_storage)); + if (aws_mqtt5_packet_connect_storage_init(options_storage->connect, allocator, options->connect_options)) { goto error; } @@ -3956,6 +3872,15 @@ struct aws_mqtt5_client_options_storage *aws_mqtt5_client_options_storage_new( s_apply_zero_valued_defaults_to_client_options_storage(options_storage); + /* must do this after zero-valued defaults are applied so that max reconnect is accurate */ + if (options->host_resolution_override) { + options_storage->host_resolution_override = *options->host_resolution_override; + } else { + options_storage->host_resolution_override = aws_host_resolver_init_default_resolution_config(); + options_storage->host_resolution_override.resolve_frequency_ns = aws_timestamp_convert( + options_storage->max_reconnect_delay_ms, AWS_TIMESTAMP_MILLIS, AWS_TIMESTAMP_NANOS, NULL); + } + return options_storage; error: diff --git a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_to_mqtt3_adapter.c b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_to_mqtt3_adapter.c new file mode 100644 index 00000000000..bd90a6581fa --- /dev/null +++ b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_to_mqtt3_adapter.c @@ -0,0 +1,3039 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include <aws/mqtt/mqtt.h> + +#include <aws/common/clock.h> +#include <aws/common/rw_lock.h> + +#include <aws/mqtt/private/client_impl_shared.h> +#include <aws/mqtt/private/mqtt_subscription_set.h> +#include <aws/mqtt/private/v5/mqtt5_client_impl.h> +#include <aws/mqtt/private/v5/mqtt5_to_mqtt3_adapter_impl.h> +#include <aws/mqtt/v5/mqtt5_listener.h> + +/* + * A best-effort-but-not-100%-accurate translation from mqtt5 error codes to mqtt311 error codes. + */ +static int s_translate_mqtt5_error_code_to_mqtt311(int error_code) { + switch (error_code) { + case AWS_ERROR_MQTT5_ENCODE_FAILURE: + case AWS_ERROR_MQTT5_DECODE_PROTOCOL_ERROR: + return AWS_ERROR_MQTT_PROTOCOL_ERROR; + + case AWS_ERROR_MQTT5_CONNACK_CONNECTION_REFUSED: + return AWS_ERROR_MQTT_PROTOCOL_ERROR; /* a decidedly strange choice by the 311 implementation */ + + case AWS_ERROR_MQTT5_CONNACK_TIMEOUT: + case AWS_ERROR_MQTT5_PING_RESPONSE_TIMEOUT: + return AWS_ERROR_MQTT_TIMEOUT; + + case AWS_ERROR_MQTT5_USER_REQUESTED_STOP: + case AWS_ERROR_MQTT5_CLIENT_TERMINATED: + return AWS_IO_SOCKET_CLOSED; + + case AWS_ERROR_MQTT5_DISCONNECT_RECEIVED: + return AWS_ERROR_MQTT_UNEXPECTED_HANGUP; + + case AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY: + return AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION; + + case AWS_ERROR_MQTT5_ENCODE_SIZE_UNSUPPORTED_PACKET_TYPE: + return AWS_ERROR_MQTT_INVALID_PACKET_TYPE; + + case AWS_ERROR_MQTT5_OPERATION_PROCESSING_FAILURE: + return AWS_ERROR_MQTT_PROTOCOL_ERROR; + + case AWS_ERROR_MQTT5_INVALID_UTF8_STRING: + return AWS_ERROR_MQTT_INVALID_TOPIC; + + default: + return error_code; + } +} + +struct aws_mqtt_adapter_final_destroy_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection *connection; +}; + +static void s_mqtt_adapter_final_destroy_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + (void)status; + + struct aws_mqtt_adapter_final_destroy_task *destroy_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = destroy_task->connection->impl; + + AWS_LOGF_DEBUG(AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: Final destruction of mqtt3-to-5 adapter", (void *)adapter); + + aws_mqtt_client_on_connection_termination_fn *termination_handler = NULL; + void *termination_handler_user_data = NULL; + if (adapter->on_termination != NULL) { + termination_handler = adapter->on_termination; + termination_handler_user_data = adapter->on_termination_user_data; + } + + if (adapter->client->config->websocket_handshake_transform_user_data == adapter) { + /* + * If the mqtt5 client is pointing to us for websocket transform, then erase that. The callback + * is invoked from our pinned event loop so this is safe. + * + * TODO: It is possible that multiple adapters may have sequentially side-affected the websocket handshake. + * For now, in that case, subsequent connection attempts will probably not succeed. + */ + adapter->client->config->websocket_handshake_transform = NULL; + adapter->client->config->websocket_handshake_transform_user_data = NULL; + } + + aws_mqtt_subscription_set_destroy(adapter->subscriptions); + aws_mqtt5_to_mqtt3_adapter_operation_table_clean_up(&adapter->operational_state); + + adapter->client = aws_mqtt5_client_release(adapter->client); + + aws_mem_release(adapter->allocator, adapter); + + aws_mem_release(destroy_task->allocator, destroy_task); + + /* trigger the termination callback */ + if (termination_handler) { + termination_handler(termination_handler_user_data); + } +} + +static struct aws_mqtt_adapter_final_destroy_task *s_aws_mqtt_adapter_final_destroy_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter) { + + struct aws_mqtt_adapter_final_destroy_task *destroy_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_adapter_final_destroy_task)); + + aws_task_init( + &destroy_task->task, s_mqtt_adapter_final_destroy_task_fn, (void *)destroy_task, "MqttAdapterFinalDestroy"); + destroy_task->allocator = adapter->allocator; + destroy_task->connection = &adapter->base; /* Do not acquire, we're at zero external and internal ref counts */ + + return destroy_task; +} + +static void s_aws_mqtt_adapter_final_destroy(struct aws_mqtt_client_connection_5_impl *adapter) { + + struct aws_mqtt_adapter_final_destroy_task *task = + s_aws_mqtt_adapter_final_destroy_task_new(adapter->allocator, adapter); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create adapter final destroy task, last_error: %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); +} + +struct aws_mqtt_adapter_disconnect_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + aws_mqtt_client_on_disconnect_fn *on_disconnect; + void *on_disconnect_user_data; +}; + +static void s_adapter_disconnect_task_fn(struct aws_task *task, void *arg, enum aws_task_status status); + +static struct aws_mqtt_adapter_disconnect_task *s_aws_mqtt_adapter_disconnect_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + aws_mqtt_client_on_disconnect_fn *on_disconnect, + void *on_disconnect_user_data) { + + struct aws_mqtt_adapter_disconnect_task *disconnect_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_adapter_disconnect_task)); + + aws_task_init( + &disconnect_task->task, s_adapter_disconnect_task_fn, (void *)disconnect_task, "AdapterDisconnectTask"); + disconnect_task->allocator = adapter->allocator; + disconnect_task->adapter = + (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + + disconnect_task->on_disconnect = on_disconnect; + disconnect_task->on_disconnect_user_data = on_disconnect_user_data; + + return disconnect_task; +} + +static int s_aws_mqtt_client_connection_5_disconnect( + void *impl, + aws_mqtt_client_on_disconnect_fn *on_disconnect, + void *on_disconnect_user_data) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_adapter_disconnect_task *task = + s_aws_mqtt_adapter_disconnect_task_new(adapter->allocator, adapter, on_disconnect, on_disconnect_user_data); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create adapter disconnect task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_adapter_connect_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + struct aws_byte_buf host_name; + uint32_t port; + struct aws_socket_options socket_options; + struct aws_tls_connection_options *tls_options_ptr; + struct aws_tls_connection_options tls_options; + + struct aws_byte_buf client_id; + uint16_t keep_alive_time_secs; + uint32_t ping_timeout_ms; + uint32_t protocol_operation_timeout_ms; + aws_mqtt_client_on_connection_complete_fn *on_connection_complete; + void *on_connection_complete_user_data; + bool clean_session; +}; + +static void s_aws_mqtt_adapter_connect_task_destroy(struct aws_mqtt_adapter_connect_task *task) { + if (task == NULL) { + return; + } + + aws_byte_buf_clean_up(&task->host_name); + aws_byte_buf_clean_up(&task->client_id); + + if (task->tls_options_ptr) { + aws_tls_connection_options_clean_up(task->tls_options_ptr); + } + + aws_mem_release(task->allocator, task); +} + +static void s_adapter_connect_task_fn(struct aws_task *task, void *arg, enum aws_task_status status); + +static struct aws_mqtt_adapter_connect_task *s_aws_mqtt_adapter_connect_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + const struct aws_mqtt_connection_options *connection_options) { + + struct aws_mqtt_adapter_connect_task *connect_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_adapter_connect_task)); + + aws_task_init(&connect_task->task, s_adapter_connect_task_fn, (void *)connect_task, "AdapterConnectTask"); + connect_task->allocator = adapter->allocator; + + aws_byte_buf_init_copy_from_cursor(&connect_task->host_name, allocator, connection_options->host_name); + connect_task->port = connection_options->port; + connect_task->socket_options = *connection_options->socket_options; + if (connection_options->tls_options) { + if (aws_tls_connection_options_copy(&connect_task->tls_options, connection_options->tls_options)) { + goto error; + } + connect_task->tls_options_ptr = &connect_task->tls_options; + + /* Cheat and set the tls_options host_name to our copy if they're the same */ + if (!connect_task->tls_options.server_name) { + struct aws_byte_cursor host_name_cur = aws_byte_cursor_from_buf(&connect_task->host_name); + + if (aws_tls_connection_options_set_server_name( + &connect_task->tls_options, connect_task->allocator, &host_name_cur)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - Failed to set TLS Connection Options server name", + (void *)adapter); + goto error; + } + } + } + connect_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + aws_byte_buf_init_copy_from_cursor(&connect_task->client_id, allocator, connection_options->client_id); + + connect_task->keep_alive_time_secs = connection_options->keep_alive_time_secs; + connect_task->ping_timeout_ms = connection_options->ping_timeout_ms; + connect_task->protocol_operation_timeout_ms = connection_options->protocol_operation_timeout_ms; + connect_task->on_connection_complete = connection_options->on_connection_complete; + connect_task->on_connection_complete_user_data = connection_options->user_data; + connect_task->clean_session = connection_options->clean_session; + + return connect_task; + +error: + s_aws_mqtt_adapter_connect_task_destroy(connect_task); + + return NULL; +} + +static int s_validate_adapter_connection_options( + const struct aws_mqtt_connection_options *connection_options, + struct aws_mqtt_client_connection_5_impl *adapter) { + if (connection_options == NULL) { + return aws_raise_error(AWS_ERROR_MQTT5_CLIENT_OPTIONS_VALIDATION); + } + + if (connection_options->host_name.len == 0) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - host name not set in MQTT client configuration", + (void *)adapter); + return aws_raise_error(AWS_ERROR_MQTT5_CLIENT_OPTIONS_VALIDATION); + } + + /* forbid no-timeout until someone convinces me otherwise */ + if (connection_options->socket_options != NULL) { + if (connection_options->socket_options->type == AWS_SOCKET_DGRAM || + connection_options->socket_options->connect_timeout_ms == 0) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - invalid socket options in MQTT client configuration", + (void *)adapter); + return aws_raise_error(AWS_ERROR_MQTT5_CLIENT_OPTIONS_VALIDATION); + } + } + + return AWS_OP_SUCCESS; +} + +static int s_aws_mqtt_client_connection_5_connect( + void *impl, + const struct aws_mqtt_connection_options *connection_options) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + /* The client will not behave properly if ping timeout is not significantly shorter than the keep alive interval */ + if (s_validate_adapter_connection_options(connection_options, adapter)) { + return AWS_OP_ERR; + } + + struct aws_mqtt_adapter_connect_task *task = + s_aws_mqtt_adapter_connect_task_new(adapter->allocator, adapter, connection_options); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - failed to create adapter connect task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +static void s_aws_mqtt5_to_mqtt3_adapter_lifecycle_handler(const struct aws_mqtt5_client_lifecycle_event *event) { + struct aws_mqtt_client_connection_5_impl *adapter = event->user_data; + + switch (event->event_type) { + + case AWS_MQTT5_CLET_CONNECTION_SUCCESS: + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - received on connection success event from mqtt5 client, adapter in state " + "(%d)", + (void *)adapter, + (int)adapter->adapter_state); + if (adapter->adapter_state != AWS_MQTT_AS_STAY_DISCONNECTED) { + if (adapter->on_connection_success != NULL) { + (*adapter->on_connection_success)( + &adapter->base, 0, event->settings->rejoined_session, adapter->on_connection_success_user_data); + } + + if (adapter->adapter_state == AWS_MQTT_AS_FIRST_CONNECT) { + /* + * If the 311 view is that this is an initial connection attempt, then invoke the completion + * callback and move to the stay-connected state. + */ + if (adapter->on_connection_complete != NULL) { + (*adapter->on_connection_complete)( + &adapter->base, + event->error_code, + 0, + event->settings->rejoined_session, + adapter->on_connection_complete_user_data); + + adapter->on_connection_complete = NULL; + adapter->on_connection_complete_user_data = NULL; + } + adapter->adapter_state = AWS_MQTT_AS_STAY_CONNECTED; + } else if (adapter->adapter_state == AWS_MQTT_AS_STAY_CONNECTED) { + /* + * If the 311 view is that we're in the stay-connected state (ie we've successfully done or + * simulated an initial connection), then invoke the connection resumption callback. + */ + if (adapter->on_resumed != NULL) { + (*adapter->on_resumed)( + &adapter->base, 0, event->settings->rejoined_session, adapter->on_resumed_user_data); + } + } + } + break; + + case AWS_MQTT5_CLET_CONNECTION_FAILURE: + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - received on connection failure event from mqtt5 client, adapter in state " + "(%d)", + (void *)adapter, + (int)adapter->adapter_state); + + /* + * The MQTT311 interface only cares about connection failures when it's the initial connection attempt + * after a call to connect(). Since an adapter connect() can sever an existing connection (with an + * error code of AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT) we only react to connection failures + * if + * (1) the error code is not AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT and + * (2) we're in the FIRST_CONNECT state + * + * Only if both of these are true should we invoke the connection completion callback with a failure and + * put the adapter into the "disconnected" state, simulating the way the 311 client stops after an + * initial connection failure. + */ + if (event->error_code != AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT) { + if (adapter->adapter_state != AWS_MQTT_AS_STAY_DISCONNECTED) { + int mqtt311_error_code = s_translate_mqtt5_error_code_to_mqtt311(event->error_code); + + if (adapter->on_connection_failure != NULL) { + (*adapter->on_connection_failure)( + &adapter->base, mqtt311_error_code, adapter->on_connection_failure_user_data); + } + + if (adapter->adapter_state == AWS_MQTT_AS_FIRST_CONNECT) { + if (adapter->on_connection_complete != NULL) { + (*adapter->on_connection_complete)( + &adapter->base, + mqtt311_error_code, + 0, + false, + adapter->on_connection_complete_user_data); + + adapter->on_connection_complete = NULL; + adapter->on_connection_complete_user_data = NULL; + } + + adapter->adapter_state = AWS_MQTT_AS_STAY_DISCONNECTED; + } + } + } + + break; + + case AWS_MQTT5_CLET_DISCONNECTION: + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - received on disconnection event from mqtt5 client, adapter in state (%d), " + "error code (%d)", + (void *)adapter, + (int)adapter->adapter_state, + event->error_code); + /* + * If the 311 view is that we're in the stay-connected state (ie we've successfully done or simulated + * an initial connection), then invoke the connection interrupted callback. + */ + if (adapter->on_interrupted != NULL && adapter->adapter_state == AWS_MQTT_AS_STAY_CONNECTED && + event->error_code != AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT) { + + (*adapter->on_interrupted)( + &adapter->base, + s_translate_mqtt5_error_code_to_mqtt311(event->error_code), + adapter->on_interrupted_user_data); + } + break; + + case AWS_MQTT5_CLET_STOPPED: + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - received on stopped event from mqtt5 client, adapter in state (%d)", + (void *)adapter, + (int)adapter->adapter_state); + + /* If an MQTT311-view user is waiting on a disconnect callback, invoke it */ + if (adapter->on_disconnect) { + (*adapter->on_disconnect)(&adapter->base, adapter->on_disconnect_user_data); + + adapter->on_disconnect = NULL; + adapter->on_disconnect_user_data = NULL; + } + + if (adapter->on_closed) { + (*adapter->on_closed)(&adapter->base, NULL, adapter->on_closed_user_data); + } + + /* + * Judgement call: If the mqtt5 client is stopped behind our back, it seems better to transition to the + * disconnected state (which only requires a connect() to restart) then stay in the STAY_CONNECTED state + * which currently requires a disconnect() and then a connect() to restore connectivity. + * + * ToDo: what if we disabled mqtt5 client start/stop somehow while the adapter is attached, preventing + * the potential to backstab each other? Unfortunately neither start() nor stop() have an error reporting + * mechanism. + */ + adapter->adapter_state = AWS_MQTT_AS_STAY_DISCONNECTED; + break; + + default: + break; + } +} + +static void s_aws_mqtt5_to_mqtt3_adapter_disconnect_handler( + struct aws_mqtt_client_connection_5_impl *adapter, + struct aws_mqtt_adapter_disconnect_task *disconnect_task) { + + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - performing disconnect safe callback, adapter in state (%d)", + (void *)adapter, + (int)adapter->adapter_state); + + /* + * If we're already disconnected (from the 311 perspective only), then invoke the callback and return + */ + if (adapter->adapter_state == AWS_MQTT_AS_STAY_DISCONNECTED) { + if (disconnect_task->on_disconnect) { + (*disconnect_task->on_disconnect)(&adapter->base, disconnect_task->on_disconnect_user_data); + } + + return; + } + + /* + * If we had a pending first connect, then notify failure + */ + if (adapter->adapter_state == AWS_MQTT_AS_FIRST_CONNECT) { + if (adapter->on_connection_complete != NULL) { + (*adapter->on_connection_complete)( + &adapter->base, + AWS_ERROR_MQTT_CONNECTION_SHUTDOWN, + 0, + false, + adapter->on_connection_complete_user_data); + + adapter->on_connection_complete = NULL; + adapter->on_connection_complete_user_data = NULL; + } + } + + adapter->adapter_state = AWS_MQTT_AS_STAY_DISCONNECTED; + + bool invoke_callbacks = true; + if (adapter->client->desired_state != AWS_MCS_STOPPED) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - disconnect forwarding stop request to mqtt5 client", + (void *)adapter); + + aws_mqtt5_client_change_desired_state(adapter->client, AWS_MCS_STOPPED, NULL); + + adapter->on_disconnect = disconnect_task->on_disconnect; + adapter->on_disconnect_user_data = disconnect_task->on_disconnect_user_data; + invoke_callbacks = false; + } + + if (invoke_callbacks) { + if (disconnect_task->on_disconnect != NULL) { + (*disconnect_task->on_disconnect)(&adapter->base, disconnect_task->on_disconnect_user_data); + } + + if (adapter->on_closed) { + (*adapter->on_closed)(&adapter->base, NULL, adapter->on_closed_user_data); + } + } +} + +static void s_adapter_disconnect_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_adapter_disconnect_task *disconnect_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = disconnect_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + s_aws_mqtt5_to_mqtt3_adapter_disconnect_handler(adapter, disconnect_task); + +done: + + aws_ref_count_release(&adapter->internal_refs); + + aws_mem_release(disconnect_task->allocator, disconnect_task); +} + +static void s_aws_mqtt5_to_mqtt3_adapter_update_config_on_connect( + struct aws_mqtt_client_connection_5_impl *adapter, + struct aws_mqtt_adapter_connect_task *connect_task) { + struct aws_mqtt5_client_options_storage *config = adapter->client->config; + + aws_string_destroy(config->host_name); + config->host_name = aws_string_new_from_buf(adapter->allocator, &connect_task->host_name); + config->port = connect_task->port; + config->socket_options = connect_task->socket_options; + + if (config->tls_options_ptr) { + aws_tls_connection_options_clean_up(&config->tls_options); + config->tls_options_ptr = NULL; + } + + if (connect_task->tls_options_ptr) { + aws_tls_connection_options_copy(&config->tls_options, connect_task->tls_options_ptr); + config->tls_options_ptr = &config->tls_options; + } + + aws_byte_buf_clean_up(&adapter->client->negotiated_settings.client_id_storage); + aws_byte_buf_init_copy_from_cursor( + &adapter->client->negotiated_settings.client_id_storage, + adapter->allocator, + aws_byte_cursor_from_buf(&connect_task->client_id)); + + config->connect->storage_view.keep_alive_interval_seconds = connect_task->keep_alive_time_secs; + config->ping_timeout_ms = connect_task->ping_timeout_ms; + + /* Override timeout, rounding up as necessary */ + config->ack_timeout_seconds = (uint32_t)aws_timestamp_convert( + connect_task->protocol_operation_timeout_ms + AWS_TIMESTAMP_MILLIS - 1, + AWS_TIMESTAMP_MILLIS, + AWS_TIMESTAMP_SECS, + NULL); + + if (connect_task->clean_session) { + config->session_behavior = AWS_MQTT5_CSBT_CLEAN; + config->connect->storage_view.session_expiry_interval_seconds = NULL; + } else { + config->session_behavior = AWS_MQTT5_CSBT_REJOIN_ALWAYS; + /* This is a judgement call to translate session expiry to the maximum possible allowed by AWS IoT Core */ + config->connect->session_expiry_interval_seconds = 7 * 24 * 60 * 60; + config->connect->storage_view.session_expiry_interval_seconds = + &config->connect->session_expiry_interval_seconds; + } +} + +static void s_aws_mqtt5_to_mqtt3_adapter_connect_handler( + struct aws_mqtt_client_connection_5_impl *adapter, + struct aws_mqtt_adapter_connect_task *connect_task) { + + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - performing connect safe callback, adapter in state (%d)", + (void *)adapter, + (int)adapter->adapter_state); + + if (adapter->adapter_state != AWS_MQTT_AS_STAY_DISCONNECTED) { + if (connect_task->on_connection_complete) { + (*connect_task->on_connection_complete)( + &adapter->base, + AWS_ERROR_MQTT_ALREADY_CONNECTED, + 0, + false, + connect_task->on_connection_complete_user_data); + } + + return; + } + + if (adapter->on_disconnect) { + (*adapter->on_disconnect)(&adapter->base, adapter->on_disconnect_user_data); + + adapter->on_disconnect = NULL; + adapter->on_disconnect_user_data = NULL; + } + + adapter->adapter_state = AWS_MQTT_AS_FIRST_CONNECT; + + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - resetting mqtt5 client connection and requesting start", + (void *)adapter); + + /* Update mqtt5 config */ + s_aws_mqtt5_to_mqtt3_adapter_update_config_on_connect(adapter, connect_task); + + aws_mqtt5_client_reset_connection(adapter->client); + + aws_mqtt5_client_change_desired_state(adapter->client, AWS_MCS_CONNECTED, NULL); + + adapter->on_connection_complete = connect_task->on_connection_complete; + adapter->on_connection_complete_user_data = connect_task->on_connection_complete_user_data; +} + +static void s_adapter_connect_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_adapter_connect_task *connect_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = connect_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + s_aws_mqtt5_to_mqtt3_adapter_connect_handler(adapter, connect_task); + +done: + + aws_ref_count_release(&adapter->internal_refs); + + s_aws_mqtt_adapter_connect_task_destroy(connect_task); +} + +static bool s_aws_mqtt5_listener_publish_received_adapter( + const struct aws_mqtt5_packet_publish_view *publish, + void *user_data) { + + struct aws_mqtt_client_connection_5_impl *adapter = user_data; + struct aws_mqtt_client_connection *connection = &adapter->base; + + struct aws_mqtt_subscription_set_publish_received_options incoming_publish_options = { + .connection = connection, + .topic = publish->topic, + .qos = (enum aws_mqtt_qos)publish->qos, + .retain = publish->retain, + .dup = publish->duplicate, + .payload = publish->payload, + }; + + aws_mqtt_subscription_set_on_publish_received(adapter->subscriptions, &incoming_publish_options); + + if (adapter->on_any_publish) { + (*adapter->on_any_publish)( + connection, + &publish->topic, + &publish->payload, + publish->duplicate, + (enum aws_mqtt_qos)publish->qos, + publish->retain, + adapter->on_any_publish_user_data); + } + + return false; +} + +struct aws_mqtt_set_interruption_handlers_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + aws_mqtt_client_on_connection_interrupted_fn *on_interrupted; + void *on_interrupted_user_data; + + aws_mqtt_client_on_connection_resumed_fn *on_resumed; + void *on_resumed_user_data; +}; + +static void s_set_interruption_handlers_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_interruption_handlers_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + adapter->on_interrupted = set_task->on_interrupted; + adapter->on_interrupted_user_data = set_task->on_interrupted_user_data; + adapter->on_resumed = set_task->on_resumed; + adapter->on_resumed_user_data = set_task->on_resumed_user_data; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + aws_mem_release(set_task->allocator, set_task); +} + +static struct aws_mqtt_set_interruption_handlers_task *s_aws_mqtt_set_interruption_handlers_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + aws_mqtt_client_on_connection_interrupted_fn *on_interrupted, + void *on_interrupted_user_data, + aws_mqtt_client_on_connection_resumed_fn *on_resumed, + void *on_resumed_user_data) { + + struct aws_mqtt_set_interruption_handlers_task *set_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_interruption_handlers_task)); + + aws_task_init( + &set_task->task, s_set_interruption_handlers_task_fn, (void *)set_task, "SetInterruptionHandlersTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + set_task->on_interrupted = on_interrupted; + set_task->on_interrupted_user_data = on_interrupted_user_data; + set_task->on_resumed = on_resumed; + set_task->on_resumed_user_data = on_resumed_user_data; + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_interruption_handlers( + void *impl, + aws_mqtt_client_on_connection_interrupted_fn *on_interrupted, + void *on_interrupted_user_data, + aws_mqtt_client_on_connection_resumed_fn *on_resumed, + void *on_resumed_user_data) { + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_interruption_handlers_task *task = s_aws_mqtt_set_interruption_handlers_task_new( + adapter->allocator, adapter, on_interrupted, on_interrupted_user_data, on_resumed, on_resumed_user_data); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set interruption handlers task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_connection_result_handlers_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + aws_mqtt_client_on_connection_success_fn *on_connection_success; + void *on_connection_success_user_data; + + aws_mqtt_client_on_connection_failure_fn *on_connection_failure; + void *on_connection_failure_user_data; +}; + +static void s_set_connection_result_handlers_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_connection_result_handlers_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + adapter->on_connection_success = set_task->on_connection_success; + adapter->on_connection_success_user_data = set_task->on_connection_success_user_data; + adapter->on_connection_failure = set_task->on_connection_failure; + adapter->on_connection_failure_user_data = set_task->on_connection_failure_user_data; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + aws_mem_release(set_task->allocator, set_task); +} + +static struct aws_mqtt_set_connection_result_handlers_task *s_aws_mqtt_set_connection_result_handlers_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + aws_mqtt_client_on_connection_success_fn *on_connection_success, + void *on_connection_success_user_data, + aws_mqtt_client_on_connection_failure_fn *on_connection_failure, + void *on_connection_failure_user_data) { + + struct aws_mqtt_set_connection_result_handlers_task *set_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_connection_result_handlers_task)); + + aws_task_init( + &set_task->task, s_set_connection_result_handlers_task_fn, (void *)set_task, "SetConnectionResultHandlersTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + set_task->on_connection_success = on_connection_success; + set_task->on_connection_success_user_data = on_connection_success_user_data; + set_task->on_connection_failure = on_connection_failure; + set_task->on_connection_failure_user_data = on_connection_failure_user_data; + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_connection_result_handlers( + void *impl, + aws_mqtt_client_on_connection_success_fn *on_connection_success, + void *on_connection_success_user_data, + aws_mqtt_client_on_connection_failure_fn *on_connection_failure, + void *on_connection_failure_user_data) { + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_connection_result_handlers_task *task = s_aws_mqtt_set_connection_result_handlers_task_new( + adapter->allocator, + adapter, + on_connection_success, + on_connection_success_user_data, + on_connection_failure, + on_connection_failure_user_data); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set connection result handlers task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_on_closed_handler_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + aws_mqtt_client_on_connection_closed_fn *on_closed; + void *on_closed_user_data; +}; + +static void s_set_on_closed_handler_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_on_closed_handler_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + adapter->on_closed = set_task->on_closed; + adapter->on_closed_user_data = set_task->on_closed_user_data; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + aws_mem_release(set_task->allocator, set_task); +} + +static struct aws_mqtt_set_on_closed_handler_task *s_aws_mqtt_set_on_closed_handler_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + aws_mqtt_client_on_connection_closed_fn *on_closed, + void *on_closed_user_data) { + + struct aws_mqtt_set_on_closed_handler_task *set_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_on_closed_handler_task)); + + aws_task_init(&set_task->task, s_set_on_closed_handler_task_fn, (void *)set_task, "SetOnClosedHandlerTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + set_task->on_closed = on_closed; + set_task->on_closed_user_data = on_closed_user_data; + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_on_closed_handler( + void *impl, + aws_mqtt_client_on_connection_closed_fn *on_closed, + void *on_closed_user_data) { + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_on_closed_handler_task *task = + s_aws_mqtt_set_on_closed_handler_task_new(adapter->allocator, adapter, on_closed, on_closed_user_data); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set on closed handler task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_on_termination_handlers_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + aws_mqtt_client_on_connection_termination_fn *on_termination_callback; + void *on_termination_ud; +}; + +static void s_set_on_termination_handlers_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + struct aws_mqtt_set_on_termination_handlers_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + adapter->on_termination = set_task->on_termination_callback; + adapter->on_termination_user_data = set_task->on_termination_ud; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + aws_mem_release(set_task->allocator, set_task); +} + +static struct aws_mqtt_set_on_termination_handlers_task *s_aws_mqtt_set_on_termination_handler_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + aws_mqtt_client_on_connection_termination_fn *on_termination, + void *on_termination_user_data) { + + struct aws_mqtt_set_on_termination_handlers_task *set_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_on_termination_handlers_task)); + + aws_task_init(&set_task->task, s_set_on_termination_handlers_task_fn, (void *)set_task, "SetOnClosedHandlerTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + set_task->on_termination_callback = on_termination; + set_task->on_termination_ud = on_termination_user_data; + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_termination_handler( + void *impl, + aws_mqtt_client_on_connection_termination_fn *on_termination, + void *on_termination_ud) { + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_on_termination_handlers_task *task = + s_aws_mqtt_set_on_termination_handler_task_new(adapter->allocator, adapter, on_termination, on_termination_ud); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set on closed handler task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_on_any_publish_handler_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + aws_mqtt_client_publish_received_fn *on_any_publish; + void *on_any_publish_user_data; +}; + +static void s_set_on_any_publish_handler_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_on_any_publish_handler_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + adapter->on_any_publish = set_task->on_any_publish; + adapter->on_any_publish_user_data = set_task->on_any_publish_user_data; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + aws_mem_release(set_task->allocator, set_task); +} + +static struct aws_mqtt_set_on_any_publish_handler_task *s_aws_mqtt_set_on_any_publish_handler_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + aws_mqtt_client_publish_received_fn *on_any_publish, + void *on_any_publish_user_data) { + + struct aws_mqtt_set_on_any_publish_handler_task *set_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_on_any_publish_handler_task)); + + aws_task_init( + &set_task->task, s_set_on_any_publish_handler_task_fn, (void *)set_task, "SetOnAnyPublishHandlerTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + set_task->on_any_publish = on_any_publish; + set_task->on_any_publish_user_data = on_any_publish_user_data; + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_on_any_publish_handler( + void *impl, + aws_mqtt_client_publish_received_fn *on_any_publish, + void *on_any_publish_user_data) { + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_on_any_publish_handler_task *task = s_aws_mqtt_set_on_any_publish_handler_task_new( + adapter->allocator, adapter, on_any_publish, on_any_publish_user_data); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set on any publish task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_reconnect_timeout_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + uint64_t min_timeout; + uint64_t max_timeout; +}; + +static void s_set_reconnect_timeout_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_reconnect_timeout_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + /* we're in the mqtt5 client's event loop; it's safe to access internal state */ + adapter->client->config->min_reconnect_delay_ms = set_task->min_timeout; + adapter->client->config->max_reconnect_delay_ms = set_task->max_timeout; + adapter->client->current_reconnect_delay_ms = set_task->min_timeout; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + aws_mem_release(set_task->allocator, set_task); +} + +static struct aws_mqtt_set_reconnect_timeout_task *s_aws_mqtt_set_reconnect_timeout_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + uint64_t min_timeout, + uint64_t max_timeout) { + + struct aws_mqtt_set_reconnect_timeout_task *set_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_reconnect_timeout_task)); + + aws_task_init(&set_task->task, s_set_reconnect_timeout_task_fn, (void *)set_task, "SetReconnectTimeoutTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + set_task->min_timeout = aws_min_u64(min_timeout, max_timeout); + set_task->max_timeout = aws_max_u64(min_timeout, max_timeout); + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_reconnect_timeout( + void *impl, + uint64_t min_timeout, + uint64_t max_timeout) { + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_reconnect_timeout_task *task = + s_aws_mqtt_set_reconnect_timeout_task_new(adapter->allocator, adapter, min_timeout, max_timeout); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set reconnect timeout task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_http_proxy_options_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + struct aws_http_proxy_config *proxy_config; +}; + +static void s_set_http_proxy_options_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_http_proxy_options_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + /* we're in the mqtt5 client's event loop; it's safe to access internal state */ + aws_http_proxy_config_destroy(adapter->client->config->http_proxy_config); + + /* move the proxy config from the set task to the client's config */ + adapter->client->config->http_proxy_config = set_task->proxy_config; + if (adapter->client->config->http_proxy_config != NULL) { + aws_http_proxy_options_init_from_config( + &adapter->client->config->http_proxy_options, adapter->client->config->http_proxy_config); + } + + /* don't clean up the proxy config if it was successfully assigned to the mqtt5 client */ + set_task->proxy_config = NULL; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + /* If the task was canceled we need to clean this up because it didn't get assigned to the mqtt5 client */ + aws_http_proxy_config_destroy(set_task->proxy_config); + + aws_mem_release(set_task->allocator, set_task); +} + +static struct aws_mqtt_set_http_proxy_options_task *s_aws_mqtt_set_http_proxy_options_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + struct aws_http_proxy_options *proxy_options) { + + struct aws_http_proxy_config *proxy_config = + aws_http_proxy_config_new_tunneling_from_proxy_options(allocator, proxy_options); + if (proxy_config == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_mqtt_set_http_proxy_options_task *set_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_http_proxy_options_task)); + + aws_task_init(&set_task->task, s_set_http_proxy_options_task_fn, (void *)set_task, "SetHttpProxyOptionsTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + set_task->proxy_config = proxy_config; + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_http_proxy_options( + void *impl, + struct aws_http_proxy_options *proxy_options) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_http_proxy_options_task *task = + s_aws_mqtt_set_http_proxy_options_task_new(adapter->allocator, adapter, proxy_options); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set http proxy options task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_use_websockets_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + aws_mqtt_transform_websocket_handshake_fn *transformer; + void *transformer_user_data; +}; + +static void s_aws_mqtt5_adapter_websocket_handshake_completion_fn( + struct aws_http_message *request, + int error_code, + void *complete_ctx) { + + struct aws_mqtt_client_connection_5_impl *adapter = complete_ctx; + + (*adapter->mqtt5_websocket_handshake_completion_function)( + request, + s_translate_mqtt5_error_code_to_mqtt311(error_code), + adapter->mqtt5_websocket_handshake_completion_user_data); + + aws_ref_count_release(&adapter->internal_refs); +} + +static void s_aws_mqtt5_adapter_transform_websocket_handshake_fn( + struct aws_http_message *request, + void *user_data, + aws_mqtt5_transform_websocket_handshake_complete_fn *complete_fn, + void *complete_ctx) { + + struct aws_mqtt_client_connection_5_impl *adapter = user_data; + + if (adapter->websocket_handshake_transformer == NULL) { + (*complete_fn)(request, AWS_ERROR_SUCCESS, complete_ctx); + } else { + aws_ref_count_acquire(&adapter->internal_refs); + adapter->mqtt5_websocket_handshake_completion_function = complete_fn; + adapter->mqtt5_websocket_handshake_completion_user_data = complete_ctx; + + (*adapter->websocket_handshake_transformer)( + request, + adapter->websocket_handshake_transformer_user_data, + s_aws_mqtt5_adapter_websocket_handshake_completion_fn, + adapter); + } +} + +static void s_set_use_websockets_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_use_websockets_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + adapter->websocket_handshake_transformer = set_task->transformer; + adapter->websocket_handshake_transformer_user_data = set_task->transformer_user_data; + + /* we're in the mqtt5 client's event loop; it's safe to access its internal state */ + adapter->client->config->websocket_handshake_transform = s_aws_mqtt5_adapter_transform_websocket_handshake_fn; + adapter->client->config->websocket_handshake_transform_user_data = adapter; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + aws_mem_release(set_task->allocator, set_task); +} + +static struct aws_mqtt_set_use_websockets_task *s_aws_mqtt_set_use_websockets_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + aws_mqtt_transform_websocket_handshake_fn *transformer, + void *transformer_user_data) { + + struct aws_mqtt_set_use_websockets_task *set_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_use_websockets_task)); + + aws_task_init(&set_task->task, s_set_use_websockets_task_fn, (void *)set_task, "SetUseWebsocketsTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + set_task->transformer = transformer; + set_task->transformer_user_data = transformer_user_data; + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_use_websockets( + void *impl, + aws_mqtt_transform_websocket_handshake_fn *transformer, + void *transformer_user_data, + aws_mqtt_validate_websocket_handshake_fn *validator, + void *validator_user_data) { + + /* mqtt5 doesn't use these */ + (void)validator; + (void)validator_user_data; + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_use_websockets_task *task = + s_aws_mqtt_set_use_websockets_task_new(adapter->allocator, adapter, transformer, transformer_user_data); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set use websockets task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_host_resolution_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + struct aws_host_resolution_config host_resolution_config; +}; + +static void s_set_host_resolution_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_host_resolution_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + /* we're in the mqtt5 client's event loop; it's safe to access internal state */ + adapter->client->config->host_resolution_override = set_task->host_resolution_config; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + aws_mem_release(set_task->allocator, set_task); +} + +static struct aws_mqtt_set_host_resolution_task *s_aws_mqtt_set_host_resolution_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + const struct aws_host_resolution_config *host_resolution_config) { + + struct aws_mqtt_set_host_resolution_task *set_task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_host_resolution_task)); + + aws_task_init(&set_task->task, s_set_host_resolution_task_fn, (void *)set_task, "SetHostResolutionTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + set_task->host_resolution_config = *host_resolution_config; + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_host_resolution_options( + void *impl, + const struct aws_host_resolution_config *host_resolution_config) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_host_resolution_task *task = + s_aws_mqtt_set_host_resolution_task_new(adapter->allocator, adapter, host_resolution_config); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set reconnect timeout task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_will_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + struct aws_byte_buf topic_buffer; + enum aws_mqtt_qos qos; + bool retain; + struct aws_byte_buf payload_buffer; +}; + +static void s_aws_mqtt_set_will_task_destroy(struct aws_mqtt_set_will_task *task) { + if (task == NULL) { + return; + } + + aws_byte_buf_clean_up(&task->topic_buffer); + aws_byte_buf_clean_up(&task->payload_buffer); + + aws_mem_release(task->allocator, task); +} + +static void s_set_will_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_will_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + /* we're in the mqtt5 client's event loop; it's safe to access internal state */ + struct aws_mqtt5_packet_connect_storage *connect = adapter->client->config->connect; + + /* clean up the old will if necessary */ + if (connect->will != NULL) { + aws_mqtt5_packet_publish_storage_clean_up(connect->will); + aws_mem_release(connect->allocator, connect->will); + connect->will = NULL; + } + + struct aws_mqtt5_packet_publish_view will = { + .topic = aws_byte_cursor_from_buf(&set_task->topic_buffer), + .qos = (enum aws_mqtt5_qos)set_task->qos, + .retain = set_task->retain, + .payload = aws_byte_cursor_from_buf(&set_task->payload_buffer), + }; + + /* make a new will */ + connect->will = aws_mem_calloc(connect->allocator, 1, sizeof(struct aws_mqtt5_packet_publish_storage)); + aws_mqtt5_packet_publish_storage_init(connect->will, connect->allocator, &will); + + /* manually update the storage view's will reference */ + connect->storage_view.will = &connect->will->storage_view; + +done: + + aws_ref_count_release(&adapter->internal_refs); + + s_aws_mqtt_set_will_task_destroy(set_task); +} + +static struct aws_mqtt_set_will_task *s_aws_mqtt_set_will_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + bool retain, + const struct aws_byte_cursor *payload) { + + if (topic == NULL) { + return NULL; + } + + struct aws_mqtt_set_will_task *set_task = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_will_task)); + + aws_task_init(&set_task->task, s_set_will_task_fn, (void *)set_task, "SetWillTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + + set_task->qos = qos; + set_task->retain = retain; + aws_byte_buf_init_copy_from_cursor(&set_task->topic_buffer, allocator, *topic); + if (payload != NULL) { + aws_byte_buf_init_copy_from_cursor(&set_task->payload_buffer, allocator, *payload); + } + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_will( + void *impl, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + bool retain, + const struct aws_byte_cursor *payload) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + /* check qos */ + if (qos < 0 || qos > AWS_MQTT_QOS_EXACTLY_ONCE) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter, invalid qos for will", (void *)adapter); + return aws_raise_error(AWS_ERROR_MQTT_INVALID_QOS); + } + + /* check topic */ + if (!aws_mqtt_is_valid_topic(topic)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter, invalid topic for will", (void *)adapter); + return aws_raise_error(AWS_ERROR_MQTT_INVALID_TOPIC); + } + + struct aws_mqtt_set_will_task *task = + s_aws_mqtt_set_will_task_new(adapter->allocator, adapter, topic, qos, retain, payload); + if (task == NULL) { + AWS_LOGF_ERROR(AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: failed to create set will task", (void *)adapter); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +struct aws_mqtt_set_login_task { + struct aws_task task; + struct aws_allocator *allocator; + struct aws_mqtt_client_connection_5_impl *adapter; + + struct aws_byte_buf username_buffer; + struct aws_byte_buf password_buffer; +}; + +static void s_aws_mqtt_set_login_task_destroy(struct aws_mqtt_set_login_task *task) { + if (task == NULL) { + return; + } + + aws_byte_buf_clean_up_secure(&task->username_buffer); + aws_byte_buf_clean_up_secure(&task->password_buffer); + + aws_mem_release(task->allocator, task); +} + +static void s_set_login_task_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_set_login_task *set_task = arg; + struct aws_mqtt_client_connection_5_impl *adapter = set_task->adapter; + if (status != AWS_TASK_STATUS_RUN_READY) { + goto done; + } + + struct aws_byte_cursor username_cursor = aws_byte_cursor_from_buf(&set_task->username_buffer); + struct aws_byte_cursor password_cursor = aws_byte_cursor_from_buf(&set_task->password_buffer); + + /* we're in the mqtt5 client's event loop; it's safe to access internal state */ + struct aws_mqtt5_packet_connect_storage *old_connect = adapter->client->config->connect; + + /* + * Packet storage stores binary data in a single buffer. The safest way to replace some binary data is + * to make a new storage from the old storage, deleting the old storage after construction is complete. + */ + struct aws_mqtt5_packet_connect_view new_connect_view = old_connect->storage_view; + + if (set_task->username_buffer.len > 0) { + new_connect_view.username = &username_cursor; + } else { + new_connect_view.username = NULL; + } + + if (set_task->password_buffer.len > 0) { + new_connect_view.password = &password_cursor; + } else { + new_connect_view.password = NULL; + } + + if (aws_mqtt5_packet_connect_view_validate(&new_connect_view)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter - invalid CONNECT username or password", + (void *)adapter); + goto done; + } + + struct aws_mqtt5_packet_connect_storage *new_connect = + aws_mem_calloc(adapter->allocator, 1, sizeof(struct aws_mqtt5_packet_connect_storage)); + aws_mqtt5_packet_connect_storage_init(new_connect, adapter->allocator, &new_connect_view); + + adapter->client->config->connect = new_connect; + aws_mqtt5_packet_connect_storage_clean_up(old_connect); + aws_mem_release(old_connect->allocator, old_connect); + +done: + + aws_ref_count_release(&adapter->internal_refs); + + s_aws_mqtt_set_login_task_destroy(set_task); +} + +static struct aws_mqtt_set_login_task *s_aws_mqtt_set_login_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection_5_impl *adapter, + const struct aws_byte_cursor *username, + const struct aws_byte_cursor *password) { + + struct aws_mqtt_set_login_task *set_task = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_set_login_task)); + + aws_task_init(&set_task->task, s_set_login_task_fn, (void *)set_task, "SetLoginTask"); + set_task->allocator = adapter->allocator; + set_task->adapter = (struct aws_mqtt_client_connection_5_impl *)aws_ref_count_acquire(&adapter->internal_refs); + + if (username != NULL) { + aws_byte_buf_init_copy_from_cursor(&set_task->username_buffer, allocator, *username); + } + + if (password != NULL) { + aws_byte_buf_init_copy_from_cursor(&set_task->password_buffer, allocator, *password); + } + + return set_task; +} + +static int s_aws_mqtt_client_connection_5_set_login( + void *impl, + const struct aws_byte_cursor *username, + const struct aws_byte_cursor *password) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + struct aws_mqtt_set_login_task *task = + s_aws_mqtt_set_login_task_new(adapter->allocator, adapter, username, password); + if (task == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: failed to create set login task, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return AWS_OP_ERR; + } + + aws_event_loop_schedule_task_now(adapter->loop, &task->task); + + return AWS_OP_SUCCESS; +} + +static void s_aws_mqtt5_to_mqtt3_adapter_on_zero_internal_refs(void *context) { + struct aws_mqtt_client_connection_5_impl *adapter = context; + + s_aws_mqtt_adapter_final_destroy(adapter); +} + +static void s_aws_mqtt5_to_mqtt3_adapter_on_listener_detached(void *context) { + struct aws_mqtt_client_connection_5_impl *adapter = context; + + /* + * Release the single internal reference that we started with. Only ephemeral references for cross-thread + * tasks might remain, and they will disappear quickly. + */ + aws_ref_count_release(&adapter->internal_refs); +} + +static struct aws_mqtt_client_connection *s_aws_mqtt_client_connection_5_acquire(void *impl) { + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + aws_ref_count_acquire(&adapter->external_refs); + + return &adapter->base; +} + +static void s_aws_mqtt5_to_mqtt3_adapter_on_zero_external_refs(void *impl) { + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + /* + * When the adapter's exernal ref count goes to zero, here's what we want to do: + * + * (1) Release the client listener, starting its asynchronous shutdown process (since we're the only user + * of it) + * (2) Wait for the client listener to notify us that asynchronous shutdown is over. At this point we + * are guaranteed that no more callbacks from the mqtt5 client will reach us. + * (3) Release the single internal ref we started with when the adapter was created. + * (4) On last internal ref, we can safely release the mqtt5 client and synchronously clean up all other + * resources + * + * Step (1) is done here. + * Steps (2) and (3) are accomplished by s_aws_mqtt5_to_mqtt3_adapter_on_listener_detached + * Step (4) is completed by s_aws_mqtt5_to_mqtt3_adapter_on_zero_internal_refs + */ + aws_mqtt5_listener_release(adapter->listener); +} + +static void s_aws_mqtt_client_connection_5_release(void *impl) { + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + aws_ref_count_release(&adapter->external_refs); +} + +/* + * When submitting an operation (across threads), we not only need to keep the adapter alive, we also need to keep + * the operation alive since it's technically already being tracked within the adapter's operational state. + * + * Note: we may not truly need the operation ref but it's safer to keep it. + */ +static void s_aws_mqtt5_to_mqtt3_adapter_operation_acquire_cross_thread_refs( + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation) { + if (!operation->holding_adapter_ref) { + operation->holding_adapter_ref = true; + aws_ref_count_acquire(&operation->adapter->internal_refs); + } + + aws_mqtt5_to_mqtt3_adapter_operation_acquire(operation); +} + +/* + * Once an operation has been received on the adapter's event loop, whether reject or accepted, we must release the + * transient references to the operation and adapter + */ +static void s_aws_mqtt5_to_mqtt3_adapter_operation_release_cross_thread_refs( + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation) { + if (operation->holding_adapter_ref) { + operation->holding_adapter_ref = false; + aws_ref_count_release(&operation->adapter->internal_refs); + } + + aws_mqtt5_to_mqtt3_adapter_operation_release(operation); +} + +static void s_adapter_publish_operation_destroy(void *context) { + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation = context; + if (operation == NULL) { + return; + } + + struct aws_mqtt5_to_mqtt3_adapter_operation_publish *publish_op = operation->impl; + + struct aws_mqtt_client_connection_5_impl *adapter_to_release = NULL; + if (publish_op->base.holding_adapter_ref) { + adapter_to_release = publish_op->base.adapter; + } + + /* We're going away before our MQTT5 operation, make sure it doesn't try to call us back when it completes */ + publish_op->publish_op->completion_options.completion_callback = NULL; + publish_op->publish_op->completion_options.completion_user_data = NULL; + + aws_mqtt5_operation_release(&publish_op->publish_op->base); + + aws_mem_release(operation->allocator, operation); + + if (adapter_to_release != NULL) { + aws_ref_count_release(&adapter_to_release->internal_refs); + } +} + +static void s_aws_mqtt5_to_mqtt3_adapter_publish_completion_fn( + enum aws_mqtt5_packet_type packet_type, + const void *packet, + int error_code, + void *complete_ctx) { + + int error_code_final = error_code; + + if (error_code_final == AWS_ERROR_SUCCESS && packet_type == AWS_MQTT5_PT_PUBACK) { + const struct aws_mqtt5_packet_puback_view *puback_view = packet; + if (puback_view->reason_code >= 128) { + error_code_final = AWS_ERROR_MQTT_ACK_REASON_CODE_FAILURE; + } + } + + struct aws_mqtt5_to_mqtt3_adapter_operation_publish *publish_op = complete_ctx; + + if (publish_op->on_publish_complete != NULL) { + (*publish_op->on_publish_complete)( + &publish_op->base.adapter->base, + publish_op->base.id, + error_code_final, + publish_op->on_publish_complete_user_data); + } + + aws_mqtt5_to_mqtt3_adapter_operation_table_remove_operation( + &publish_op->base.adapter->operational_state, publish_op->base.id); +} + +static void s_fail_publish(void *impl, int error_code) { + struct aws_mqtt5_to_mqtt3_adapter_operation_publish *publish_op = impl; + + if (publish_op->on_publish_complete != NULL) { + (*publish_op->on_publish_complete)( + &publish_op->base.adapter->base, + publish_op->base.id, + error_code, + publish_op->on_publish_complete_user_data); + } +} + +static struct aws_mqtt5_to_mqtt3_adapter_operation_vtable s_publish_vtable = { + .fail_fn = s_fail_publish, +}; + +struct aws_mqtt5_to_mqtt3_adapter_operation_publish *aws_mqtt5_to_mqtt3_adapter_operation_new_publish( + struct aws_allocator *allocator, + const struct aws_mqtt5_to_mqtt3_adapter_publish_options *options) { + struct aws_mqtt5_to_mqtt3_adapter_operation_publish *publish_op = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_to_mqtt3_adapter_operation_publish)); + + publish_op->base.allocator = allocator; + aws_ref_count_init(&publish_op->base.ref_count, publish_op, s_adapter_publish_operation_destroy); + publish_op->base.impl = publish_op; + publish_op->base.vtable = &s_publish_vtable; + publish_op->base.type = AWS_MQTT5TO3_AOT_PUBLISH; + publish_op->base.adapter = options->adapter; + publish_op->base.holding_adapter_ref = false; + + struct aws_mqtt5_packet_publish_view publish_view = { + .topic = options->topic, + .qos = (enum aws_mqtt5_qos)options->qos, + .payload = options->payload, + .retain = options->retain, + }; + + struct aws_mqtt5_publish_completion_options publish_completion_options = { + .completion_callback = s_aws_mqtt5_to_mqtt3_adapter_publish_completion_fn, + .completion_user_data = publish_op, + }; + + publish_op->publish_op = aws_mqtt5_operation_publish_new( + allocator, options->adapter->client, &publish_view, &publish_completion_options); + if (publish_op->publish_op == NULL) { + goto error; + } + + publish_op->on_publish_complete = options->on_complete; + publish_op->on_publish_complete_user_data = options->on_complete_userdata; + + return publish_op; + +error: + + aws_mqtt5_to_mqtt3_adapter_operation_release(&publish_op->base); + + return NULL; +} + +void s_adapter_publish_submission_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt5_to_mqtt3_adapter_operation_publish *operation = arg; + + struct aws_mqtt_client_connection_5_impl *adapter = operation->base.adapter; + + aws_mqtt5_client_submit_operation_internal( + adapter->client, &operation->publish_op->base, status != AWS_TASK_STATUS_RUN_READY); + + /* + * The operation has been submitted in-thread. We can release the transient refs (operation, adapter) needed to + * keep things alive during the handover + */ + s_aws_mqtt5_to_mqtt3_adapter_operation_release_cross_thread_refs(&operation->base); +} + +static uint16_t s_aws_mqtt_client_connection_5_publish( + void *impl, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + bool retain, + const struct aws_byte_cursor *payload, + aws_mqtt_op_complete_fn *on_complete, + void *userdata) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + AWS_LOGF_DEBUG(AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter, invoking publish API", (void *)adapter); + + /* check qos */ + if (qos < 0 || qos > AWS_MQTT_QOS_EXACTLY_ONCE) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter, invalid qos for publish", (void *)adapter); + aws_raise_error(AWS_ERROR_MQTT_INVALID_QOS); + return 0; + } + + if (!aws_mqtt_is_valid_topic(topic)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter, invalid topic for publish", (void *)adapter); + aws_raise_error(AWS_ERROR_MQTT_INVALID_TOPIC); + return 0; + } + + struct aws_byte_cursor topic_cursor = *topic; + struct aws_byte_cursor payload_cursor; + AWS_ZERO_STRUCT(payload_cursor); + if (payload != NULL) { + payload_cursor = *payload; + } + + struct aws_mqtt5_to_mqtt3_adapter_publish_options publish_options = { + .adapter = adapter, + .topic = topic_cursor, + .qos = qos, + .retain = retain, + .payload = payload_cursor, + .on_complete = on_complete, + .on_complete_userdata = userdata, + }; + + struct aws_mqtt5_to_mqtt3_adapter_operation_publish *operation = + aws_mqtt5_to_mqtt3_adapter_operation_new_publish(adapter->allocator, &publish_options); + if (operation == NULL) { + return 0; + } + + if (aws_mqtt5_to_mqtt3_adapter_operation_table_add_operation(&adapter->operational_state, &operation->base)) { + goto error; + } + + uint16_t synthetic_id = operation->base.id; + + /* + * While in-transit to the adapter event loop, we take refs to both the operation and the adapter so that we + * are guaranteed they are still alive when the cross-thread submission task is run. + */ + s_aws_mqtt5_to_mqtt3_adapter_operation_acquire_cross_thread_refs(&operation->base); + + aws_task_init( + &operation->base.submission_task, + s_adapter_publish_submission_fn, + operation, + "Mqtt5ToMqtt3AdapterPublishSubmission"); + + aws_event_loop_schedule_task_now(adapter->loop, &operation->base.submission_task); + + return synthetic_id; + +error: + + aws_mqtt5_to_mqtt3_adapter_operation_release(&operation->base); + + return 0; +} + +static void s_adapter_subscribe_operation_destroy(void *context) { + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation = context; + if (operation == NULL) { + return; + } + + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *subscribe_op = operation->impl; + + size_t subscription_count = aws_array_list_length(&subscribe_op->subscriptions); + for (size_t i = 0; i < subscription_count; ++i) { + struct aws_mqtt_subscription_set_subscription_record *record = NULL; + aws_array_list_get_at(&subscribe_op->subscriptions, &record, i); + + aws_mqtt_subscription_set_subscription_record_destroy(record); + } + aws_array_list_clean_up(&subscribe_op->subscriptions); + + struct aws_mqtt_client_connection_5_impl *adapter_to_release = NULL; + if (subscribe_op->base.holding_adapter_ref) { + adapter_to_release = subscribe_op->base.adapter; + } + + /* We're going away before our MQTT5 operation, make sure it doesn't try to call us back when it completes */ + if (subscribe_op->subscribe_op != NULL) { + subscribe_op->subscribe_op->completion_options.completion_callback = NULL; + subscribe_op->subscribe_op->completion_options.completion_user_data = NULL; + + aws_mqtt5_operation_release(&subscribe_op->subscribe_op->base); + } + + aws_mem_release(operation->allocator, operation); + + if (adapter_to_release != NULL) { + aws_ref_count_release(&adapter_to_release->internal_refs); + } +} + +static enum aws_mqtt_qos s_convert_mqtt5_suback_reason_code_to_mqtt3_granted_qos( + enum aws_mqtt5_suback_reason_code reason_code) { + switch (reason_code) { + case AWS_MQTT5_SARC_GRANTED_QOS_0: + case AWS_MQTT5_SARC_GRANTED_QOS_1: + case AWS_MQTT5_SARC_GRANTED_QOS_2: + return (enum aws_mqtt_qos)reason_code; + + default: + return AWS_MQTT_QOS_FAILURE; + } +} + +static void s_aws_mqtt5_to_mqtt3_adapter_subscribe_completion_helper( + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *subscribe_op, + const struct aws_mqtt5_packet_suback_view *suback, + int error_code) { + + struct aws_mqtt_client_connection_5_impl *adapter = subscribe_op->base.adapter; + struct aws_mqtt_subscription_set_subscription_record *record = NULL; + + if (subscribe_op->on_suback != NULL) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, completing single-topic subscribe", + (void *)adapter); + + struct aws_byte_cursor topic_filter; + AWS_ZERO_STRUCT(topic_filter); + + enum aws_mqtt_qos granted_qos = AWS_MQTT_QOS_AT_MOST_ONCE; + + size_t subscription_count = aws_array_list_length(&subscribe_op->subscriptions); + if (subscription_count > 0) { + aws_array_list_get_at(&subscribe_op->subscriptions, &record, 0); + topic_filter = record->subscription_view.topic_filter; + } + + if (suback != NULL) { + if (suback->reason_code_count > 0) { + granted_qos = s_convert_mqtt5_suback_reason_code_to_mqtt3_granted_qos(suback->reason_codes[0]); + } + } else { + granted_qos = AWS_MQTT_QOS_FAILURE; + } + (*subscribe_op->on_suback)( + &adapter->base, + subscribe_op->base.id, + &topic_filter, + granted_qos, + error_code, + subscribe_op->on_suback_user_data); + } + + if (subscribe_op->on_multi_suback != NULL) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, completing multi-topic subscribe", + (void *)adapter); + + if (suback == NULL) { + (*subscribe_op->on_multi_suback)( + &adapter->base, subscribe_op->base.id, NULL, error_code, subscribe_op->on_multi_suback_user_data); + } else { + AWS_VARIABLE_LENGTH_ARRAY( + struct aws_mqtt_topic_subscription, multi_sub_subscription_buf, suback->reason_code_count); + AWS_VARIABLE_LENGTH_ARRAY( + struct aws_mqtt_topic_subscription *, multi_sub_subscription_ptr_buf, suback->reason_code_count); + struct aws_mqtt_topic_subscription *subscription_ptr = + (struct aws_mqtt_topic_subscription *)multi_sub_subscription_buf; + + struct aws_array_list multi_sub_list; + aws_array_list_init_static( + &multi_sub_list, + multi_sub_subscription_ptr_buf, + suback->reason_code_count, + sizeof(struct aws_mqtt_topic_subscription *)); + + size_t subscription_count = aws_array_list_length(&subscribe_op->subscriptions); + + for (size_t i = 0; i < suback->reason_code_count; ++i) { + struct aws_mqtt_topic_subscription *subscription = subscription_ptr + i; + AWS_ZERO_STRUCT(*subscription); + + subscription->qos = s_convert_mqtt5_suback_reason_code_to_mqtt3_granted_qos(suback->reason_codes[i]); + + if (i < subscription_count) { + aws_array_list_get_at(&subscribe_op->subscriptions, &record, i); + + subscription->topic = record->subscription_view.topic_filter; + subscription->on_publish = record->subscription_view.on_publish_received; + subscription->on_publish_ud = record->subscription_view.callback_user_data; + subscription->on_cleanup = record->subscription_view.on_cleanup; + } + + aws_array_list_push_back(&multi_sub_list, &subscription); + } + (*subscribe_op->on_multi_suback)( + &adapter->base, + subscribe_op->base.id, + &multi_sub_list, + error_code, + subscribe_op->on_multi_suback_user_data); + } + } +} + +static void s_aws_mqtt5_to_mqtt3_adapter_subscribe_completion_fn( + const struct aws_mqtt5_packet_suback_view *suback, + int error_code, + void *complete_ctx) { + + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *subscribe_op = complete_ctx; + struct aws_mqtt_client_connection_5_impl *adapter = subscribe_op->base.adapter; + + s_aws_mqtt5_to_mqtt3_adapter_subscribe_completion_helper(subscribe_op, suback, error_code); + + aws_mqtt5_to_mqtt3_adapter_operation_table_remove_operation(&adapter->operational_state, subscribe_op->base.id); +} + +static int s_validate_adapter_subscribe_options( + size_t subscription_count, + struct aws_mqtt_topic_subscription *subscriptions, + struct aws_mqtt_client_connection_5_impl *adapter) { + for (size_t i = 0; i < subscription_count; ++i) { + struct aws_mqtt_topic_subscription *subscription = subscriptions + i; + + /* check qos */ + if (subscription->qos < 0 || subscription->qos > AWS_MQTT_QOS_EXACTLY_ONCE) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter, invalid qos for subscribe", (void *)adapter); + return aws_raise_error(AWS_ERROR_MQTT_INVALID_QOS); + } + + /* check topic */ + if (!aws_mqtt_is_valid_topic_filter(&subscription->topic)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, invalid topic filter for subscribe", + (void *)adapter); + return aws_raise_error(AWS_ERROR_MQTT_INVALID_TOPIC); + } + } + + return AWS_OP_SUCCESS; +} + +static int s_aws_mqtt5_to_mqtt3_adapter_build_subscribe( + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *subscribe_op, + size_t subscription_count, + struct aws_mqtt_topic_subscription *subscriptions) { + struct aws_allocator *allocator = subscribe_op->base.allocator; + + /* make persistent adapter sub array */ + aws_array_list_init_dynamic( + &subscribe_op->subscriptions, + allocator, + subscription_count, + sizeof(struct aws_mqtt_subscription_set_subscription_record *)); + + for (size_t i = 0; i < subscription_count; ++i) { + struct aws_mqtt_topic_subscription *subscription_options = &subscriptions[i]; + + struct aws_mqtt_subscription_set_subscription_options subscription_record_options = { + .topic_filter = subscription_options->topic, + .qos = (enum aws_mqtt5_qos)subscription_options->qos, + .on_publish_received = subscription_options->on_publish, + .callback_user_data = subscription_options->on_publish_ud, + .on_cleanup = subscription_options->on_cleanup, + }; + struct aws_mqtt_subscription_set_subscription_record *record = + aws_mqtt_subscription_set_subscription_record_new(allocator, &subscription_record_options); + + aws_array_list_push_back(&subscribe_op->subscriptions, &record); + } + + /* make temp mqtt5 subscription view array */ + AWS_VARIABLE_LENGTH_ARRAY(struct aws_mqtt5_subscription_view, mqtt5_subscription_buffer, subscription_count); + struct aws_mqtt5_subscription_view *subscription_ptr = mqtt5_subscription_buffer; + for (size_t i = 0; i < subscription_count; ++i) { + struct aws_mqtt5_subscription_view *subscription = subscription_ptr + i; + AWS_ZERO_STRUCT(*subscription); + + subscription->topic_filter = subscriptions[i].topic; + subscription->qos = (enum aws_mqtt5_qos)subscriptions[i].qos; + } + + struct aws_mqtt5_packet_subscribe_view subscribe_view = { + .subscriptions = subscription_ptr, + .subscription_count = subscription_count, + }; + + struct aws_mqtt5_subscribe_completion_options subscribe_completion_options = { + .completion_callback = s_aws_mqtt5_to_mqtt3_adapter_subscribe_completion_fn, + .completion_user_data = subscribe_op, + }; + + subscribe_op->subscribe_op = aws_mqtt5_operation_subscribe_new( + allocator, subscribe_op->base.adapter->client, &subscribe_view, &subscribe_completion_options); + + if (subscribe_op->subscribe_op == NULL) { + /* subscribe options validation will have been raised as the error */ + return AWS_OP_ERR; + } + + return AWS_OP_SUCCESS; +} + +static void s_fail_subscribe(void *impl, int error_code) { + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *subscribe_op = impl; + + s_aws_mqtt5_to_mqtt3_adapter_subscribe_completion_helper(subscribe_op, NULL, error_code); +} + +static struct aws_mqtt5_to_mqtt3_adapter_operation_vtable s_subscribe_vtable = { + .fail_fn = s_fail_subscribe, +}; + +struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *aws_mqtt5_to_mqtt3_adapter_operation_new_subscribe( + struct aws_allocator *allocator, + const struct aws_mqtt5_to_mqtt3_adapter_subscribe_options *options, + struct aws_mqtt_client_connection_5_impl *adapter) { + + if (s_validate_adapter_subscribe_options(options->subscription_count, options->subscriptions, adapter)) { + return NULL; + } + + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *subscribe_op = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe)); + + subscribe_op->base.allocator = allocator; + aws_ref_count_init(&subscribe_op->base.ref_count, subscribe_op, s_adapter_subscribe_operation_destroy); + subscribe_op->base.impl = subscribe_op; + subscribe_op->base.vtable = &s_subscribe_vtable; + subscribe_op->base.type = AWS_MQTT5TO3_AOT_SUBSCRIBE; + subscribe_op->base.adapter = options->adapter; + subscribe_op->base.holding_adapter_ref = false; + + /* + * If we're a regular subscribe, build the mqtt5 operation now. Otherwise, we have to wait until + * we're on the event loop thread and it's safe to query the subscription set. + */ + if (options->subscription_count > 0) { + if (s_aws_mqtt5_to_mqtt3_adapter_build_subscribe( + subscribe_op, options->subscription_count, options->subscriptions)) { + goto error; + } + } + + subscribe_op->on_suback = options->on_suback; + subscribe_op->on_suback_user_data = options->on_suback_user_data; + subscribe_op->on_multi_suback = options->on_multi_suback; + subscribe_op->on_multi_suback_user_data = options->on_multi_suback_user_data; + + return subscribe_op; + +error: + + aws_mqtt5_to_mqtt3_adapter_operation_release(&subscribe_op->base); + + return NULL; +} + +static int s_aws_mqtt5_to_mqtt3_adapter_build_resubscribe( + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *subscribe_op, + struct aws_array_list *full_subscriptions) { + size_t subscription_count = aws_array_list_length(full_subscriptions); + + AWS_VARIABLE_LENGTH_ARRAY(struct aws_mqtt_topic_subscription, multi_sub_subscriptions, subscription_count); + + for (size_t i = 0; i < subscription_count; ++i) { + struct aws_mqtt_subscription_set_subscription_options *existing_subscription = NULL; + aws_array_list_get_at_ptr(full_subscriptions, (void **)&existing_subscription, i); + + multi_sub_subscriptions[i].topic = existing_subscription->topic_filter; + multi_sub_subscriptions[i].qos = (enum aws_mqtt_qos)existing_subscription->qos; + multi_sub_subscriptions[i].on_publish = existing_subscription->on_publish_received; + multi_sub_subscriptions[i].on_cleanup = existing_subscription->on_cleanup; + multi_sub_subscriptions[i].on_publish_ud = existing_subscription->callback_user_data; + } + + return s_aws_mqtt5_to_mqtt3_adapter_build_subscribe(subscribe_op, subscription_count, multi_sub_subscriptions); +} + +void s_adapter_subscribe_submission_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *operation = arg; + struct aws_mqtt_client_connection_5_impl *adapter = operation->base.adapter; + + struct aws_array_list full_subscriptions; + AWS_ZERO_STRUCT(full_subscriptions); + + /* If we're a re-subscribe, it's now safe to build the subscription set and MQTT5 subscribe op */ + if (operation->subscribe_op == NULL) { + aws_mqtt_subscription_set_get_subscriptions(adapter->subscriptions, &full_subscriptions); + size_t subscription_count = aws_array_list_length(&full_subscriptions); + if (subscription_count == 0 || s_aws_mqtt5_to_mqtt3_adapter_build_resubscribe(operation, &full_subscriptions)) { + /* There's either nothing to do (no subscriptions) or we failed to build the op (should never happen) */ + int error_code = aws_last_error(); + if (subscription_count == 0) { + error_code = AWS_ERROR_MQTT_CONNECTION_RESUBSCRIBE_NO_TOPICS; + } + + if (operation->on_multi_suback) { + (*operation->on_multi_suback)( + &adapter->base, operation->base.id, NULL, error_code, operation->on_multi_suback_user_data); + } + + /* + * Remove the persistent ref represented by being seated in the incomplete operations table. + * The other (transient) ref gets released at the end of the function. + */ + aws_mqtt5_to_mqtt3_adapter_operation_table_remove_operation( + &adapter->operational_state, operation->base.id); + goto complete; + } + } + + size_t subscription_count = aws_array_list_length(&operation->subscriptions); + for (size_t i = 0; i < subscription_count; ++i) { + struct aws_mqtt_subscription_set_subscription_record *record = NULL; + aws_array_list_get_at(&operation->subscriptions, &record, i); + + aws_mqtt_subscription_set_add_subscription(adapter->subscriptions, &record->subscription_view); + } + + aws_mqtt5_client_submit_operation_internal( + adapter->client, &operation->subscribe_op->base, status != AWS_TASK_STATUS_RUN_READY); + +complete: + + aws_array_list_clean_up(&full_subscriptions); + + /* + * The operation has been submitted in-thread. We can release the transient refs (operation, adapter) needed to + * keep things alive during the handover + */ + s_aws_mqtt5_to_mqtt3_adapter_operation_release_cross_thread_refs(&operation->base); +} + +static uint16_t s_aws_mqtt_client_connection_5_subscribe( + void *impl, + const struct aws_byte_cursor *topic_filter, + enum aws_mqtt_qos qos, + aws_mqtt_client_publish_received_fn *on_publish, + void *on_publish_ud, + aws_mqtt_userdata_cleanup_fn *on_ud_cleanup, + aws_mqtt_suback_fn *on_suback, + void *on_suback_user_data) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, single-topic subscribe API invoked", + (void *)adapter); + + struct aws_mqtt_topic_subscription subscription = { + .topic = *topic_filter, + .qos = qos, + .on_publish = on_publish, + .on_cleanup = on_ud_cleanup, + .on_publish_ud = on_publish_ud, + }; + + struct aws_mqtt5_to_mqtt3_adapter_subscribe_options subscribe_options = { + .adapter = adapter, + .subscriptions = &subscription, + .subscription_count = 1, + .on_suback = on_suback, + .on_suback_user_data = on_suback_user_data, + }; + + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *operation = + aws_mqtt5_to_mqtt3_adapter_operation_new_subscribe(adapter->allocator, &subscribe_options, adapter); + if (operation == NULL) { + return 0; + } + + if (aws_mqtt5_to_mqtt3_adapter_operation_table_add_operation(&adapter->operational_state, &operation->base)) { + goto error; + } + + uint16_t synthetic_id = operation->base.id; + + /* + * While in-transit to the adapter event loop, we take refs to both the operation and the adapter so that we + * are guaranteed they are still alive when the cross-thread submission task is run. + */ + s_aws_mqtt5_to_mqtt3_adapter_operation_acquire_cross_thread_refs(&operation->base); + + aws_task_init( + &operation->base.submission_task, + s_adapter_subscribe_submission_fn, + operation, + "Mqtt5ToMqtt3AdapterSubscribeSubmission"); + + aws_event_loop_schedule_task_now(adapter->loop, &operation->base.submission_task); + + return synthetic_id; + +error: + + ; + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, single-topic subscribe failed synchronously, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + + aws_mqtt5_to_mqtt3_adapter_operation_release(&operation->base); + + return 0; +} + +static uint16_t s_aws_mqtt_client_connection_5_subscribe_multiple( + void *impl, + const struct aws_array_list *topic_filters, + aws_mqtt_suback_multi_fn *on_suback, + void *on_suback_user_data) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter, multi-topic subscribe API invoked", (void *)adapter); + + if (topic_filters == NULL || aws_array_list_length(topic_filters) == 0) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter multi-topic subscribe empty", (void *)adapter); + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return 0; + } + + struct aws_mqtt_topic_subscription *subscriptions = topic_filters->data; + + struct aws_mqtt5_to_mqtt3_adapter_subscribe_options subscribe_options = { + .adapter = adapter, + .subscriptions = subscriptions, + .subscription_count = aws_array_list_length(topic_filters), + .on_multi_suback = on_suback, + .on_multi_suback_user_data = on_suback_user_data, + }; + + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *operation = + aws_mqtt5_to_mqtt3_adapter_operation_new_subscribe(adapter->allocator, &subscribe_options, adapter); + if (operation == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, multi-topic subscribe operation creation failed, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return 0; + } + + if (aws_mqtt5_to_mqtt3_adapter_operation_table_add_operation(&adapter->operational_state, &operation->base)) { + goto error; + } + + uint16_t synthetic_id = operation->base.id; + + /* + * While in-transit to the adapter event loop, we take refs to both the operation and the adapter so that we + * are guaranteed they are still alive when the cross-thread submission task is run. + */ + s_aws_mqtt5_to_mqtt3_adapter_operation_acquire_cross_thread_refs(&operation->base); + + aws_task_init( + &operation->base.submission_task, + s_adapter_subscribe_submission_fn, + operation, + "Mqtt5ToMqtt3AdapterSubscribeMultipleSubmission"); + + aws_event_loop_schedule_task_now(adapter->loop, &operation->base.submission_task); + + return synthetic_id; + +error: + + ; + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, multi-topic subscribe failed, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + + aws_mqtt5_to_mqtt3_adapter_operation_release(&operation->base); + + return 0; +} + +static void s_adapter_unsubscribe_operation_destroy(void *context) { + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation = context; + if (operation == NULL) { + return; + } + + struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe *unsubscribe_op = operation->impl; + + aws_byte_buf_clean_up(&unsubscribe_op->topic_filter); + + struct aws_mqtt_client_connection_5_impl *adapter_to_release = NULL; + if (unsubscribe_op->base.holding_adapter_ref) { + adapter_to_release = unsubscribe_op->base.adapter; + } + + /* We're going away before our MQTT5 operation, make sure it doesn't try to call us back when it completes */ + unsubscribe_op->unsubscribe_op->completion_options.completion_callback = NULL; + unsubscribe_op->unsubscribe_op->completion_options.completion_user_data = NULL; + + aws_mqtt5_operation_release(&unsubscribe_op->unsubscribe_op->base); + + aws_mem_release(operation->allocator, operation); + + if (adapter_to_release != NULL) { + aws_ref_count_release(&adapter_to_release->internal_refs); + } +} + +static void s_aws_mqtt5_to_mqtt3_adapter_unsubscribe_completion_fn( + const struct aws_mqtt5_packet_unsuback_view *unsuback, + int error_code, + void *complete_ctx) { + (void)unsuback; + + struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe *unsubscribe_op = complete_ctx; + + if (unsubscribe_op->on_unsuback != NULL) { + (*unsubscribe_op->on_unsuback)( + &unsubscribe_op->base.adapter->base, + unsubscribe_op->base.id, + error_code, + unsubscribe_op->on_unsuback_user_data); + } + + aws_mqtt5_to_mqtt3_adapter_operation_table_remove_operation( + &unsubscribe_op->base.adapter->operational_state, unsubscribe_op->base.id); +} + +static void s_fail_unsubscribe(void *impl, int error_code) { + struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe *unsubscribe_op = impl; + + if (unsubscribe_op->on_unsuback != NULL) { + (*unsubscribe_op->on_unsuback)( + &unsubscribe_op->base.adapter->base, + unsubscribe_op->base.id, + error_code, + unsubscribe_op->on_unsuback_user_data); + } +} + +static struct aws_mqtt5_to_mqtt3_adapter_operation_vtable s_unsubscribe_vtable = { + .fail_fn = s_fail_unsubscribe, +}; + +struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe *aws_mqtt5_to_mqtt3_adapter_operation_new_unsubscribe( + struct aws_allocator *allocator, + const struct aws_mqtt5_to_mqtt3_adapter_unsubscribe_options *options) { + + struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe *unsubscribe_op = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe)); + + unsubscribe_op->base.allocator = allocator; + aws_ref_count_init(&unsubscribe_op->base.ref_count, unsubscribe_op, s_adapter_unsubscribe_operation_destroy); + unsubscribe_op->base.impl = unsubscribe_op; + unsubscribe_op->base.vtable = &s_unsubscribe_vtable; + unsubscribe_op->base.type = AWS_MQTT5TO3_AOT_UNSUBSCRIBE; + unsubscribe_op->base.adapter = options->adapter; + unsubscribe_op->base.holding_adapter_ref = false; + + struct aws_mqtt5_packet_unsubscribe_view unsubscribe_view = { + .topic_filters = &options->topic_filter, + .topic_filter_count = 1, + }; + + struct aws_mqtt5_unsubscribe_completion_options unsubscribe_completion_options = { + .completion_callback = s_aws_mqtt5_to_mqtt3_adapter_unsubscribe_completion_fn, + .completion_user_data = unsubscribe_op, + }; + + unsubscribe_op->unsubscribe_op = aws_mqtt5_operation_unsubscribe_new( + allocator, options->adapter->client, &unsubscribe_view, &unsubscribe_completion_options); + if (unsubscribe_op->unsubscribe_op == NULL) { + goto error; + } + + unsubscribe_op->on_unsuback = options->on_unsuback; + unsubscribe_op->on_unsuback_user_data = options->on_unsuback_user_data; + + aws_byte_buf_init_copy_from_cursor(&unsubscribe_op->topic_filter, allocator, options->topic_filter); + + return unsubscribe_op; + +error: + + aws_mqtt5_to_mqtt3_adapter_operation_release(&unsubscribe_op->base); + + return NULL; +} + +void s_adapter_unsubscribe_submission_fn(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe *operation = arg; + + struct aws_mqtt_client_connection_5_impl *adapter = operation->base.adapter; + + aws_mqtt_subscription_set_remove_subscription( + adapter->subscriptions, aws_byte_cursor_from_buf(&operation->topic_filter)); + + aws_mqtt5_client_submit_operation_internal( + adapter->client, &operation->unsubscribe_op->base, status != AWS_TASK_STATUS_RUN_READY); + + /* + * The operation has been submitted in-thread. We can release the transient refs (operation, adapter) needed to + * keep things alive during the handover + */ + s_aws_mqtt5_to_mqtt3_adapter_operation_release_cross_thread_refs(&operation->base); +} + +static uint16_t s_aws_mqtt_client_connection_5_unsubscribe( + void *impl, + const struct aws_byte_cursor *topic_filter, + aws_mqtt_op_complete_fn *on_unsuback, + void *on_unsuback_user_data) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + AWS_LOGF_DEBUG(AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter, unsubscribe called", (void *)adapter); + + if (!aws_mqtt_is_valid_topic_filter(topic_filter)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, unsubscribe failed, invalid topic filter", + (void *)adapter); + aws_raise_error(AWS_ERROR_MQTT_INVALID_TOPIC); + return 0; + } + + struct aws_mqtt5_to_mqtt3_adapter_unsubscribe_options unsubscribe_options = { + .adapter = adapter, + .topic_filter = *topic_filter, + .on_unsuback = on_unsuback, + .on_unsuback_user_data = on_unsuback_user_data, + }; + + struct aws_mqtt5_to_mqtt3_adapter_operation_unsubscribe *operation = + aws_mqtt5_to_mqtt3_adapter_operation_new_unsubscribe(adapter->allocator, &unsubscribe_options); + if (operation == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, unsubscribe operation creation failed, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return 0; + } + + if (aws_mqtt5_to_mqtt3_adapter_operation_table_add_operation(&adapter->operational_state, &operation->base)) { + goto error; + } + + uint16_t synthetic_id = operation->base.id; + + /* + * While in-transit to the adapter event loop, we take refs to both the operation and the adapter so that we + * are guaranteed they are still alive when the cross-thread submission task is run. + */ + s_aws_mqtt5_to_mqtt3_adapter_operation_acquire_cross_thread_refs(&operation->base); + + aws_task_init( + &operation->base.submission_task, + s_adapter_unsubscribe_submission_fn, + operation, + "Mqtt5ToMqtt3AdapterUnsubscribeSubmission"); + + aws_event_loop_schedule_task_now(adapter->loop, &operation->base.submission_task); + + return synthetic_id; + +error: + + ; + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, unsubscribe failed, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + + aws_mqtt5_to_mqtt3_adapter_operation_release(&operation->base); + + return 0; +} + +static int s_aws_mqtt_client_connection_5_reconnect( + void *impl, + aws_mqtt_client_on_connection_complete_fn *on_connection_complete, + void *userdata) { + (void)impl; + (void)on_connection_complete; + (void)userdata; + + /* DEPRECATED, connection will reconnect automatically now. */ + AWS_LOGF_ERROR(AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "aws_mqtt_client_connection_reconnect has been DEPRECATED."); + return aws_raise_error(AWS_ERROR_UNSUPPORTED_OPERATION); +} + +static int s_aws_mqtt_client_connection_5_get_stats( + void *impl, + struct aws_mqtt_connection_operation_statistics *stats) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + // Error checking + if (!adapter) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "Invalid MQTT3-to-5 adapter used when trying to get operation statistics"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + AWS_LOGF_DEBUG(AWS_LS_MQTT5_TO_MQTT3_ADAPTER, "id=%p: mqtt3-to-5-adapter, get_stats invoked", (void *)adapter); + + if (!stats) { + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: Invalid MQTT311 statistics struct used when trying to get operation statistics", + (void *)adapter); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_mqtt5_client_operation_statistics mqtt5_stats; + AWS_ZERO_STRUCT(mqtt5_stats); + aws_mqtt5_client_get_stats(adapter->client, &mqtt5_stats); + + stats->incomplete_operation_count = mqtt5_stats.incomplete_operation_count; + stats->incomplete_operation_size = mqtt5_stats.incomplete_operation_size; + stats->unacked_operation_count = mqtt5_stats.unacked_operation_count; + stats->unacked_operation_size = mqtt5_stats.unacked_operation_size; + + return AWS_OP_SUCCESS; +} + +static uint16_t s_aws_mqtt_5_resubscribe_existing_topics( + void *impl, + aws_mqtt_suback_multi_fn *on_suback, + void *on_suback_user_data) { + + struct aws_mqtt_client_connection_5_impl *adapter = impl; + + AWS_LOGF_DEBUG( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, resubscribe_existing_topics invoked", + (void *)adapter); + + struct aws_mqtt5_to_mqtt3_adapter_subscribe_options subscribe_options = { + .adapter = adapter, + .subscriptions = NULL, + .subscription_count = 0, + .on_multi_suback = on_suback, + .on_multi_suback_user_data = on_suback_user_data, + }; + + struct aws_mqtt5_to_mqtt3_adapter_operation_subscribe *operation = + aws_mqtt5_to_mqtt3_adapter_operation_new_subscribe(adapter->allocator, &subscribe_options, adapter); + if (operation == NULL) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, resubscribe_existing_topics failed on operation creation, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + return 0; + } + + if (aws_mqtt5_to_mqtt3_adapter_operation_table_add_operation(&adapter->operational_state, &operation->base)) { + goto error; + } + + uint16_t synthetic_id = operation->base.id; + + /* + * While in-transit to the adapter event loop, we take refs to both the operation and the adapter so that we + * are guaranteed they are still alive when the cross-thread submission task is run. + */ + s_aws_mqtt5_to_mqtt3_adapter_operation_acquire_cross_thread_refs(&operation->base); + + aws_task_init( + &operation->base.submission_task, + s_adapter_subscribe_submission_fn, + operation, + "Mqtt5ToMqtt3AdapterSubscribeResubscribe"); + + aws_event_loop_schedule_task_now(adapter->loop, &operation->base.submission_task); + + return synthetic_id; + +error: + + ; + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + "id=%p: mqtt3-to-5-adapter, resubscribe_existing_topics failed, error code %d(%s)", + (void *)adapter, + error_code, + aws_error_debug_str(error_code)); + + aws_mqtt5_to_mqtt3_adapter_operation_release(&operation->base); + + return 0; +} + +static struct aws_mqtt_client_connection_vtable s_aws_mqtt_client_connection_5_vtable = { + .acquire_fn = s_aws_mqtt_client_connection_5_acquire, + .release_fn = s_aws_mqtt_client_connection_5_release, + .set_will_fn = s_aws_mqtt_client_connection_5_set_will, + .set_login_fn = s_aws_mqtt_client_connection_5_set_login, + .use_websockets_fn = s_aws_mqtt_client_connection_5_use_websockets, + .set_http_proxy_options_fn = s_aws_mqtt_client_connection_5_set_http_proxy_options, + .set_host_resolution_options_fn = s_aws_mqtt_client_connection_5_set_host_resolution_options, + .set_reconnect_timeout_fn = s_aws_mqtt_client_connection_5_set_reconnect_timeout, + .set_connection_result_handlers = s_aws_mqtt_client_connection_5_set_connection_result_handlers, + .set_connection_interruption_handlers_fn = s_aws_mqtt_client_connection_5_set_interruption_handlers, + .set_connection_closed_handler_fn = s_aws_mqtt_client_connection_5_set_on_closed_handler, + .set_connection_termination_handler_fn = s_aws_mqtt_client_connection_5_set_termination_handler, + .set_on_any_publish_handler_fn = s_aws_mqtt_client_connection_5_set_on_any_publish_handler, + .connect_fn = s_aws_mqtt_client_connection_5_connect, + .reconnect_fn = s_aws_mqtt_client_connection_5_reconnect, + .disconnect_fn = s_aws_mqtt_client_connection_5_disconnect, + .subscribe_multiple_fn = s_aws_mqtt_client_connection_5_subscribe_multiple, + .subscribe_fn = s_aws_mqtt_client_connection_5_subscribe, + .resubscribe_existing_topics_fn = s_aws_mqtt_5_resubscribe_existing_topics, + .unsubscribe_fn = s_aws_mqtt_client_connection_5_unsubscribe, + .publish_fn = s_aws_mqtt_client_connection_5_publish, + .get_stats_fn = s_aws_mqtt_client_connection_5_get_stats, +}; + +static struct aws_mqtt_client_connection_vtable *s_aws_mqtt_client_connection_5_vtable_ptr = + &s_aws_mqtt_client_connection_5_vtable; + +struct aws_mqtt_client_connection *aws_mqtt_client_connection_new_from_mqtt5_client(struct aws_mqtt5_client *client) { + struct aws_allocator *allocator = client->allocator; + struct aws_mqtt_client_connection_5_impl *adapter = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_client_connection_5_impl)); + + adapter->allocator = allocator; + + adapter->base.vtable = s_aws_mqtt_client_connection_5_vtable_ptr; + adapter->base.impl = adapter; + + adapter->client = aws_mqtt5_client_acquire(client); + adapter->loop = client->loop; + adapter->adapter_state = AWS_MQTT_AS_STAY_DISCONNECTED; + + aws_ref_count_init(&adapter->external_refs, adapter, s_aws_mqtt5_to_mqtt3_adapter_on_zero_external_refs); + aws_ref_count_init(&adapter->internal_refs, adapter, s_aws_mqtt5_to_mqtt3_adapter_on_zero_internal_refs); + + aws_mqtt5_to_mqtt3_adapter_operation_table_init(&adapter->operational_state, allocator); + + adapter->subscriptions = aws_mqtt_subscription_set_new(allocator); + + struct aws_mqtt5_listener_config listener_config = { + .client = client, + .listener_callbacks = + { + .listener_publish_received_handler = s_aws_mqtt5_listener_publish_received_adapter, + .listener_publish_received_handler_user_data = adapter, + .lifecycle_event_handler = s_aws_mqtt5_to_mqtt3_adapter_lifecycle_handler, + .lifecycle_event_handler_user_data = adapter, + }, + .termination_callback = s_aws_mqtt5_to_mqtt3_adapter_on_listener_detached, + .termination_callback_user_data = adapter, + }; + adapter->listener = aws_mqtt5_listener_new(allocator, &listener_config); + + return &adapter->base; +} + +#define DEFAULT_MQTT_ADAPTER_OPERATION_TABLE_SIZE 100 + +void aws_mqtt5_to_mqtt3_adapter_operation_table_init( + struct aws_mqtt5_to_mqtt3_adapter_operation_table *table, + struct aws_allocator *allocator) { + aws_mutex_init(&table->lock); + aws_hash_table_init( + &table->operations, + allocator, + DEFAULT_MQTT_ADAPTER_OPERATION_TABLE_SIZE, + aws_mqtt_hash_uint16_t, + aws_mqtt_compare_uint16_t_eq, + NULL, + NULL); + table->next_id = 1; +} + +static int s_adapter_operation_fail(void *context, struct aws_hash_element *operation_element) { + (void)context; + + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation = operation_element->value; + + (*operation->vtable->fail_fn)(operation->impl, AWS_ERROR_MQTT_CONNECTION_DESTROYED); + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; +} + +static int s_adapter_operation_clean_up(void *context, struct aws_hash_element *operation_element) { + (void)context; + + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation = operation_element->value; + + aws_mqtt5_to_mqtt3_adapter_operation_release(operation); + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; +} + +void aws_mqtt5_to_mqtt3_adapter_operation_table_clean_up(struct aws_mqtt5_to_mqtt3_adapter_operation_table *table) { + aws_hash_table_foreach(&table->operations, s_adapter_operation_fail, table); + aws_hash_table_foreach(&table->operations, s_adapter_operation_clean_up, table); + + aws_hash_table_clean_up(&table->operations); + + aws_mutex_clean_up(&table->lock); +} + +static uint16_t s_next_adapter_id(uint16_t current_id) { + if (++current_id == 0) { + current_id = 1; + } + + return current_id; +} + +int aws_mqtt5_to_mqtt3_adapter_operation_table_add_operation( + struct aws_mqtt5_to_mqtt3_adapter_operation_table *table, + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation) { + + operation->id = 0; + + aws_mutex_lock(&table->lock); + + uint16_t current_id = table->next_id; + struct aws_hash_element *elem = NULL; + for (uint16_t i = 0; i < UINT16_MAX; ++i) { + aws_hash_table_find(&table->operations, ¤t_id, &elem); + + if (elem == NULL) { + operation->id = current_id; + table->next_id = s_next_adapter_id(current_id); + + if (aws_hash_table_put(&table->operations, &operation->id, operation, NULL)) { + operation->id = 0; + } + + goto done; + } + + current_id = s_next_adapter_id(current_id); + } + +done: + + aws_mutex_unlock(&table->lock); + + return (operation->id != 0) ? AWS_OP_SUCCESS : aws_raise_error(AWS_ERROR_MQTT_QUEUE_FULL); +} + +void aws_mqtt5_to_mqtt3_adapter_operation_table_remove_operation( + struct aws_mqtt5_to_mqtt3_adapter_operation_table *table, + uint16_t operation_id) { + struct aws_hash_element existing_element; + AWS_ZERO_STRUCT(existing_element); + + aws_mutex_lock(&table->lock); + aws_hash_table_remove(&table->operations, &operation_id, &existing_element, NULL); + aws_mutex_unlock(&table->lock); + + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation = existing_element.value; + if (operation != NULL) { + aws_mqtt5_to_mqtt3_adapter_operation_release(operation); + } +} + +struct aws_mqtt5_to_mqtt3_adapter_operation_base *aws_mqtt5_to_mqtt3_adapter_operation_release( + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation) { + if (operation != NULL) { + aws_ref_count_release(&operation->ref_count); + } + + return NULL; +} + +struct aws_mqtt5_to_mqtt3_adapter_operation_base *aws_mqtt5_to_mqtt3_adapter_operation_acquire( + struct aws_mqtt5_to_mqtt3_adapter_operation_base *operation) { + if (operation != NULL) { + aws_ref_count_acquire(&operation->ref_count); + } + + return operation; +} diff --git a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_topic_alias.c b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_topic_alias.c index 928c7719283..4d0185ef11e 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_topic_alias.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_topic_alias.c @@ -7,6 +7,7 @@ #include <aws/common/lru_cache.h> #include <aws/common/string.h> +#include <aws/mqtt/private/client_impl_shared.h> #include <aws/mqtt/private/v5/mqtt5_utils.h> int aws_mqtt5_inbound_topic_alias_resolver_init( @@ -131,7 +132,7 @@ static struct aws_mqtt5_outbound_topic_alias_resolver *s_aws_mqtt5_outbound_topi struct aws_allocator *allocator); static struct aws_mqtt5_outbound_topic_alias_resolver *s_aws_mqtt5_outbound_topic_alias_resolver_lru_new( struct aws_allocator *allocator); -static struct aws_mqtt5_outbound_topic_alias_resolver *s_aws_mqtt5_outbound_topic_alias_resolver_user_new( +static struct aws_mqtt5_outbound_topic_alias_resolver *s_aws_mqtt5_outbound_topic_alias_resolver_manual_new( struct aws_allocator *allocator); struct aws_mqtt5_outbound_topic_alias_resolver *aws_mqtt5_outbound_topic_alias_resolver_new( @@ -139,8 +140,8 @@ struct aws_mqtt5_outbound_topic_alias_resolver *aws_mqtt5_outbound_topic_alias_r enum aws_mqtt5_client_outbound_topic_alias_behavior_type outbound_alias_behavior) { switch (aws_mqtt5_outbound_topic_alias_behavior_type_to_non_default(outbound_alias_behavior)) { - case AWS_MQTT5_COTABT_USER: - return s_aws_mqtt5_outbound_topic_alias_resolver_user_new(allocator); + case AWS_MQTT5_COTABT_MANUAL: + return s_aws_mqtt5_outbound_topic_alias_resolver_manual_new(allocator); case AWS_MQTT5_COTABT_LRU: return s_aws_mqtt5_outbound_topic_alias_resolver_lru_new(allocator); @@ -166,7 +167,7 @@ int aws_mqtt5_outbound_topic_alias_resolver_reset( uint16_t topic_alias_maximum) { if (resolver == NULL) { - return AWS_OP_ERR; + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } return (*resolver->vtable->reset_fn)(resolver, topic_alias_maximum); @@ -242,60 +243,60 @@ static struct aws_mqtt5_outbound_topic_alias_resolver *s_aws_mqtt5_outbound_topi } /* - * User resolver + * Manual resolver * - * User resolution implies the user is controlling the topic alias assignments, but we still want to validate their + * Manual resolution implies the user is controlling the topic alias assignments, but we still want to validate their * actions. In particular, we track the currently valid set of aliases (based on previous outbound publishes) * and only use an alias when the submitted publish is an exact match for the current assignment. */ -struct aws_mqtt5_outbound_topic_alias_resolver_user { +struct aws_mqtt5_outbound_topic_alias_resolver_manual { struct aws_mqtt5_outbound_topic_alias_resolver base; struct aws_array_list aliases; }; -static void s_cleanup_user_aliases(struct aws_mqtt5_outbound_topic_alias_resolver_user *user_resolver) { - for (size_t i = 0; i < aws_array_list_length(&user_resolver->aliases); ++i) { +static void s_cleanup_manual_aliases(struct aws_mqtt5_outbound_topic_alias_resolver_manual *manual_resolver) { + for (size_t i = 0; i < aws_array_list_length(&manual_resolver->aliases); ++i) { struct aws_string *alias = NULL; - aws_array_list_get_at(&user_resolver->aliases, &alias, i); + aws_array_list_get_at(&manual_resolver->aliases, &alias, i); aws_string_destroy(alias); } - aws_array_list_clean_up(&user_resolver->aliases); - AWS_ZERO_STRUCT(user_resolver->aliases); + aws_array_list_clean_up(&manual_resolver->aliases); + AWS_ZERO_STRUCT(manual_resolver->aliases); } -static void s_aws_mqtt5_outbound_topic_alias_resolver_user_destroy( +static void s_aws_mqtt5_outbound_topic_alias_resolver_manual_destroy( struct aws_mqtt5_outbound_topic_alias_resolver *resolver) { if (resolver == NULL) { return; } - struct aws_mqtt5_outbound_topic_alias_resolver_user *user_resolver = resolver->impl; - s_cleanup_user_aliases(user_resolver); + struct aws_mqtt5_outbound_topic_alias_resolver_manual *manual_resolver = resolver->impl; + s_cleanup_manual_aliases(manual_resolver); - aws_mem_release(resolver->allocator, user_resolver); + aws_mem_release(resolver->allocator, manual_resolver); } -static int s_aws_mqtt5_outbound_topic_alias_resolver_user_reset( +static int s_aws_mqtt5_outbound_topic_alias_resolver_manual_reset( struct aws_mqtt5_outbound_topic_alias_resolver *resolver, uint16_t topic_alias_maximum) { - struct aws_mqtt5_outbound_topic_alias_resolver_user *user_resolver = resolver->impl; - s_cleanup_user_aliases(user_resolver); + struct aws_mqtt5_outbound_topic_alias_resolver_manual *manual_resolver = resolver->impl; + s_cleanup_manual_aliases(manual_resolver); aws_array_list_init_dynamic( - &user_resolver->aliases, resolver->allocator, topic_alias_maximum, sizeof(struct aws_string *)); + &manual_resolver->aliases, resolver->allocator, topic_alias_maximum, sizeof(struct aws_string *)); for (size_t i = 0; i < topic_alias_maximum; ++i) { struct aws_string *invalid_alias = NULL; - aws_array_list_push_back(&user_resolver->aliases, &invalid_alias); + aws_array_list_push_back(&manual_resolver->aliases, &invalid_alias); } return AWS_OP_SUCCESS; } -static int s_aws_mqtt5_outbound_topic_alias_resolver_user_resolve_outbound_publish_fn( +static int s_aws_mqtt5_outbound_topic_alias_resolver_manual_resolve_outbound_publish_fn( struct aws_mqtt5_outbound_topic_alias_resolver *resolver, const struct aws_mqtt5_packet_publish_view *publish_view, uint16_t *topic_alias_out, @@ -315,15 +316,15 @@ static int s_aws_mqtt5_outbound_topic_alias_resolver_user_resolve_outbound_publi return aws_raise_error(AWS_ERROR_MQTT5_INVALID_OUTBOUND_TOPIC_ALIAS); } - struct aws_mqtt5_outbound_topic_alias_resolver_user *user_resolver = resolver->impl; + struct aws_mqtt5_outbound_topic_alias_resolver_manual *manual_resolver = resolver->impl; uint16_t user_alias_index = user_alias - 1; - if (user_alias_index >= aws_array_list_length(&user_resolver->aliases)) { + if (user_alias_index >= aws_array_list_length(&manual_resolver->aliases)) { /* should have been caught by dynamic publish validation */ return aws_raise_error(AWS_ERROR_MQTT5_INVALID_OUTBOUND_TOPIC_ALIAS); } struct aws_string *current_assignment = NULL; - aws_array_list_get_at(&user_resolver->aliases, ¤t_assignment, user_alias_index); + aws_array_list_get_at(&manual_resolver->aliases, ¤t_assignment, user_alias_index); *topic_alias_out = user_alias; @@ -345,25 +346,25 @@ static int s_aws_mqtt5_outbound_topic_alias_resolver_user_resolve_outbound_publi if (!can_use_alias) { aws_string_destroy(current_assignment); current_assignment = aws_string_new_from_cursor(resolver->allocator, &publish_view->topic); - aws_array_list_set_at(&user_resolver->aliases, ¤t_assignment, user_alias_index); + aws_array_list_set_at(&manual_resolver->aliases, ¤t_assignment, user_alias_index); } return AWS_OP_SUCCESS; } -static struct aws_mqtt5_outbound_topic_alias_resolver_vtable s_aws_mqtt5_outbound_topic_alias_resolver_user_vtable = { - .destroy_fn = s_aws_mqtt5_outbound_topic_alias_resolver_user_destroy, - .reset_fn = s_aws_mqtt5_outbound_topic_alias_resolver_user_reset, - .resolve_outbound_publish_fn = s_aws_mqtt5_outbound_topic_alias_resolver_user_resolve_outbound_publish_fn, +static struct aws_mqtt5_outbound_topic_alias_resolver_vtable s_aws_mqtt5_outbound_topic_alias_resolver_manual_vtable = { + .destroy_fn = s_aws_mqtt5_outbound_topic_alias_resolver_manual_destroy, + .reset_fn = s_aws_mqtt5_outbound_topic_alias_resolver_manual_reset, + .resolve_outbound_publish_fn = s_aws_mqtt5_outbound_topic_alias_resolver_manual_resolve_outbound_publish_fn, }; -static struct aws_mqtt5_outbound_topic_alias_resolver *s_aws_mqtt5_outbound_topic_alias_resolver_user_new( +static struct aws_mqtt5_outbound_topic_alias_resolver *s_aws_mqtt5_outbound_topic_alias_resolver_manual_new( struct aws_allocator *allocator) { - struct aws_mqtt5_outbound_topic_alias_resolver_user *resolver = - aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_outbound_topic_alias_resolver_user)); + struct aws_mqtt5_outbound_topic_alias_resolver_manual *resolver = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_outbound_topic_alias_resolver_manual)); resolver->base.allocator = allocator; - resolver->base.vtable = &s_aws_mqtt5_outbound_topic_alias_resolver_user_vtable; + resolver->base.vtable = &s_aws_mqtt5_outbound_topic_alias_resolver_manual_vtable; resolver->base.impl = resolver; aws_array_list_init_dynamic(&resolver->aliases, allocator, 0, sizeof(struct aws_string *)); @@ -450,13 +451,6 @@ static void s_destroy_assignment_value(void *value) { s_aws_topic_alias_assignment_destroy(value); } -static bool s_topic_hash_equality_fn(const void *a, const void *b) { - const struct aws_byte_cursor *a_cursor = a; - const struct aws_byte_cursor *b_cursor = b; - - return aws_byte_cursor_eq(a_cursor, b_cursor); -} - static int s_aws_mqtt5_outbound_topic_alias_resolver_lru_reset( struct aws_mqtt5_outbound_topic_alias_resolver *resolver, uint16_t topic_alias_maximum) { @@ -471,7 +465,7 @@ static int s_aws_mqtt5_outbound_topic_alias_resolver_lru_reset( lru_resolver->lru_cache = aws_cache_new_lru( lru_resolver->base.allocator, aws_hash_byte_cursor_ptr, - s_topic_hash_equality_fn, + aws_mqtt_byte_cursor_hash_equality, NULL, s_destroy_assignment_value, topic_alias_maximum); diff --git a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_utils.c b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_utils.c index 88de757c65f..79dc686bd9d 100644 --- a/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_utils.c +++ b/contrib/restricted/aws/aws-c-mqtt/source/v5/mqtt5_utils.c @@ -7,7 +7,6 @@ #include <aws/common/byte_buf.h> #include <aws/common/device_random.h> -#include <aws/common/encoding.h> #include <inttypes.h> uint8_t aws_mqtt5_compute_fixed_header_byte1(enum aws_mqtt5_packet_type packet_type, uint8_t flags) { @@ -290,7 +289,7 @@ enum aws_mqtt5_client_session_behavior_type aws_mqtt5_client_session_behavior_ty const char *aws_mqtt5_outbound_topic_alias_behavior_type_to_c_string( enum aws_mqtt5_client_outbound_topic_alias_behavior_type outbound_aliasing_behavior) { switch (aws_mqtt5_outbound_topic_alias_behavior_type_to_non_default(outbound_aliasing_behavior)) { - case AWS_MQTT5_COTABT_USER: + case AWS_MQTT5_COTABT_MANUAL: return "User-controlled outbound topic aliasing behavior"; case AWS_MQTT5_COTABT_LRU: return "LRU caching outbound topic aliasing behavior"; @@ -302,6 +301,13 @@ const char *aws_mqtt5_outbound_topic_alias_behavior_type_to_c_string( } } +bool aws_mqtt5_outbound_topic_alias_behavior_type_validate( + enum aws_mqtt5_client_outbound_topic_alias_behavior_type outbound_aliasing_behavior) { + + return outbound_aliasing_behavior >= AWS_MQTT5_COTABT_DEFAULT && + outbound_aliasing_behavior <= AWS_MQTT5_COTABT_DISABLED; +} + enum aws_mqtt5_client_outbound_topic_alias_behavior_type aws_mqtt5_outbound_topic_alias_behavior_type_to_non_default( enum aws_mqtt5_client_outbound_topic_alias_behavior_type outbound_aliasing_behavior) { if (outbound_aliasing_behavior == AWS_MQTT5_COTABT_DEFAULT) { @@ -323,6 +329,13 @@ const char *aws_mqtt5_inbound_topic_alias_behavior_type_to_c_string( } } +bool aws_mqtt5_inbound_topic_alias_behavior_type_validate( + enum aws_mqtt5_client_inbound_topic_alias_behavior_type inbound_aliasing_behavior) { + + return inbound_aliasing_behavior >= AWS_MQTT5_CITABT_DEFAULT && + inbound_aliasing_behavior <= AWS_MQTT5_CITABT_DISABLED; +} + enum aws_mqtt5_client_inbound_topic_alias_behavior_type aws_mqtt5_inbound_topic_alias_behavior_type_to_non_default( enum aws_mqtt5_client_inbound_topic_alias_behavior_type inbound_aliasing_behavior) { if (inbound_aliasing_behavior == AWS_MQTT5_CITABT_DEFAULT) { @@ -413,7 +426,7 @@ uint64_t aws_mqtt5_client_random_in_range(uint64_t from, uint64_t to) { static uint8_t s_aws_iot_core_rules_prefix[] = "$aws/rules/"; -struct aws_byte_cursor aws_mqtt5_topic_skip_aws_iot_rules_prefix(struct aws_byte_cursor topic_cursor) { +static struct aws_byte_cursor s_aws_mqtt5_topic_skip_aws_iot_rules_prefix(struct aws_byte_cursor topic_cursor) { size_t prefix_length = AWS_ARRAY_SIZE(s_aws_iot_core_rules_prefix) - 1; /* skip 0-terminator */ struct aws_byte_cursor rules_prefix = { @@ -454,47 +467,18 @@ struct aws_byte_cursor aws_mqtt5_topic_skip_aws_iot_rules_prefix(struct aws_byte return topic_cursor_copy; } -size_t aws_mqtt5_topic_get_segment_count(struct aws_byte_cursor topic_cursor) { - size_t segment_count = 0; - - struct aws_byte_cursor segment_cursor; - AWS_ZERO_STRUCT(segment_cursor); - - while (aws_byte_cursor_next_split(&topic_cursor, '/', &segment_cursor)) { - ++segment_count; - } - - return segment_count; -} - -bool aws_mqtt_is_valid_topic_filter_for_iot_core(struct aws_byte_cursor topic_filter_cursor) { - struct aws_byte_cursor post_rule_suffix = aws_mqtt5_topic_skip_aws_iot_rules_prefix(topic_filter_cursor); - return aws_mqtt5_topic_get_segment_count(post_rule_suffix) <= AWS_IOT_CORE_MAXIMUM_TOPIC_SEGMENTS; -} - -bool aws_mqtt_is_valid_topic_for_iot_core(struct aws_byte_cursor topic_cursor) { - struct aws_byte_cursor post_rule_suffix = aws_mqtt5_topic_skip_aws_iot_rules_prefix(topic_cursor); - if (aws_mqtt5_topic_get_segment_count(post_rule_suffix) > AWS_IOT_CORE_MAXIMUM_TOPIC_SEGMENTS) { - return false; - } - - return post_rule_suffix.len <= AWS_IOT_CORE_MAXIMUM_TOPIC_LENGTH; -} - static uint8_t s_shared_subscription_prefix[] = "$share"; static bool s_is_not_hash_or_plus(uint8_t byte) { return byte != '+' && byte != '#'; } -/* $share/{ShareName}/{filter} */ -bool aws_mqtt_is_topic_filter_shared_subscription(struct aws_byte_cursor topic_cursor) { - +static struct aws_byte_cursor s_aws_mqtt5_topic_skip_shared_prefix(struct aws_byte_cursor topic_cursor) { /* shared subscription filters must have an initial segment of "$share" */ struct aws_byte_cursor first_segment_cursor; AWS_ZERO_STRUCT(first_segment_cursor); if (!aws_byte_cursor_next_split(&topic_cursor, '/', &first_segment_cursor)) { - return false; + return topic_cursor; } struct aws_byte_cursor share_prefix_cursor = { @@ -503,7 +487,7 @@ bool aws_mqtt_is_topic_filter_shared_subscription(struct aws_byte_cursor topic_c }; if (!aws_byte_cursor_eq_ignore_case(&share_prefix_cursor, &first_segment_cursor)) { - return false; + return topic_cursor; } /* @@ -512,12 +496,12 @@ bool aws_mqtt_is_topic_filter_shared_subscription(struct aws_byte_cursor topic_c */ struct aws_byte_cursor second_segment_cursor = first_segment_cursor; if (!aws_byte_cursor_next_split(&topic_cursor, '/', &second_segment_cursor)) { - return false; + return topic_cursor; } if (second_segment_cursor.len == 0 || !aws_byte_cursor_satisfies_pred(&second_segment_cursor, s_is_not_hash_or_plus)) { - return false; + return topic_cursor; } /* @@ -527,48 +511,58 @@ bool aws_mqtt_is_topic_filter_shared_subscription(struct aws_byte_cursor topic_c size_t remaining_length = topic_cursor.ptr + topic_cursor.len - (second_segment_cursor.len + second_segment_cursor.ptr); if (remaining_length == 0) { - return false; + return topic_cursor; } aws_byte_cursor_advance(&remaining_cursor, topic_cursor.len - remaining_length + 1); - if (!aws_mqtt_is_valid_topic_filter(&remaining_cursor)) { - return false; - } + return remaining_cursor; +} - return true; +struct aws_byte_cursor aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(struct aws_byte_cursor topic_cursor) { + struct aws_byte_cursor skip_shared = s_aws_mqtt5_topic_skip_shared_prefix(topic_cursor); + struct aws_byte_cursor skip_rules = s_aws_mqtt5_topic_skip_aws_iot_rules_prefix(skip_shared); + + return skip_rules; } -/* UTF-8 encoded string validation respect to [MQTT-1.5.3-2]. */ -static int aws_mqtt5_utf8_decoder(uint32_t codepoint, void *user_data) { - (void)user_data; - /* U+0000 - A UTF-8 Encoded String MUST NOT include an encoding of the null character U+0000. [MQTT-1.5.4-2] - * U+0001..U+001F control characters are not valid - */ - if (AWS_UNLIKELY(codepoint <= 0x001F)) { - return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING); - } +size_t aws_mqtt5_topic_get_segment_count(struct aws_byte_cursor topic_cursor) { + size_t segment_count = 0; - /* U+007F..U+009F control characters are not valid */ - if (AWS_UNLIKELY((codepoint >= 0x007F) && (codepoint <= 0x009F))) { - return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING); - } + struct aws_byte_cursor segment_cursor; + AWS_ZERO_STRUCT(segment_cursor); - /* Unicode non-characters are not valid: https://www.unicode.org/faq/private_use.html#nonchar1 */ - if (AWS_UNLIKELY((codepoint & 0x00FFFF) >= 0x00FFFE)) { - return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING); + while (aws_byte_cursor_next_split(&topic_cursor, '/', &segment_cursor)) { + ++segment_count; } - if (AWS_UNLIKELY(codepoint >= 0xFDD0 && codepoint <= 0xFDEF)) { - return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING); + + return segment_count; +} + +bool aws_mqtt_is_valid_topic_filter_for_iot_core(struct aws_byte_cursor topic_filter_cursor) { + struct aws_byte_cursor post_rule_suffix = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(topic_filter_cursor); + return aws_mqtt5_topic_get_segment_count(post_rule_suffix) <= AWS_IOT_CORE_MAXIMUM_TOPIC_SEGMENTS; +} + +bool aws_mqtt_is_valid_topic_for_iot_core(struct aws_byte_cursor topic_cursor) { + struct aws_byte_cursor post_rule_suffix = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(topic_cursor); + if (aws_mqtt5_topic_get_segment_count(post_rule_suffix) > AWS_IOT_CORE_MAXIMUM_TOPIC_SEGMENTS) { + return false; } - return AWS_ERROR_SUCCESS; + return post_rule_suffix.len <= AWS_IOT_CORE_MAXIMUM_TOPIC_LENGTH; } -struct aws_utf8_decoder_options g_aws_mqtt5_utf8_decoder_options = { - .on_codepoint = aws_mqtt5_utf8_decoder, -}; +/* $share/{ShareName}/{filter} */ +bool aws_mqtt_is_topic_filter_shared_subscription(struct aws_byte_cursor topic_cursor) { + struct aws_byte_cursor remaining_cursor = s_aws_mqtt5_topic_skip_shared_prefix(topic_cursor); + if (remaining_cursor.len == topic_cursor.len) { + return false; + } -int aws_mqtt5_validate_utf8_text(struct aws_byte_cursor text) { - return aws_decode_utf8(text, &g_aws_mqtt5_utf8_decoder_options); + if (!aws_mqtt_is_valid_topic_filter(&remaining_cursor)) { + return false; + } + + return true; } diff --git a/contrib/restricted/aws/aws-c-mqtt/ya.make b/contrib/restricted/aws/aws-c-mqtt/ya.make index 7dd1d6ba7cf..8f460436aa3 100644 --- a/contrib/restricted/aws/aws-c-mqtt/ya.make +++ b/contrib/restricted/aws/aws-c-mqtt/ya.make @@ -6,9 +6,9 @@ LICENSE(Apache-2.0) LICENSE_TEXTS(.yandex_meta/licenses.list.txt) -VERSION(0.8.8) +VERSION(0.10.4) -ORIGINAL_SOURCE(https://github.com/awslabs/aws-c-mqtt/archive/v0.8.8.tar.gz) +ORIGINAL_SOURCE(https://github.com/awslabs/aws-c-mqtt/archive/v0.10.4.tar.gz) PEERDIR( contrib/restricted/aws/aws-c-common @@ -31,7 +31,6 @@ CFLAGS( -DAWS_HTTP_USE_IMPORT_EXPORT -DAWS_IO_USE_IMPORT_EXPORT -DAWS_MQTT_USE_IMPORT_EXPORT - -DAWS_MQTT_WITH_WEBSOCKETS -DAWS_USE_EPOLL -DHAVE_SYSCONF -DINTEL_NO_ITTNOTIFY_API @@ -46,8 +45,11 @@ ENDIF() SRCS( source/client.c source/client_channel_handler.c + source/client_impl_shared.c source/fixed_header.c source/mqtt.c + source/mqtt311_decoder.c + source/mqtt_subscription_set.c source/packets.c source/shared_constants.c source/topic_tree.c @@ -57,6 +59,7 @@ SRCS( source/v5/mqtt5_encoder.c source/v5/mqtt5_listener.c source/v5/mqtt5_options_storage.c + source/v5/mqtt5_to_mqtt3_adapter.c source/v5/mqtt5_topic_alias.c source/v5/mqtt5_types.c source/v5/mqtt5_utils.c |
