From b7de17b8f3cceea759d4e7525aeb71166be08912 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 29 Nov 2023 11:55:33 -0800 Subject: [PATCH 001/124] Checkpoint before context switch --- include/aws/mqtt/private/client_impl_shared.h | 19 + .../request-response/protocol_adapter.h | 96 +++++ source/client.c | 7 + source/client_impl_shared.c | 4 + source/request-response/protocol_adapter.c | 350 ++++++++++++++++++ source/v5/mqtt5_to_mqtt3_adapter.c | 7 + 6 files changed, 483 insertions(+) create mode 100644 include/aws/mqtt/private/request-response/protocol_adapter.h create mode 100644 source/request-response/protocol_adapter.c diff --git a/include/aws/mqtt/private/client_impl_shared.h b/include/aws/mqtt/private/client_impl_shared.h index d244cfe7..9f28894f 100644 --- a/include/aws/mqtt/private/client_impl_shared.h +++ b/include/aws/mqtt/private/client_impl_shared.h @@ -10,6 +10,20 @@ struct aws_mqtt_client_connection; +/* + * Internal enum that indicates what type of struct the underlying impl pointer actually is. We use this + * to safely interact with private APIs on the implementation or extract the adapted 5 client directly, as + * necessary. + */ +enum aws_mqtt311_impl_type { + + /* 311 connection impl can be cast to `struct aws_mqtt_client_connection_311_impl` */ + AWS_MQTT311_IT_311_CONNECTION_IMPL, + + /* 311 connection impl can be cast to `struct aws_mqtt_client_connection_5_impl`*/ + AWS_MQTT311_IT_5_ADAPTER_IMPL, +}; + struct aws_mqtt_client_connection_vtable { struct aws_mqtt_client_connection *(*acquire_fn)(void *impl); @@ -107,6 +121,8 @@ struct aws_mqtt_client_connection_vtable { void *userdata); int (*get_stats_fn)(void *impl, struct aws_mqtt_connection_operation_statistics *stats); + + enum aws_mqtt311_impl_type (*get_impl_type)(void *impl); }; struct aws_mqtt_client_connection { @@ -114,6 +130,9 @@ struct aws_mqtt_client_connection { void *impl; }; +AWS_MQTT_API enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type( + struct aws_mqtt_client_connection *connection); + 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); diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h new file mode 100644 index 00000000..7d7d9117 --- /dev/null +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -0,0 +1,96 @@ +#ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_PROTOCOL_ADAPTER_H +#define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_PROTOCOL_ADAPTER_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include + +struct aws_allocator; +struct aws_mqtt_client_connection; +struct aws_mqtt5_client; + +struct aws_protocol_adapter_subscribe_options { + struct aws_byte_cursor topic_filter; + uint32_t ack_timeout_seconds; +}; + +struct aws_protocol_adapter_unsubscribe_options { + struct aws_byte_cursor topic_filter; + uint32_t ack_timeout_seconds; +}; + +struct aws_protocol_adapter_publish_options { + struct aws_byte_cursor topic; + struct aws_byte_cursor payload; + + void (*completion_callback_fn)(int, void *); + void *user_data; + uint32_t ack_timeout_seconds; +}; + +enum aws_protocol_adapter_subscription_status_update { + AWS_PASS_ESTABLISHMENT_SUCCESS, + AWS_PASS_ESTABLISHMENT_FAILURE, + AWS_PASS_REMOVED +}; + +struct aws_protocol_adapter_subscription_status_update_event { + struct aws_byte_cursor topic_filter; + enum aws_protocol_adapter_subscription_status_update status_update; +}; + +struct aws_protocol_adapter_incoming_publish_event { + struct aws_byte_cursor topic; + struct aws_byte_cursor payload; +}; + +typedef void(aws_protocol_adapter_subscription_status_fn)(struct aws_protocol_adapter_subscription_status_update_event *update, void *user_data); +typedef void(aws_protocol_adapter_incoming_publish_fn)(struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data); +typedef void(aws_protocol_adapter_terminate_callback_fn)(void *user_data); + +struct aws_mqtt_protocol_adapter_options { + aws_protocol_adapter_subscription_status_fn *subscription_status_update_callback; + aws_protocol_adapter_incoming_publish_fn *incoming_publish_callback; + aws_protocol_adapter_terminate_callback_fn *terminate_callback; + + void *user_data; +}; + +struct aws_mqtt_protocol_adapter_vtable { + + void (*aws_mqtt_protocol_adapter_release_fn)(void *); + + int (*aws_mqtt_protocol_adapter_subscribe_fn)(void *, struct aws_protocol_adapter_subscribe_options *); + + int (*aws_mqtt_protocol_adapter_unsubscribe_fn)(void *, struct aws_protocol_adapter_unsubscribe_options *); + + int (*aws_mqtt_protocol_adapter_publish_fn)(void *, struct aws_protocol_adapter_publish_options *); +}; + +struct aws_mqtt_protocol_adapter { + const struct aws_mqtt_protocol_adapter_vtable *vtable; + void *impl; +}; + +AWS_EXTERN_C_BEGIN + +AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt_client_connection *connection); + +AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt5_client *client); + +AWS_MQTT_API void aws_mqtt_protocol_adapter_release(struct aws_mqtt_protocol_adapter *adapter); + +AWS_MQTT_API int aws_mqtt_protocol_adapter_subscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_subscribe_options *options); + +AWS_MQTT_API int aws_mqtt_protocol_adapter_unsubscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_unsubscribe_options *options); + +AWS_MQTT_API int aws_mqtt_protocol_adapter_publish(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_publish_options *options); + +AWS_EXTERN_C_END + +#endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_PROTOCOL_ADAPTER_H */ diff --git a/source/client.c b/source/client.c index c332c4da..42ab634c 100644 --- a/source/client.c +++ b/source/client.c @@ -3220,6 +3220,12 @@ static void s_aws_mqtt_client_connection_311_release(void *impl) { aws_ref_count_release(&connection->ref_count); } +enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_3_get_impl(void *impl) { + (void)impl; + + return AWS_MQTT311_IT_311_CONNECTION_IMPL; +} + 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, @@ -3243,6 +3249,7 @@ static struct aws_mqtt_client_connection_vtable s_aws_mqtt_client_connection_311 .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, + .get_impl_type = s_aws_mqtt_client_connection_3_get_impl, }; static struct aws_mqtt_client_connection_vtable *s_aws_mqtt_client_connection_311_vtable_ptr = diff --git a/source/client_impl_shared.c b/source/client_impl_shared.c index 6f65eb88..0f0e70a2 100644 --- a/source/client_impl_shared.c +++ b/source/client_impl_shared.c @@ -204,6 +204,10 @@ int aws_mqtt_client_connection_get_stats( return (*connection->vtable->get_stats_fn)(connection->impl, stats); } +enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type(struct aws_mqtt_client_connection *connection) { + return (*connection->vtable->get_impl_type)(connection->impl); +} + uint64_t aws_mqtt_hash_uint16_t(const void *item) { return *(uint16_t *)item; } diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c new file mode 100644 index 00000000..d29f705d --- /dev/null +++ b/source/request-response/protocol_adapter.c @@ -0,0 +1,350 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include +#include +#include +#include +#include + +struct aws_protocol_adapter_weak_ref { + struct aws_allocator *allocator; + struct aws_ref_count refcount; + void *referenced; +}; + +static void s_destroy_protocol_adapter_weak_ref(void *value) { + struct aws_protocol_adapter_weak_ref *weak_ref = value; + + aws_mem_release(weak_ref->allocator, weak_ref); +} + +struct aws_protocol_adapter_weak_ref *aws_protocol_adapter_weak_ref_new(struct aws_allocator *allocator, void *referenced) { + struct aws_protocol_adapter_weak_ref *weak_ref = aws_mem_calloc(allocator, 1, sizeof(struct aws_protocol_adapter_weak_ref)); + + aws_ref_count_init(&weak_ref->refcount, weak_ref, s_destroy_protocol_adapter_weak_ref); + weak_ref->allocator = allocator; + weak_ref->referenced = referenced; + + return weak_ref; +} + +struct aws_protocol_adapter_weak_ref *aws_protocol_adapter_weak_ref_acquire(struct aws_protocol_adapter_weak_ref *weak_ref) { + if (NULL != weak_ref) { + aws_ref_count_acquire(&weak_ref->refcount); + } + + return weak_ref; +} + +struct aws_protocol_adapter_weak_ref *aws_protocol_adapter_weak_ref_release(struct aws_protocol_adapter_weak_ref *weak_ref) { + if (NULL != weak_ref) { + aws_ref_count_release(&weak_ref->refcount); + } + + return NULL; +} + +/******************************************************************************************************************/ + +enum aws_protocol_adapter_subscription_status { + PASS_NONE, + PASS_SUBSCRIBING, + PASS_SUBSCRIBED, + PASS_UNSUBSCRIBING, +}; + +struct aws_mqtt_protocol_adapter_subscription { + struct aws_allocator *allocator; + + struct aws_byte_cursor topic_filter; + struct aws_byte_buf topic_filter_buf; + + enum aws_protocol_adapter_subscription_status status; +}; + +static struct aws_mqtt_protocol_adapter_subscription *s_aws_mqtt_protocol_adapter_subscription_new(struct aws_allocator *allocator, struct aws_byte_cursor topic_filter) { + struct aws_mqtt_protocol_adapter_subscription *subscription = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_subscription)); + + subscription->allocator = allocator; + aws_byte_buf_init_copy_from_cursor(&subscription->topic_filter_buf, allocator, topic_filter); + subscription->topic_filter = aws_byte_cursor_from_buf(&subscription->topic_filter_buf); + + return subscription; +} + +static void s_aws_mqtt_protocol_adapter_subscription_destroy(struct aws_mqtt_protocol_adapter_subscription *subscription) { + aws_byte_buf_clean_up(&subscription->topic_filter_buf); + aws_mem_release(subscription->allocator, subscription); +} + +struct aws_mqtt_protocol_adapter_subscription_set { + struct aws_allocator *allocator; + struct aws_hash_table subscriptions; // aws_byte_cursor * -> aws_mqtt_protocol_adapter_subscription * + + aws_protocol_adapter_subscription_status_fn *subscription_status_update_callback; + void *callback_user_data; +}; + +static int s_aws_mqtt_protocol_adapter_subscription_set_init(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options) { + subscription_set->allocator = allocator; + subscription_set->subscription_status_update_callback = options->subscription_status_update_callback; + subscription_set->callback_user_data = options->user_data; + + return aws_hash_table_init(&subscription_set->subscriptions, allocator, 0, aws_hash_byte_cursor_ptr, aws_mqtt_byte_cursor_hash_equality, NULL, NULL); +} + +static int s_aws_mqtt_protocol_adapter_subscription_set_subscription_clean_up(void *context, struct aws_hash_element *elem) { + struct aws_mqtt_protocol_adapter *adapter = context; + + struct aws_mqtt_protocol_adapter_subscription *subscription = elem->value; + + if (subscription->status != PASS_UNSUBSCRIBING) { + struct aws_protocol_adapter_unsubscribe_options options = { + .topic_filter = subscription->topic_filter, + }; + + aws_mqtt_protocol_adapter_unsubscribe(adapter, &options); + } + + s_aws_mqtt_protocol_adapter_subscription_destroy(subscription); + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; +} + +static void s_aws_mqtt_protocol_adapter_subscription_set_clean_up(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_mqtt_protocol_adapter *owner) { + struct aws_hash_table subscriptions; + AWS_ZERO_STRUCT(subscriptions); + + aws_hash_table_swap(&subscription_set->subscriptions, &subscriptions); + + aws_hash_table_foreach(&subscriptions, s_aws_mqtt_protocol_adapter_subscription_set_subscription_clean_up, owner); + + aws_hash_table_clean_up(&subscriptions); +} + +static void s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, bool success) { + (void)subscription_set; + (void)topic_filter; + (void)success; + + ??; +} + +static void s_aws_mqtt_protocol_adapter_subscription_set_update_subscription(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, enum aws_protocol_adapter_subscription_status status) { + (void)subscription_set; + (void)topic_filter; + (void)status; + + ??; +} + +static void s_aws_mqtt_protocol_adapter_subscription_set_create_or_update_subscription(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, enum aws_protocol_adapter_subscription_status status) { + (void)subscription_set; + (void)topic_filter; + (void)status; + + ??; +} + + +/******************************************************************************************************************/ + +struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt_client_connection *connection) { + (void)allocator; + (void)options; + (void)connection; + + return NULL; +} + +/******************************************************************************************************************/ + +struct aws_mqtt_protocol_adapter_5_impl { + struct aws_allocator *allocator; + struct aws_mqtt_protocol_adapter base; + struct aws_protocol_adapter_weak_ref *callback_ref; + struct aws_mqtt_protocol_adapter_options config; + + struct aws_event_loop *loop; + struct aws_mqtt5_client *client; + struct aws_mqtt5_listener *listener; + + struct aws_mqtt_protocol_adapter_subscription_set subscriptions; +}; + +static void s_aws_mqtt_protocol_adapter_5_release(void *impl) { + struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; + + aws_mqtt5_listener_release(adapter->listener); +} + +struct aws_mqtt_protocol_adapter_5_subscribe_data { + struct aws_allocator *allocator; + + struct aws_byte_buf *topic_filter; + struct aws_protocol_adapter_weak_ref *callback_ref; +}; + +static struct aws_mqtt_protocol_adapter_5_subscribe_data *aws_mqtt_protocol_adapter_5_subscribe_data_new(struct aws_allocator *allocator, struct aws_byte_cursor topic_filter, struct aws_protocol_adapter_weak_ref *callback_ref) { + struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_subscribe_data)); + + subscribe_data->allocator = allocator; + subscribe_data->callback_ref = aws_protocol_adapter_weak_ref_acquire(callback_ref); + aws_byte_buf_init_copy_from_cursor(&subscribe_data->topic_filter, allocator, topic_filter); + + return subscribe_data; +} + +static void aws_mqtt_protocol_adapter_5_subscribe_data_delete(struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data) { + aws_protocol_adapter_weak_ref_release(subscribe_data->callback_ref); + + aws_mem_release(subscribe_data->allocator, subscribe_data); +} + +static void s_protocol_adapter_5_subscribe_completion(const struct aws_mqtt5_packet_suback_view *suback, + int error_code, + void *complete_ctx) { + struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data = complete_ctx; + struct aws_mqtt_protocol_adapter_5_impl *adapter = subscribe_data->callback_ref->referenced; + + if (adapter == NULL) { + goto done; + } + + bool success = error_code == AWS_ERROR_SUCCESS && suback != NULL && suback->reason_code_count == 1 && suback->reason_codes[0] <= AWS_MQTT5_SARC_GRANTED_QOS_1; + s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion(&adapter->config, &adapter->subscriptions, aws_byte_cursor_from_buf(&subscribe_data->topic_filter), success); + +done: + + aws_mqtt_protocol_adapter_5_subscribe_data_delete(subscribe_data); +} + +int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { + struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; + + struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data = aws_mqtt_protocol_adapter_5_subscribe_data_new(adapter->allocator, options->topic_filter, adapter->callback_ref); + + struct aws_mqtt5_subscription_view subscription_view = { + .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, + .topic_filter = options->topic_filter, + }; + + struct aws_mqtt5_packet_subscribe_view subscribe_view = { + .subscriptions = &subscription_view, + .subscription_count = 1, + }; + + struct aws_mqtt5_subscribe_completion_options completion_options = { + .ack_timeout_seconds_override = options->ack_timeout_seconds, + .completion_callback = s_protocol_adapter_5_subscribe_completion, + .completion_user_data = subscribe_data, + }; + + if (aws_mqtt5_client_subscribe(adapter->client, &subscribe_view, &completion_options)) { + goto error; + } + + return AWS_OP_SUCCESS; + +error: + + aws_mqtt_protocol_adapter_5_subscribe_data_delete(subscribe_data); + + return AWS_OP_ERR; +} + +int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { + struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; + (void)adapter; + (void)options; + + return aws_raise_error(AWS_ERROR_UNIMPLEMENTED); +} + +int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { + struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; + (void)adapter; + (void)options; + + return aws_raise_error(AWS_ERROR_UNIMPLEMENTED); +} + +static bool s_protocol_adapter_mqtt5_listener_publish_received(const struct aws_mqtt5_packet_publish_view *publish, void *user_data) { + (void)publish; + (void)user_data; +} + +static void s_protocol_adapter_mqtt5_lifecycle_event_callback(const struct aws_mqtt5_client_lifecycle_event *event) { + (void)event; +} + +static void s_protocol_adapter_mqtt5_listener_termination_callback(void *user_data) { + struct aws_mqtt_protocol_adapter_5_impl *adapter = user_data; + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(adapter->client->loop)); + + aws_mqtt5_client_release(adapter->client); + + adapter->callback_ref->referenced = NULL; + aws_protocol_adapter_weak_ref_release(adapter->callback_ref); + + aws_mem_release(adapter->allocator, adapter); +} + +static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt5_vtable = { + .aws_mqtt_protocol_adapter_release_fn = s_aws_mqtt_protocol_adapter_5_release, + .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_5_subscribe, + .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_5_unsubscribe, + .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_5_publish, +}; + +struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt5_client *client) { + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(client->loop)); + + struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_impl)); + + adapter->allocator = allocator; + adapter->base.impl = adapter; + adapter->base.vtable = &s_protocol_adapter_mqtt5_vtable; + adapter->callback_ref = aws_protocol_adapter_weak_ref_new(allocator, adapter); + adapter->config = *options; + adapter->loop = client->loop; + adapter->client = aws_mqtt5_client_acquire(client); + + struct aws_mqtt5_listener_config listener_options = { + .client = client, + .listener_callbacks = { + .listener_publish_received_handler = s_protocol_adapter_mqtt5_listener_publish_received, + .listener_publish_received_handler_user_data = adapter, + .lifecycle_event_handler = s_protocol_adapter_mqtt5_lifecycle_event_callback, + .lifecycle_event_handler_user_data = adapter + }, + .termination_callback = s_protocol_adapter_mqtt5_listener_termination_callback, + .termination_callback_user_data = adapter, + }; + + adapter->listener = aws_mqtt5_listener_new(allocator, &listener_options); + + return adapter; +} + +void aws_mqtt_protocol_adapter_release(struct aws_mqtt_protocol_adapter *adapter) { + (*adapter->vtable->aws_mqtt_protocol_adapter_release_fn)(adapter->impl); +} + +int aws_mqtt_protocol_adapter_subscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_subscribe_options *options) { + return (*adapter->vtable->aws_mqtt_protocol_adapter_subscribe_fn)(adapter->impl, options); +} + +int aws_mqtt_protocol_adapter_unsubscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_unsubscribe_options *options) { + return (*adapter->vtable->aws_mqtt_protocol_adapter_unsubscribe_fn)(adapter->impl, options); +} + +int aws_mqtt_protocol_adapter_publish(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_publish_options *options) { + return (*adapter->vtable->aws_mqtt_protocol_adapter_publish_fn)(adapter->impl, options); +} \ No newline at end of file diff --git a/source/v5/mqtt5_to_mqtt3_adapter.c b/source/v5/mqtt5_to_mqtt3_adapter.c index e48ca3d3..c89da9c0 100644 --- a/source/v5/mqtt5_to_mqtt3_adapter.c +++ b/source/v5/mqtt5_to_mqtt3_adapter.c @@ -2854,6 +2854,12 @@ static uint16_t s_aws_mqtt_5_resubscribe_existing_topics( return 0; } +enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_5_get_impl(void *impl) { + (void)impl; + + return AWS_MQTT311_IT_5_ADAPTER_IMPL; +} + 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, @@ -2877,6 +2883,7 @@ static struct aws_mqtt_client_connection_vtable s_aws_mqtt_client_connection_5_v .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, + .get_impl_type = s_aws_mqtt_client_connection_5_get_impl, }; static struct aws_mqtt_client_connection_vtable *s_aws_mqtt_client_connection_5_vtable_ptr = From f501fa03e3701a27c3095f5fb15e5b8dce25ce13 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 11 Dec 2023 11:27:59 -0800 Subject: [PATCH 002/124] Checkpoint --- .../mqtt/private/request-response/weak_ref.h | 30 ++++++ source/request-response/protocol_adapter.c | 100 ++++++++---------- source/request-response/weak_ref.c | 54 ++++++++++ 3 files changed, 127 insertions(+), 57 deletions(-) create mode 100644 include/aws/mqtt/private/request-response/weak_ref.h create mode 100644 source/request-response/weak_ref.c diff --git a/include/aws/mqtt/private/request-response/weak_ref.h b/include/aws/mqtt/private/request-response/weak_ref.h new file mode 100644 index 00000000..34968874 --- /dev/null +++ b/include/aws/mqtt/private/request-response/weak_ref.h @@ -0,0 +1,30 @@ +#ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_WEAK_REF_H +#define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_WEAK_REF_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include + +struct aws_weak_ref; + +AWS_EXTERN_C_BEGIN + +AWS_MQTT_API struct aws_weak_ref *aws_weak_ref_new(struct aws_allocator *allocator, void *referenced); + +AWS_MQTT_API struct aws_weak_ref *aws_weak_ref_acquire(struct aws_weak_ref *weak_ref); + +AWS_MQTT_API struct aws_weak_ref *aws_weak_ref_release(struct aws_weak_ref *weak_ref); + +AWS_MQTT_API void *aws_weak_ref_get_reference(struct aws_weak_ref *weak_ref); + +AWS_MQTT_API void aws_weak_ref_zero_reference(struct aws_weak_ref *weak_ref); + +AWS_EXTERN_C_END + + +#endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_WEAK_REF_H */ diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index d29f705d..7c91bda2 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -7,50 +7,11 @@ #include #include +#include #include #include #include -struct aws_protocol_adapter_weak_ref { - struct aws_allocator *allocator; - struct aws_ref_count refcount; - void *referenced; -}; - -static void s_destroy_protocol_adapter_weak_ref(void *value) { - struct aws_protocol_adapter_weak_ref *weak_ref = value; - - aws_mem_release(weak_ref->allocator, weak_ref); -} - -struct aws_protocol_adapter_weak_ref *aws_protocol_adapter_weak_ref_new(struct aws_allocator *allocator, void *referenced) { - struct aws_protocol_adapter_weak_ref *weak_ref = aws_mem_calloc(allocator, 1, sizeof(struct aws_protocol_adapter_weak_ref)); - - aws_ref_count_init(&weak_ref->refcount, weak_ref, s_destroy_protocol_adapter_weak_ref); - weak_ref->allocator = allocator; - weak_ref->referenced = referenced; - - return weak_ref; -} - -struct aws_protocol_adapter_weak_ref *aws_protocol_adapter_weak_ref_acquire(struct aws_protocol_adapter_weak_ref *weak_ref) { - if (NULL != weak_ref) { - aws_ref_count_acquire(&weak_ref->refcount); - } - - return weak_ref; -} - -struct aws_protocol_adapter_weak_ref *aws_protocol_adapter_weak_ref_release(struct aws_protocol_adapter_weak_ref *weak_ref) { - if (NULL != weak_ref) { - aws_ref_count_release(&weak_ref->refcount); - } - - return NULL; -} - -/******************************************************************************************************************/ - enum aws_protocol_adapter_subscription_status { PASS_NONE, PASS_SUBSCRIBING, @@ -84,25 +45,27 @@ static void s_aws_mqtt_protocol_adapter_subscription_destroy(struct aws_mqtt_pro struct aws_mqtt_protocol_adapter_subscription_set { struct aws_allocator *allocator; + struct aws_mqtt_protocol_adapter *owner; // not an acquired reference due to the parent-child relationship struct aws_hash_table subscriptions; // aws_byte_cursor * -> aws_mqtt_protocol_adapter_subscription * aws_protocol_adapter_subscription_status_fn *subscription_status_update_callback; void *callback_user_data; }; -static int s_aws_mqtt_protocol_adapter_subscription_set_init(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options) { +static int s_aws_mqtt_protocol_adapter_subscription_set_init(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *owner, struct aws_mqtt_protocol_adapter_options *options) { subscription_set->allocator = allocator; + subscription_set->owner = owner; subscription_set->subscription_status_update_callback = options->subscription_status_update_callback; subscription_set->callback_user_data = options->user_data; return aws_hash_table_init(&subscription_set->subscriptions, allocator, 0, aws_hash_byte_cursor_ptr, aws_mqtt_byte_cursor_hash_equality, NULL, NULL); } -static int s_aws_mqtt_protocol_adapter_subscription_set_subscription_clean_up(void *context, struct aws_hash_element *elem) { - struct aws_mqtt_protocol_adapter *adapter = context; +static int s_aws_mqtt_protocol_adapter_subscription_set_subscription_destroy(void *context, struct aws_hash_element *elem) { + struct aws_mqtt_protocol_adapter_subscription_set *subscription_set = context; + struct aws_mqtt_protocol_adapter *adapter = subscription_set->owner; struct aws_mqtt_protocol_adapter_subscription *subscription = elem->value; - if (subscription->status != PASS_UNSUBSCRIBING) { struct aws_protocol_adapter_unsubscribe_options options = { .topic_filter = subscription->topic_filter, @@ -116,23 +79,46 @@ static int s_aws_mqtt_protocol_adapter_subscription_set_subscription_clean_up(vo return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; } -static void s_aws_mqtt_protocol_adapter_subscription_set_clean_up(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_mqtt_protocol_adapter *owner) { +static void s_aws_mqtt_protocol_adapter_subscription_set_clean_up(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set) { struct aws_hash_table subscriptions; AWS_ZERO_STRUCT(subscriptions); aws_hash_table_swap(&subscription_set->subscriptions, &subscriptions); - aws_hash_table_foreach(&subscriptions, s_aws_mqtt_protocol_adapter_subscription_set_subscription_clean_up, owner); + aws_hash_table_foreach(&subscriptions, s_aws_mqtt_protocol_adapter_subscription_set_subscription_destroy, subscription_set); aws_hash_table_clean_up(&subscriptions); } +/* + * On subscribe success: if there's not an entry, is this possible? No because we're called only by a function that checks for adapter weak->strong first, so the adapter exists and we don't allow subscription removal without an unsubscribe complete and we don't allow the subscribe until the unsubscribe has completed. But what + * if + * On subscribe success: if there's an entry, transition | subscribing -> subscribed, send an update + * On subscribe failure: if there's not an entry, is this possible? + * On subscribe failure: if there's an entry, transition -> unsubscribing, send an update + * + * Should we just blindly add if the adapter exists? Yes: simplest. No: represents undefined behavior if it shouldn't be happening + * + * In the design we said that the subscription set is just a dumb reflection of the ordered sequence of operations + * from the rr client which implies we should just create_or_update. The only time we don't want to create_or_update + * is if we're in/post destruction but then there's no adapter and we early out + */ static void s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, bool success) { - (void)subscription_set; - (void)topic_filter; - (void)success; + if (!success) { + struct aws_protocol_adapter_unsubscribe_options options = { + .topic_filter = topic_filter, + }; + + aws_mqtt_protocol_adapter_unsubscribe(subscription_set->owner, &options); + } + + struct aws_hash_element *hash_element = NULL; + if (!aws_hash_table_find(&subscription_set->subscriptions, &topic_filter, &hash_element) || hash_element == NULL) { + return; + } + + struct aws_mqtt_protocol_adapter_subscription *subscription = hash_element->value; - ??; } static void s_aws_mqtt_protocol_adapter_subscription_set_update_subscription(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, enum aws_protocol_adapter_subscription_status status) { @@ -194,14 +180,14 @@ static struct aws_mqtt_protocol_adapter_5_subscribe_data *aws_mqtt_protocol_adap struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_subscribe_data)); subscribe_data->allocator = allocator; - subscribe_data->callback_ref = aws_protocol_adapter_weak_ref_acquire(callback_ref); + subscribe_data->callback_ref = aws_weak_ref_acquire(callback_ref); aws_byte_buf_init_copy_from_cursor(&subscribe_data->topic_filter, allocator, topic_filter); return subscribe_data; } static void aws_mqtt_protocol_adapter_5_subscribe_data_delete(struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data) { - aws_protocol_adapter_weak_ref_release(subscribe_data->callback_ref); + aws_weak_ref_release(subscribe_data->callback_ref); aws_mem_release(subscribe_data->allocator, subscribe_data); } @@ -210,14 +196,14 @@ static void s_protocol_adapter_5_subscribe_completion(const struct aws_mqtt5_pac int error_code, void *complete_ctx) { struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data = complete_ctx; - struct aws_mqtt_protocol_adapter_5_impl *adapter = subscribe_data->callback_ref->referenced; + struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(subscribe_data->callback_ref); if (adapter == NULL) { goto done; } bool success = error_code == AWS_ERROR_SUCCESS && suback != NULL && suback->reason_code_count == 1 && suback->reason_codes[0] <= AWS_MQTT5_SARC_GRANTED_QOS_1; - s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion(&adapter->config, &adapter->subscriptions, aws_byte_cursor_from_buf(&subscribe_data->topic_filter), success); + s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion(&adapter->subscriptions, aws_byte_cursor_from_buf(&subscribe_data->topic_filter), success); done: @@ -290,8 +276,8 @@ static void s_protocol_adapter_mqtt5_listener_termination_callback(void *user_da aws_mqtt5_client_release(adapter->client); - adapter->callback_ref->referenced = NULL; - aws_protocol_adapter_weak_ref_release(adapter->callback_ref); + aws_weak_ref_zero_reference(adapter->callback_ref); + aws_weak_ref_release(adapter->callback_ref); aws_mem_release(adapter->allocator, adapter); } @@ -311,7 +297,7 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aw adapter->allocator = allocator; adapter->base.impl = adapter; adapter->base.vtable = &s_protocol_adapter_mqtt5_vtable; - adapter->callback_ref = aws_protocol_adapter_weak_ref_new(allocator, adapter); + adapter->callback_ref = aws_weak_ref_new(allocator, adapter); adapter->config = *options; adapter->loop = client->loop; adapter->client = aws_mqtt5_client_acquire(client); diff --git a/source/request-response/weak_ref.c b/source/request-response/weak_ref.c new file mode 100644 index 00000000..e3e5009c --- /dev/null +++ b/source/request-response/weak_ref.c @@ -0,0 +1,54 @@ +/** +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0. +*/ + +#include + +#include + +struct aws_weak_ref { + struct aws_allocator *allocator; + struct aws_ref_count refcount; + void *referenced; +}; + +static void s_destroy_weak_ref(void *value) { + struct aws_weak_ref *weak_ref = value; + + aws_mem_release(weak_ref->allocator, weak_ref); +} + +struct aws_weak_ref *aws_weak_ref_new(struct aws_allocator *allocator, void *referenced) { + struct aws_weak_ref *weak_ref = aws_mem_calloc(allocator, 1, sizeof(struct aws_weak_ref)); + + aws_ref_count_init(&weak_ref->refcount, weak_ref, s_destroy_weak_ref); + weak_ref->allocator = allocator; + weak_ref->referenced = referenced; + + return weak_ref; +} + +struct aws_weak_ref *aws_weak_ref_acquire(struct aws_weak_ref *weak_ref) { + if (NULL != weak_ref) { + aws_ref_count_acquire(&weak_ref->refcount); + } + + return weak_ref; +} + +struct aws_weak_ref *aws_weak_ref_release(struct aws_weak_ref *weak_ref) { + if (NULL != weak_ref) { + aws_ref_count_release(&weak_ref->refcount); + } + + return NULL; +} + +void *aws_weak_ref_get_reference(struct aws_weak_ref *weak_ref) { + return weak_ref->referenced; +} + +void aws_weak_ref_zero_reference(struct aws_weak_ref *weak_ref) { + weak_ref->referenced = NULL; +} \ No newline at end of file From 7a1db2657146dc004484d68f017633061e4c8763 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 17 Jan 2024 07:03:00 -0800 Subject: [PATCH 003/124] Sync point --- .../request-response/protocol_adapter.h | 4 +- source/request-response/protocol_adapter.c | 101 +++++++++++++----- 2 files changed, 79 insertions(+), 26 deletions(-) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 7d7d9117..90c4f0c4 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -63,7 +63,7 @@ struct aws_mqtt_protocol_adapter_options { struct aws_mqtt_protocol_adapter_vtable { - void (*aws_mqtt_protocol_adapter_release_fn)(void *); + void (*aws_mqtt_protocol_adapter_delete_fn)(void *); int (*aws_mqtt_protocol_adapter_subscribe_fn)(void *, struct aws_protocol_adapter_subscribe_options *); @@ -83,7 +83,7 @@ AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_fro AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt5_client *client); -AWS_MQTT_API void aws_mqtt_protocol_adapter_release(struct aws_mqtt_protocol_adapter *adapter); +AWS_MQTT_API void aws_mqtt_protocol_adapter_delete(struct aws_mqtt_protocol_adapter *adapter); AWS_MQTT_API int aws_mqtt_protocol_adapter_subscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_subscribe_options *options); diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 7c91bda2..f3494c86 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -43,6 +43,8 @@ static void s_aws_mqtt_protocol_adapter_subscription_destroy(struct aws_mqtt_pro aws_mem_release(subscription->allocator, subscription); } +/******************************************************************************************************************/ + struct aws_mqtt_protocol_adapter_subscription_set { struct aws_allocator *allocator; struct aws_mqtt_protocol_adapter *owner; // not an acquired reference due to the parent-child relationship @@ -90,6 +92,42 @@ static void s_aws_mqtt_protocol_adapter_subscription_set_clean_up(struct aws_mqt aws_hash_table_clean_up(&subscriptions); } +static void s_aws_mqtt_protocol_adapter_subscription_set_update_subscription(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, enum aws_protocol_adapter_subscription_status status) { + (void)subscription_set; + (void)topic_filter; + (void)status; + + // TODO +} + +static void s_aws_mqtt_protocol_adapter_subscription_set_create_or_update_subscription(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, enum aws_protocol_adapter_subscription_status status) { + (void)subscription_set; + (void)topic_filter; + (void)status; + + // TODO +} + +/* + * New API contract + * + * Invariant 1: Subscribe is only called from the RR subscription manager when going from 0 to 1 pending operations + * Invariant 2: Unsubscribe is only called from the RR subscription manager when there are 0 pending operations, not + * necessarily on the exact transition to zero though. + * + * Entries are not tracked with the exception of eventstream impl which needs the stream handles to close. + * A subscribe failure should not trigger an unsubscribe, only notify the status callback. + * Subscription event callback should be {subscribe_success, subscribe_failure, unsubscribe_success, unsubscribe_failure}. + * The sub manager is responsible for calling Unsubscribe on all its entries when shutting down (before releasing + * hold of the adapter). + * + * How do we know not to retry unsubscribe failures because a subscribe came in? Well, we don't retry failures; let + * the manager make that decision. Only retry (maybe) if the manager is gone (ie failure against a zeroed weak ref). + * + * On subscribe failures with zeroed weak ref, trust that an Unsubscribe was sent that will resolve later and let it + * decide what to do. + */ + /* * On subscribe success: if there's not an entry, is this possible? No because we're called only by a function that checks for adapter weak->strong first, so the adapter exists and we don't allow subscription removal without an unsubscribe complete and we don't allow the subscribe until the unsubscribe has completed. But what * if @@ -104,7 +142,9 @@ static void s_aws_mqtt_protocol_adapter_subscription_set_clean_up(struct aws_mqt * is if we're in/post destruction but then there's no adapter and we early out */ static void s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, bool success) { - if (!success) { + if (success) { + s_aws_mqtt_protocol_adapter_subscription_set_update_subscription(subscription_set, topic_filter, PASS_SUBSCRIBED); + } else { struct aws_protocol_adapter_unsubscribe_options options = { .topic_filter = topic_filter, }; @@ -112,32 +152,30 @@ static void s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion aws_mqtt_protocol_adapter_unsubscribe(subscription_set->owner, &options); } + + struct aws_hash_element *hash_element = NULL; if (!aws_hash_table_find(&subscription_set->subscriptions, &topic_filter, &hash_element) || hash_element == NULL) { return; } struct aws_mqtt_protocol_adapter_subscription *subscription = hash_element->value; + AWS_FATAL_ASSERT(subscription != NULL); -} - -static void s_aws_mqtt_protocol_adapter_subscription_set_update_subscription(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, enum aws_protocol_adapter_subscription_status status) { - (void)subscription_set; - (void)topic_filter; - (void)status; + switch (subscription->status) { + case PASS_SUBSCRIBING: { + if (success) { + subscription->status = PASS_SUBSCRIBED; + } + } - ??; -} - -static void s_aws_mqtt_protocol_adapter_subscription_set_create_or_update_subscription(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, enum aws_protocol_adapter_subscription_status status) { - (void)subscription_set; - (void)topic_filter; - (void)status; + default: + break; + } - ??; + // TODO } - /******************************************************************************************************************/ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt_client_connection *connection) { @@ -145,6 +183,7 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311(struct (void)options; (void)connection; + // TODO return NULL; } @@ -163,16 +202,18 @@ struct aws_mqtt_protocol_adapter_5_impl { struct aws_mqtt_protocol_adapter_subscription_set subscriptions; }; -static void s_aws_mqtt_protocol_adapter_5_release(void *impl) { +static void s_aws_mqtt_protocol_adapter_5_delete(void *impl) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; aws_mqtt5_listener_release(adapter->listener); } +/* Subscribe */ + struct aws_mqtt_protocol_adapter_5_subscribe_data { struct aws_allocator *allocator; - struct aws_byte_buf *topic_filter; + struct aws_byte_buf topic_filter; struct aws_protocol_adapter_weak_ref *callback_ref; }; @@ -188,6 +229,7 @@ static struct aws_mqtt_protocol_adapter_5_subscribe_data *aws_mqtt_protocol_adap static void aws_mqtt_protocol_adapter_5_subscribe_data_delete(struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data) { aws_weak_ref_release(subscribe_data->callback_ref); + aws_byte_buf_clean_up(&subscribe_data->topic_filter); aws_mem_release(subscribe_data->allocator, subscribe_data); } @@ -202,7 +244,7 @@ static void s_protocol_adapter_5_subscribe_completion(const struct aws_mqtt5_pac goto done; } - bool success = error_code == AWS_ERROR_SUCCESS && suback != NULL && suback->reason_code_count == 1 && suback->reason_codes[0] <= AWS_MQTT5_SARC_GRANTED_QOS_1; + bool success = error_code == AWS_ERROR_SUCCESS && suback != NULL && suback->reason_code_count == 1 && suback->reason_codes[0] <= AWS_MQTT5_SARC_GRANTED_QOS_2; s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion(&adapter->subscriptions, aws_byte_cursor_from_buf(&subscribe_data->topic_filter), success); done: @@ -244,6 +286,8 @@ int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adap return AWS_OP_ERR; } +/* Unsubscribe */ + int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; (void)adapter; @@ -252,6 +296,8 @@ int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_ad return aws_raise_error(AWS_ERROR_UNIMPLEMENTED); } +/* Publish */ + int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; (void)adapter; @@ -274,16 +320,23 @@ static void s_protocol_adapter_mqtt5_listener_termination_callback(void *user_da AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(adapter->client->loop)); - aws_mqtt5_client_release(adapter->client); - aws_weak_ref_zero_reference(adapter->callback_ref); aws_weak_ref_release(adapter->callback_ref); + aws_mqtt5_client_release(adapter->client); + + aws_protocol_adapter_terminate_callback_fn *terminate_callback = adapter->config.terminate_callback; + void *terminate_user_data = adapter->config.user_data; + aws_mem_release(adapter->allocator, adapter); + + if (terminate_callback) { + (*terminate_callback)(terminate_user_data); + } } static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt5_vtable = { - .aws_mqtt_protocol_adapter_release_fn = s_aws_mqtt_protocol_adapter_5_release, + .aws_mqtt_protocol_adapter_delete_fn = s_aws_mqtt_protocol_adapter_5_delete, .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_5_subscribe, .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_5_unsubscribe, .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_5_publish, @@ -319,8 +372,8 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aw return adapter; } -void aws_mqtt_protocol_adapter_release(struct aws_mqtt_protocol_adapter *adapter) { - (*adapter->vtable->aws_mqtt_protocol_adapter_release_fn)(adapter->impl); +void aws_mqtt_protocol_adapter_delete(struct aws_mqtt_protocol_adapter *adapter) { + (*adapter->vtable->aws_mqtt_protocol_adapter_delete_fn)(adapter->impl); } int aws_mqtt_protocol_adapter_subscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_subscribe_options *options) { From 013168bc598d57f345ea76eae8503645aca68194 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 17 Jan 2024 17:26:29 -0800 Subject: [PATCH 004/124] Checkpoint --- .../request-response/protocol_adapter.h | 31 +- source/request-response/protocol_adapter.c | 340 +++++++++--------- tests/CMakeLists.txt | 5 + tests/v5/mqtt5_client_tests.c | 28 -- tests/v5/mqtt5_testing_utils.c | 26 ++ tests/v5/mqtt5_testing_utils.h | 5 + .../request_response_protocol_adapter_tests.c | 133 +++++++ 7 files changed, 363 insertions(+), 205 deletions(-) create mode 100644 tests/v5/request_response_protocol_adapter_tests.c diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 90c4f0c4..3cf39a07 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -28,20 +28,21 @@ struct aws_protocol_adapter_publish_options { struct aws_byte_cursor topic; struct aws_byte_cursor payload; - void (*completion_callback_fn)(int, void *); + void (*completion_callback_fn)(bool, void *); void *user_data; uint32_t ack_timeout_seconds; }; -enum aws_protocol_adapter_subscription_status_update { - AWS_PASS_ESTABLISHMENT_SUCCESS, - AWS_PASS_ESTABLISHMENT_FAILURE, - AWS_PASS_REMOVED +enum aws_protocol_adapter_subscription_event_type { + AWS_PASET_SUBSCRIBE_SUCCESS, + AWS_PASET_SUBSCRIBE_FAILURE, + AWS_PASET_UNSUBSCRIBE_SUCCESS, + AWS_PASET_UNSUBSCRIBE_FAILURE, }; -struct aws_protocol_adapter_subscription_status_update_event { +struct aws_protocol_adapter_subscription_event { struct aws_byte_cursor topic_filter; - enum aws_protocol_adapter_subscription_status_update status_update; + enum aws_protocol_adapter_subscription_event_type event_type; }; struct aws_protocol_adapter_incoming_publish_event { @@ -49,14 +50,26 @@ struct aws_protocol_adapter_incoming_publish_event { struct aws_byte_cursor payload; }; -typedef void(aws_protocol_adapter_subscription_status_fn)(struct aws_protocol_adapter_subscription_status_update_event *update, void *user_data); +enum aws_protocol_adapter_connection_event_type { + AWS_PACET_OFFLINE, + AWS_PACET_ONLINE, +}; + +struct aws_protocol_adapter_connection_event { + enum aws_protocol_adapter_connection_event_type event_type; + bool rejoined_session; +}; + +typedef void(aws_protocol_adapter_subscription_event_fn)(struct aws_protocol_adapter_subscription_event *event, void *user_data); typedef void(aws_protocol_adapter_incoming_publish_fn)(struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data); typedef void(aws_protocol_adapter_terminate_callback_fn)(void *user_data); +typedef void(aws_protocol_adapter_connection_event_fn)(struct aws_protocol_adapter_connection_event *event, void *user_data); struct aws_mqtt_protocol_adapter_options { - aws_protocol_adapter_subscription_status_fn *subscription_status_update_callback; + aws_protocol_adapter_subscription_event_fn *subscription_event_callback; aws_protocol_adapter_incoming_publish_fn *incoming_publish_callback; aws_protocol_adapter_terminate_callback_fn *terminate_callback; + aws_protocol_adapter_connection_event_fn *connection_event_callback; void *user_data; }; diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index f3494c86..4aae75cd 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -12,102 +12,6 @@ #include #include -enum aws_protocol_adapter_subscription_status { - PASS_NONE, - PASS_SUBSCRIBING, - PASS_SUBSCRIBED, - PASS_UNSUBSCRIBING, -}; - -struct aws_mqtt_protocol_adapter_subscription { - struct aws_allocator *allocator; - - struct aws_byte_cursor topic_filter; - struct aws_byte_buf topic_filter_buf; - - enum aws_protocol_adapter_subscription_status status; -}; - -static struct aws_mqtt_protocol_adapter_subscription *s_aws_mqtt_protocol_adapter_subscription_new(struct aws_allocator *allocator, struct aws_byte_cursor topic_filter) { - struct aws_mqtt_protocol_adapter_subscription *subscription = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_subscription)); - - subscription->allocator = allocator; - aws_byte_buf_init_copy_from_cursor(&subscription->topic_filter_buf, allocator, topic_filter); - subscription->topic_filter = aws_byte_cursor_from_buf(&subscription->topic_filter_buf); - - return subscription; -} - -static void s_aws_mqtt_protocol_adapter_subscription_destroy(struct aws_mqtt_protocol_adapter_subscription *subscription) { - aws_byte_buf_clean_up(&subscription->topic_filter_buf); - aws_mem_release(subscription->allocator, subscription); -} - -/******************************************************************************************************************/ - -struct aws_mqtt_protocol_adapter_subscription_set { - struct aws_allocator *allocator; - struct aws_mqtt_protocol_adapter *owner; // not an acquired reference due to the parent-child relationship - struct aws_hash_table subscriptions; // aws_byte_cursor * -> aws_mqtt_protocol_adapter_subscription * - - aws_protocol_adapter_subscription_status_fn *subscription_status_update_callback; - void *callback_user_data; -}; - -static int s_aws_mqtt_protocol_adapter_subscription_set_init(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *owner, struct aws_mqtt_protocol_adapter_options *options) { - subscription_set->allocator = allocator; - subscription_set->owner = owner; - subscription_set->subscription_status_update_callback = options->subscription_status_update_callback; - subscription_set->callback_user_data = options->user_data; - - return aws_hash_table_init(&subscription_set->subscriptions, allocator, 0, aws_hash_byte_cursor_ptr, aws_mqtt_byte_cursor_hash_equality, NULL, NULL); -} - -static int s_aws_mqtt_protocol_adapter_subscription_set_subscription_destroy(void *context, struct aws_hash_element *elem) { - struct aws_mqtt_protocol_adapter_subscription_set *subscription_set = context; - struct aws_mqtt_protocol_adapter *adapter = subscription_set->owner; - - struct aws_mqtt_protocol_adapter_subscription *subscription = elem->value; - if (subscription->status != PASS_UNSUBSCRIBING) { - struct aws_protocol_adapter_unsubscribe_options options = { - .topic_filter = subscription->topic_filter, - }; - - aws_mqtt_protocol_adapter_unsubscribe(adapter, &options); - } - - s_aws_mqtt_protocol_adapter_subscription_destroy(subscription); - - return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; -} - -static void s_aws_mqtt_protocol_adapter_subscription_set_clean_up(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set) { - struct aws_hash_table subscriptions; - AWS_ZERO_STRUCT(subscriptions); - - aws_hash_table_swap(&subscription_set->subscriptions, &subscriptions); - - aws_hash_table_foreach(&subscriptions, s_aws_mqtt_protocol_adapter_subscription_set_subscription_destroy, subscription_set); - - aws_hash_table_clean_up(&subscriptions); -} - -static void s_aws_mqtt_protocol_adapter_subscription_set_update_subscription(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, enum aws_protocol_adapter_subscription_status status) { - (void)subscription_set; - (void)topic_filter; - (void)status; - - // TODO -} - -static void s_aws_mqtt_protocol_adapter_subscription_set_create_or_update_subscription(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, enum aws_protocol_adapter_subscription_status status) { - (void)subscription_set; - (void)topic_filter; - (void)status; - - // TODO -} - /* * New API contract * @@ -122,61 +26,13 @@ static void s_aws_mqtt_protocol_adapter_subscription_set_create_or_update_subscr * hold of the adapter). * * How do we know not to retry unsubscribe failures because a subscribe came in? Well, we don't retry failures; let - * the manager make that decision. Only retry (maybe) if the manager is gone (ie failure against a zeroed weak ref). + * the manager make that decision. No retry when the weak ref is zeroed either. The potential for things to go wrong + * is worse than the potential of a subscription "leaking." * * On subscribe failures with zeroed weak ref, trust that an Unsubscribe was sent that will resolve later and let it * decide what to do. */ -/* - * On subscribe success: if there's not an entry, is this possible? No because we're called only by a function that checks for adapter weak->strong first, so the adapter exists and we don't allow subscription removal without an unsubscribe complete and we don't allow the subscribe until the unsubscribe has completed. But what - * if - * On subscribe success: if there's an entry, transition | subscribing -> subscribed, send an update - * On subscribe failure: if there's not an entry, is this possible? - * On subscribe failure: if there's an entry, transition -> unsubscribing, send an update - * - * Should we just blindly add if the adapter exists? Yes: simplest. No: represents undefined behavior if it shouldn't be happening - * - * In the design we said that the subscription set is just a dumb reflection of the ordered sequence of operations - * from the rr client which implies we should just create_or_update. The only time we don't want to create_or_update - * is if we're in/post destruction but then there's no adapter and we early out - */ -static void s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion(struct aws_mqtt_protocol_adapter_subscription_set *subscription_set, struct aws_byte_cursor topic_filter, bool success) { - if (success) { - s_aws_mqtt_protocol_adapter_subscription_set_update_subscription(subscription_set, topic_filter, PASS_SUBSCRIBED); - } else { - struct aws_protocol_adapter_unsubscribe_options options = { - .topic_filter = topic_filter, - }; - - aws_mqtt_protocol_adapter_unsubscribe(subscription_set->owner, &options); - } - - - - struct aws_hash_element *hash_element = NULL; - if (!aws_hash_table_find(&subscription_set->subscriptions, &topic_filter, &hash_element) || hash_element == NULL) { - return; - } - - struct aws_mqtt_protocol_adapter_subscription *subscription = hash_element->value; - AWS_FATAL_ASSERT(subscription != NULL); - - switch (subscription->status) { - case PASS_SUBSCRIBING: { - if (success) { - subscription->status = PASS_SUBSCRIBED; - } - } - - default: - break; - } - - // TODO -} - -/******************************************************************************************************************/ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt_client_connection *connection) { (void)allocator; @@ -198,27 +54,25 @@ struct aws_mqtt_protocol_adapter_5_impl { struct aws_event_loop *loop; struct aws_mqtt5_client *client; struct aws_mqtt5_listener *listener; - - struct aws_mqtt_protocol_adapter_subscription_set subscriptions; }; static void s_aws_mqtt_protocol_adapter_5_delete(void *impl) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; + // all the real cleanup is done in the listener termination callback aws_mqtt5_listener_release(adapter->listener); } -/* Subscribe */ - -struct aws_mqtt_protocol_adapter_5_subscribe_data { +// used by both subscribe and unsubscribe +struct aws_mqtt_protocol_adapter_5_subscription_op_data { struct aws_allocator *allocator; struct aws_byte_buf topic_filter; struct aws_protocol_adapter_weak_ref *callback_ref; }; -static struct aws_mqtt_protocol_adapter_5_subscribe_data *aws_mqtt_protocol_adapter_5_subscribe_data_new(struct aws_allocator *allocator, struct aws_byte_cursor topic_filter, struct aws_protocol_adapter_weak_ref *callback_ref) { - struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_subscribe_data)); +static struct aws_mqtt_protocol_adapter_5_subscription_op_data *s_aws_mqtt_protocol_adapter_5_subscription_op_data_new(struct aws_allocator *allocator, struct aws_byte_cursor topic_filter, struct aws_protocol_adapter_weak_ref *callback_ref) { + struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_subscription_op_data)); subscribe_data->allocator = allocator; subscribe_data->callback_ref = aws_weak_ref_acquire(callback_ref); @@ -227,17 +81,19 @@ static struct aws_mqtt_protocol_adapter_5_subscribe_data *aws_mqtt_protocol_adap return subscribe_data; } -static void aws_mqtt_protocol_adapter_5_subscribe_data_delete(struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data) { +static void s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data) { aws_weak_ref_release(subscribe_data->callback_ref); aws_byte_buf_clean_up(&subscribe_data->topic_filter); aws_mem_release(subscribe_data->allocator, subscribe_data); } +/* Subscribe */ + static void s_protocol_adapter_5_subscribe_completion(const struct aws_mqtt5_packet_suback_view *suback, int error_code, void *complete_ctx) { - struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data = complete_ctx; + struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = complete_ctx; struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(subscribe_data->callback_ref); if (adapter == NULL) { @@ -245,17 +101,23 @@ static void s_protocol_adapter_5_subscribe_completion(const struct aws_mqtt5_pac } bool success = error_code == AWS_ERROR_SUCCESS && suback != NULL && suback->reason_code_count == 1 && suback->reason_codes[0] <= AWS_MQTT5_SARC_GRANTED_QOS_2; - s_aws_mqtt_protocol_adapter_subscription_set_on_subscribe_completion(&adapter->subscriptions, aws_byte_cursor_from_buf(&subscribe_data->topic_filter), success); + + struct aws_protocol_adapter_subscription_event subscribe_event = { + .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), + .event_type = success ? AWS_PASET_SUBSCRIBE_SUCCESS : AWS_PASET_SUBSCRIBE_FAILURE, + }; + + (*adapter->config.subscription_event_callback)(&subscribe_event, adapter->config.user_data); done: - aws_mqtt_protocol_adapter_5_subscribe_data_delete(subscribe_data); + s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(subscribe_data); } int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_5_subscribe_data *subscribe_data = aws_mqtt_protocol_adapter_5_subscribe_data_new(adapter->allocator, options->topic_filter, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = s_aws_mqtt_protocol_adapter_5_subscription_op_data_new(adapter->allocator, options->topic_filter, adapter->callback_ref); struct aws_mqtt5_subscription_view subscription_view = { .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, @@ -281,38 +143,180 @@ int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adap error: - aws_mqtt_protocol_adapter_5_subscribe_data_delete(subscribe_data); + s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(subscribe_data); return AWS_OP_ERR; } /* Unsubscribe */ +static void s_protocol_adapter_5_unsubscribe_completion(const struct aws_mqtt5_packet_unsuback_view *unsuback, + int error_code, + void *complete_ctx) { + struct aws_mqtt_protocol_adapter_5_subscription_op_data *unsubscribe_data = complete_ctx; + struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(unsubscribe_data->callback_ref); + + if (adapter == NULL) { + goto done; + } + + bool success = error_code == AWS_ERROR_SUCCESS && unsuback != NULL && unsuback->reason_code_count == 1 && unsuback->reason_codes[0] < 128; + + struct aws_protocol_adapter_subscription_event unsubscribe_event = { + .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), + .event_type = success ? AWS_PASET_UNSUBSCRIBE_SUCCESS : AWS_PASET_UNSUBSCRIBE_FAILURE, + }; + + (*adapter->config.subscription_event_callback)(&unsubscribe_event, adapter->config.user_data); + +done: + + s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(unsubscribe_data); +} + int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - (void)adapter; - (void)options; - return aws_raise_error(AWS_ERROR_UNIMPLEMENTED); + struct aws_mqtt_protocol_adapter_5_subscription_op_data *unsubscribe_data = s_aws_mqtt_protocol_adapter_5_subscription_op_data_new(adapter->allocator, options->topic_filter, adapter->callback_ref); + + struct aws_mqtt5_packet_unsubscribe_view unsubscribe_view = { + .topic_filters = &options->topic_filter, + .topic_filter_count = 1, + }; + + struct aws_mqtt5_unsubscribe_completion_options completion_options = { + .ack_timeout_seconds_override = options->ack_timeout_seconds, + .completion_callback = s_protocol_adapter_5_unsubscribe_completion, + .completion_user_data = unsubscribe_data, + }; + + if (aws_mqtt5_client_unsubscribe(adapter->client, &unsubscribe_view, &completion_options)) { + goto error; + } + + return AWS_OP_SUCCESS; + +error: + + s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(unsubscribe_data); + + return AWS_OP_ERR; } /* Publish */ +struct aws_mqtt_protocol_adapter_5_publish_op_data { + struct aws_allocator *allocator; + struct aws_protocol_adapter_weak_ref *callback_ref; + + void (*completion_callback_fn)(bool, void *); + void *user_data; +}; + +static struct aws_mqtt_protocol_adapter_5_publish_op_data *s_aws_mqtt_protocol_adapter_5_publish_op_data_new(struct aws_allocator *allocator, const struct aws_protocol_adapter_publish_options *publish_options, struct aws_protocol_adapter_weak_ref *callback_ref) { + struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_publish_op_data)); + + publish_data->allocator = allocator; + publish_data->callback_ref = aws_weak_ref_acquire(callback_ref); + publish_data->completion_callback_fn = publish_options->completion_callback_fn; + publish_data->user_data = publish_options->user_data; + + return publish_data; +} + +static void s_aws_mqtt_protocol_adapter_5_publish_op_data_delete(struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data) { + aws_weak_ref_release(publish_data->callback_ref); + + aws_mem_release(publish_data->allocator, publish_data); +} + +static void s_protocol_adapter_5_publish_completion( + enum aws_mqtt5_packet_type packet_type, + const void *packet, + int error_code, + void *complete_ctx) { + struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = complete_ctx; + struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(publish_data->callback_ref); + + if (adapter == NULL) { + goto done; + } + + bool success = false; + if (error_code == AWS_ERROR_SUCCESS && packet_type == AWS_MQTT5_PT_PUBACK) { + const struct aws_mqtt5_packet_puback_view *puback = packet; + if (puback->reason_code < 128) { + success = true; + } + } + + (*publish_data->completion_callback_fn)(success, publish_data->user_data); + +done: + + s_aws_mqtt_protocol_adapter_5_publish_op_data_delete(publish_data); +} + int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - (void)adapter; - (void)options; + struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = s_aws_mqtt_protocol_adapter_5_publish_op_data_new(adapter->allocator, options, adapter->callback_ref); - return aws_raise_error(AWS_ERROR_UNIMPLEMENTED); + struct aws_mqtt5_packet_publish_view publish_view = { + .topic = options->topic, + .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, + .payload = options->payload + }; + + struct aws_mqtt5_publish_completion_options completion_options = { + .ack_timeout_seconds_override = options->ack_timeout_seconds, + .completion_callback = s_protocol_adapter_5_publish_completion, + .completion_user_data = publish_data, + }; + + if (aws_mqtt5_client_publish(adapter->client, &publish_view, &completion_options)) { + goto error; + } + + return AWS_OP_SUCCESS; + +error: + + s_aws_mqtt_protocol_adapter_5_publish_op_data_delete(publish_data); + + return AWS_OP_ERR; } static bool s_protocol_adapter_mqtt5_listener_publish_received(const struct aws_mqtt5_packet_publish_view *publish, void *user_data) { - (void)publish; - (void)user_data; + struct aws_mqtt_protocol_adapter_5_impl *adapter = user_data; + + struct aws_protocol_adapter_incoming_publish_event publish_event = { + .topic = publish->topic, + .payload = publish->payload + }; + + (*adapter->config.incoming_publish_callback)(&publish_event, adapter->config.user_data); + + return false; } static void s_protocol_adapter_mqtt5_lifecycle_event_callback(const struct aws_mqtt5_client_lifecycle_event *event) { - (void)event; + struct aws_mqtt_protocol_adapter_5_impl *adapter = event->user_data; + + if (event->event_type != AWS_MQTT5_CLET_CONNECTION_SUCCESS && event->event_type != AWS_MQTT5_CLET_DISCONNECTION) { + return; + } + + bool is_connection_success = event->event_type == AWS_MQTT5_CLET_CONNECTION_SUCCESS; + + struct aws_protocol_adapter_connection_event connection_event = { + .event_type = is_connection_success ? AWS_PACET_ONLINE : AWS_PACET_OFFLINE, + }; + + if (is_connection_success) { + connection_event.rejoined_session = event->settings->rejoined_session; + } + + (*adapter->config.connection_event_callback)(&connection_event, adapter->config.user_data); } static void s_protocol_adapter_mqtt5_listener_termination_callback(void *user_data) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b8ef2587..f1d90e22 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -442,6 +442,11 @@ add_test_case(mqtt_subscription_set_publish_single_level_wildcards) add_test_case(mqtt_subscription_set_publish_multi_level_wildcards) add_test_case(mqtt_subscription_set_get_subscriptions) +add_test_case(request_response_mqtt5_protocol_adapter_subscribe_success) +#add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_error_code) +#add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code) +#add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_timeout) + generate_test_driver(${PROJECT_NAME}-tests) set(TEST_PAHO_CLIENT_BINARY_NAME ${PROJECT_NAME}-paho-client) diff --git a/tests/v5/mqtt5_client_tests.c b/tests/v5/mqtt5_client_tests.c index 2d436f85..06367d3d 100644 --- a/tests/v5/mqtt5_client_tests.c +++ b/tests/v5/mqtt5_client_tests.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -18,40 +17,13 @@ #include -#include #include -#define TEST_IO_MESSAGE_LENGTH 4096 - static bool s_is_within_percentage_of(uint64_t expected_time, uint64_t actual_time, double percentage) { double actual_percent = 1.0 - (double)actual_time / (double)expected_time; return fabs(actual_percent) <= percentage; } -int aws_mqtt5_mock_server_send_packet( - struct aws_mqtt5_server_mock_connection_context *connection, - enum aws_mqtt5_packet_type packet_type, - void *packet) { - aws_mqtt5_encoder_append_packet_encoding(&connection->encoder, packet_type, packet); - - struct aws_io_message *message = aws_channel_acquire_message_from_pool( - connection->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, TEST_IO_MESSAGE_LENGTH); - if (message == NULL) { - return AWS_OP_ERR; - } - - enum aws_mqtt5_encoding_result result = - aws_mqtt5_encoder_encode_to_buffer(&connection->encoder, &message->message_data); - AWS_FATAL_ASSERT(result == AWS_MQTT5_ER_FINISHED); - - if (aws_channel_slot_send_message(connection->slot, message, AWS_CHANNEL_DIR_WRITE)) { - aws_mem_release(message->allocator, message); - return AWS_OP_ERR; - } - - return AWS_OP_SUCCESS; -} - int aws_mqtt5_mock_server_handle_connect_always_succeed( void *packet, struct aws_mqtt5_server_mock_connection_context *connection, diff --git a/tests/v5/mqtt5_testing_utils.c b/tests/v5/mqtt5_testing_utils.c index e8e72f3f..4b682d83 100644 --- a/tests/v5/mqtt5_testing_utils.c +++ b/tests/v5/mqtt5_testing_utils.c @@ -1740,3 +1740,29 @@ size_t aws_mqtt5_linked_list_length(struct aws_linked_list *list) { return length; } + +#define TEST_IO_MESSAGE_LENGTH 4096 + +int aws_mqtt5_mock_server_send_packet( + struct aws_mqtt5_server_mock_connection_context *connection, + enum aws_mqtt5_packet_type packet_type, + void *packet) { + aws_mqtt5_encoder_append_packet_encoding(&connection->encoder, packet_type, packet); + + struct aws_io_message *message = aws_channel_acquire_message_from_pool( + connection->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, TEST_IO_MESSAGE_LENGTH); + if (message == NULL) { + return AWS_OP_ERR; + } + + enum aws_mqtt5_encoding_result result = + aws_mqtt5_encoder_encode_to_buffer(&connection->encoder, &message->message_data); + AWS_FATAL_ASSERT(result == AWS_MQTT5_ER_FINISHED); + + if (aws_channel_slot_send_message(connection->slot, message, AWS_CHANNEL_DIR_WRITE)) { + aws_mem_release(message->allocator, message); + return AWS_OP_ERR; + } + + return AWS_OP_SUCCESS; +} \ No newline at end of file diff --git a/tests/v5/mqtt5_testing_utils.h b/tests/v5/mqtt5_testing_utils.h index e4fa6de8..0e30f246 100644 --- a/tests/v5/mqtt5_testing_utils.h +++ b/tests/v5/mqtt5_testing_utils.h @@ -218,6 +218,11 @@ int aws_mqtt5_mock_server_handle_unsubscribe_unsuback_success( struct aws_mqtt5_server_mock_connection_context *connection, void *user_data); +int aws_mqtt5_mock_server_send_packet( + struct aws_mqtt5_server_mock_connection_context *connection, + enum aws_mqtt5_packet_type packet_type, + void *packet); + extern const struct aws_string *g_default_client_id; #define RECONNECT_TEST_MIN_BACKOFF 500 diff --git a/tests/v5/request_response_protocol_adapter_tests.c b/tests/v5/request_response_protocol_adapter_tests.c new file mode 100644 index 00000000..29abcd99 --- /dev/null +++ b/tests/v5/request_response_protocol_adapter_tests.c @@ -0,0 +1,133 @@ +/** +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0. +*/ + +#include "mqtt5_testing_utils.h" +#include + +struct request_response_protocol_adapter_incoming_publish_event_record { + struct aws_byte_buf topic; + struct aws_byte_buf payload; +}; + +static void s_request_response_protocol_adapter_incoming_publish_event_record_init( + struct request_response_protocol_adapter_incoming_publish_event_record *record, + struct aws_allocator *allocator, + struct aws_byte_cursor topic, + struct aws_byte_cursor payload) { + + aws_byte_buf_init_copy_from_cursor(&record->topic, allocator, topic); + aws_byte_buf_init_copy_from_cursor(&record->payload, allocator, payload); +} + +static void s_request_response_protocol_adapter_incoming_publish_event_record_cleanup(struct request_response_protocol_adapter_incoming_publish_event_record *record) { + aws_byte_buf_clean_up(&record->topic); + aws_byte_buf_clean_up(&record->payload); +} + +struct request_response_protocol_adapter_connection_event_record { + enum aws_protocol_adapter_connection_event_type event_type; + bool rejoined_session; +}; + +struct request_response_protocol_adapter_subscription_event_record { + enum aws_protocol_adapter_subscription_event_type event_type; + struct aws_byte_buf topic_filter; +}; + +static void s_request_response_protocol_adapter_incoming_subscription_event_record_init( + struct request_response_protocol_adapter_subscription_event_record *record, + struct aws_allocator *allocator, + struct aws_byte_cursor topic_filter) { + + aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); +} + +static void s_request_response_protocol_adapter_incoming_subscription_event_record_cleanup(struct request_response_protocol_adapter_subscription_event_record *record) { + aws_byte_buf_clean_up(&record->topic_filter); +} + +struct aws_request_response_mqtt5_adapter_test_fixture { + struct aws_allocator *allocator; + struct aws_mqtt5_client_mock_test_fixture mqtt5_fixture; + + struct aws_mqtt_protocol_adapter *protocol_adapter; + + struct aws_array_list incoming_publish_events; + struct aws_array_list connection_events; + struct aws_array_list subscription_events; + + struct aws_mutex lock; + struct aws_condition_variable signal; +}; + + +static void s_rr_mqtt5_protocol_adapter_subscription_event(struct aws_protocol_adapter_subscription_event *event, void *user_data) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + + struct request_response_protocol_adapter_subscription_event_record record = { + .event_type = event->event_type + }; + s_request_response_protocol_adapter_incoming_subscription_event_record_init(&record, fixture->allocator, event->topic_filter); + + aws_mutex_lock(&fixture->lock); + aws_array_list_push_back(&fixture->subscription_events, &record); + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static void s_rr_mqtt5_protocol_adapter_incoming_publish(struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + +} + +static void s_rr_mqtt5_protocol_adapter_terminate_callback(void *user_data) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; +} + +static void s_rr_mqtt5_protocol_adapter_connection_event(struct aws_protocol_adapter_connection_event *event, void *user_data) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; +} + +static int s_aws_request_response_mqtt5_adapter_test_fixture_init( + struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct aws_allocator *allocator, + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options *mqtt5_fixture_config) { + + AWS_ZERO_STRUCT(*fixture); + + fixture->allocator = allocator; + + if (aws_mqtt5_client_mock_test_fixture_init(&fixture->mqtt5_fixture, allocator, mqtt5_fixture_config)) { + return AWS_OP_ERR; + } + + struct aws_mqtt_protocol_adapter_options protocol_adapter_options = { + .subscription_event_callback = s_rr_mqtt5_protocol_adapter_subscription_event, + .incoming_publish_callback = s_rr_mqtt5_protocol_adapter_incoming_publish, + .terminate_callback = s_rr_mqtt5_protocol_adapter_terminate_callback, + .connection_event_callback = s_rr_mqtt5_protocol_adapter_connection_event, + .user_data = fixture + }; + + fixture->protocol_adapter = aws_mqtt_protocol_adapter_new_from_5(allocator, &protocol_adapter_options, fixture->mqtt5_fixture.client); + AWS_FATAL_ASSERT(fixture->protocol_adapter != NULL); + + aws_array_list_init_dynamic(&fixture->incoming_publish_events, allocator, 10, sizeof(struct request_response_protocol_adapter_incoming_publish_event_record)); + aws_array_list_init_dynamic(&fixture->connection_events, allocator, 10, sizeof(struct request_response_protocol_adapter_connection_event_record)); + aws_array_list_init_dynamic(&fixture->subscription_events, allocator, 10, sizeof(struct request_response_protocol_adapter_subscription_event_record)); + + aws_mutex_init(&fixture->lock); + aws_condition_variable_init(&fixture->signal); + + return AWS_OP_SUCCESS; +} + +static void s_aws_mqtt5_to_mqtt3_adapter_test_fixture_clean_up(struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { + + aws_mqtt5_client_mock_test_fixture_clean_up(&fixture->mqtt5_fixture); + + aws_mutex_clean_up(&fixture->lock); + aws_condition_variable_clean_up(&fixture->signal); +} From b23c567509ba0eae18fce6e9e18009c7b239c999 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 18 Jan 2024 08:39:21 -0800 Subject: [PATCH 005/124] Another sync point --- tests/CMakeLists.txt | 2 +- .../request_response_protocol_adapter_tests.c | 68 ++++++++++++++++--- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f1d90e22..9bcbb720 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -442,7 +442,7 @@ add_test_case(mqtt_subscription_set_publish_single_level_wildcards) add_test_case(mqtt_subscription_set_publish_multi_level_wildcards) add_test_case(mqtt_subscription_set_get_subscriptions) -add_test_case(request_response_mqtt5_protocol_adapter_subscribe_success) +#add_test_case(request_response_mqtt5_protocol_adapter_subscribe_success) #add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_error_code) #add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code) #add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_timeout) diff --git a/tests/v5/request_response_protocol_adapter_tests.c b/tests/v5/request_response_protocol_adapter_tests.c index 29abcd99..f635e126 100644 --- a/tests/v5/request_response_protocol_adapter_tests.c +++ b/tests/v5/request_response_protocol_adapter_tests.c @@ -58,12 +58,14 @@ struct aws_request_response_mqtt5_adapter_test_fixture { struct aws_array_list connection_events; struct aws_array_list subscription_events; + bool adapter_terminated; + struct aws_mutex lock; struct aws_condition_variable signal; }; -static void s_rr_mqtt5_protocol_adapter_subscription_event(struct aws_protocol_adapter_subscription_event *event, void *user_data) { +static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event(struct aws_protocol_adapter_subscription_event *event, void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; struct request_response_protocol_adapter_subscription_event_record record = { @@ -77,17 +79,40 @@ static void s_rr_mqtt5_protocol_adapter_subscription_event(struct aws_protocol_a aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_incoming_publish(struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data) { +static void s_rr_mqtt5_protocol_adapter_test_on_incoming_publish(struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + struct request_response_protocol_adapter_incoming_publish_event_record record; + AWS_ZERO_STRUCT(record); + s_request_response_protocol_adapter_incoming_publish_event_record_init(&record, fixture->allocator, publish->topic, publish->payload); + + aws_mutex_lock(&fixture->lock); + aws_array_list_push_back(&fixture->incoming_publish_events, &record); + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_terminate_callback(void *user_data) { +static void s_rr_mqtt5_protocol_adapter_test_on_terminate_callback(void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + + aws_mutex_lock(&fixture->lock); + fixture->adapter_terminated = true; + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_connection_event(struct aws_protocol_adapter_connection_event *event, void *user_data) { +static void s_rr_mqtt5_protocol_adapter_test_on_connection_event(struct aws_protocol_adapter_connection_event *event, void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + + struct request_response_protocol_adapter_connection_event_record record = { + .event_type = event->event_type, + .rejoined_session = event->rejoined_session + }; + + aws_mutex_lock(&fixture->lock); + aws_array_list_push_back(&fixture->connection_events, &record); + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); } static int s_aws_request_response_mqtt5_adapter_test_fixture_init( @@ -104,10 +129,10 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( } struct aws_mqtt_protocol_adapter_options protocol_adapter_options = { - .subscription_event_callback = s_rr_mqtt5_protocol_adapter_subscription_event, - .incoming_publish_callback = s_rr_mqtt5_protocol_adapter_incoming_publish, - .terminate_callback = s_rr_mqtt5_protocol_adapter_terminate_callback, - .connection_event_callback = s_rr_mqtt5_protocol_adapter_connection_event, + .subscription_event_callback = s_rr_mqtt5_protocol_adapter_test_on_subscription_event, + .incoming_publish_callback = s_rr_mqtt5_protocol_adapter_test_on_incoming_publish, + .terminate_callback = s_rr_mqtt5_protocol_adapter_test_on_terminate_callback, + .connection_event_callback = s_rr_mqtt5_protocol_adapter_test_on_connection_event, .user_data = fixture }; @@ -124,10 +149,37 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( return AWS_OP_SUCCESS; } +static bool s_is_adapter_terminated(void *context) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = context; + + return fixture->adapter_terminated; +} + static void s_aws_mqtt5_to_mqtt3_adapter_test_fixture_clean_up(struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { + aws_mqtt_protocol_adapter_delete(fixture->protocol_adapter); + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->signal, s_is_adapter_terminated, fixture); + aws_mutex_unlock(&fixture->lock); aws_mqtt5_client_mock_test_fixture_clean_up(&fixture->mqtt5_fixture); + for (size_t i = 0; i < aws_array_list_length(&fixture->subscription_events); ++i) { + struct request_response_protocol_adapter_subscription_event_record record; + aws_array_list_get_at(&fixture->subscription_events, &record, i); + s_request_response_protocol_adapter_incoming_subscription_event_record_cleanup(&record); + } + aws_array_list_clean_up(&fixture->subscription_events); + + for (size_t i = 0; i < aws_array_list_length(&fixture->incoming_publish_events); ++i) { + struct request_response_protocol_adapter_incoming_publish_event_record record; + aws_array_list_get_at(&fixture->incoming_publish_events, &record, i); + s_request_response_protocol_adapter_incoming_publish_event_record_cleanup(&record); + } + aws_array_list_clean_up(&fixture->incoming_publish_events); + + aws_array_list_clean_up(&fixture->connection_events); + aws_mutex_clean_up(&fixture->lock); aws_condition_variable_clean_up(&fixture->signal); } From 37143366de8d911227640516a0d714f8f9774908 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 18 Jan 2024 14:21:02 -0800 Subject: [PATCH 006/124] First test checkpoint: --- CMakeLists.txt | 1 + .../request-response/protocol_adapter.h | 1 + source/request-response/protocol_adapter.c | 16 +- source/request-response/weak_ref.c | 2 +- tests/CMakeLists.txt | 2 +- .../request_response_protocol_adapter_tests.c | 372 ++++++++++++++++++ tests/v5/mqtt5_client_tests.c | 8 +- tests/v5/mqtt5_testing_utils.h | 5 + tests/v5/mqtt5_to_mqtt3_adapter_tests.c | 4 - .../request_response_protocol_adapter_tests.c | 185 --------- 10 files changed, 392 insertions(+), 204 deletions(-) create mode 100644 tests/request_response_protocol_adapter_tests.c delete mode 100644 tests/v5/request_response_protocol_adapter_tests.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b33f24d6..0a2eeefa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ file(GLOB AWS_MQTT_PRIV_EXPOSED_HEADERS file(GLOB AWS_MQTT_SRC "source/*.c" "source/v5/*.c" + "source/request-response/*.c" ) file(GLOB MQTT_HEADERS diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 3cf39a07..86d5a44f 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -6,6 +6,7 @@ * SPDX-License-Identifier: Apache-2.0. */ +#include #include #include diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 4aae75cd..d1a649b0 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -48,7 +48,7 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311(struct struct aws_mqtt_protocol_adapter_5_impl { struct aws_allocator *allocator; struct aws_mqtt_protocol_adapter base; - struct aws_protocol_adapter_weak_ref *callback_ref; + struct aws_weak_ref *callback_ref; struct aws_mqtt_protocol_adapter_options config; struct aws_event_loop *loop; @@ -68,10 +68,10 @@ struct aws_mqtt_protocol_adapter_5_subscription_op_data { struct aws_allocator *allocator; struct aws_byte_buf topic_filter; - struct aws_protocol_adapter_weak_ref *callback_ref; + struct aws_weak_ref *callback_ref; }; -static struct aws_mqtt_protocol_adapter_5_subscription_op_data *s_aws_mqtt_protocol_adapter_5_subscription_op_data_new(struct aws_allocator *allocator, struct aws_byte_cursor topic_filter, struct aws_protocol_adapter_weak_ref *callback_ref) { +static struct aws_mqtt_protocol_adapter_5_subscription_op_data *s_aws_mqtt_protocol_adapter_5_subscription_op_data_new(struct aws_allocator *allocator, struct aws_byte_cursor topic_filter, struct aws_weak_ref *callback_ref) { struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_subscription_op_data)); subscribe_data->allocator = allocator; @@ -207,13 +207,13 @@ int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_ad struct aws_mqtt_protocol_adapter_5_publish_op_data { struct aws_allocator *allocator; - struct aws_protocol_adapter_weak_ref *callback_ref; + struct aws_weak_ref *callback_ref; void (*completion_callback_fn)(bool, void *); void *user_data; }; -static struct aws_mqtt_protocol_adapter_5_publish_op_data *s_aws_mqtt_protocol_adapter_5_publish_op_data_new(struct aws_allocator *allocator, const struct aws_protocol_adapter_publish_options *publish_options, struct aws_protocol_adapter_weak_ref *callback_ref) { +static struct aws_mqtt_protocol_adapter_5_publish_op_data *s_aws_mqtt_protocol_adapter_5_publish_op_data_new(struct aws_allocator *allocator, const struct aws_protocol_adapter_publish_options *publish_options, struct aws_weak_ref *callback_ref) { struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_publish_op_data)); publish_data->allocator = allocator; @@ -347,8 +347,6 @@ static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt5_vtable = }; struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt5_client *client) { - AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(client->loop)); - struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_impl)); adapter->allocator = allocator; @@ -373,7 +371,7 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aw adapter->listener = aws_mqtt5_listener_new(allocator, &listener_options); - return adapter; + return &adapter->base; } void aws_mqtt_protocol_adapter_delete(struct aws_mqtt_protocol_adapter *adapter) { @@ -390,4 +388,4 @@ int aws_mqtt_protocol_adapter_unsubscribe(struct aws_mqtt_protocol_adapter *adap int aws_mqtt_protocol_adapter_publish(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_publish_options *options) { return (*adapter->vtable->aws_mqtt_protocol_adapter_publish_fn)(adapter->impl, options); -} \ No newline at end of file +} diff --git a/source/request-response/weak_ref.c b/source/request-response/weak_ref.c index e3e5009c..97561ea9 100644 --- a/source/request-response/weak_ref.c +++ b/source/request-response/weak_ref.c @@ -51,4 +51,4 @@ void *aws_weak_ref_get_reference(struct aws_weak_ref *weak_ref) { void aws_weak_ref_zero_reference(struct aws_weak_ref *weak_ref) { weak_ref->referenced = NULL; -} \ No newline at end of file +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9bcbb720..f1d90e22 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -442,7 +442,7 @@ add_test_case(mqtt_subscription_set_publish_single_level_wildcards) add_test_case(mqtt_subscription_set_publish_multi_level_wildcards) add_test_case(mqtt_subscription_set_get_subscriptions) -#add_test_case(request_response_mqtt5_protocol_adapter_subscribe_success) +add_test_case(request_response_mqtt5_protocol_adapter_subscribe_success) #add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_error_code) #add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code) #add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_timeout) diff --git a/tests/request_response_protocol_adapter_tests.c b/tests/request_response_protocol_adapter_tests.c new file mode 100644 index 00000000..da53747b --- /dev/null +++ b/tests/request_response_protocol_adapter_tests.c @@ -0,0 +1,372 @@ +/** +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0. +*/ + +#include "v5/mqtt5_testing_utils.h" +#include + +#include "aws/mqtt/private/request-response/protocol_adapter.h" + +#include + +struct request_response_protocol_adapter_incoming_publish_event_record { + struct aws_byte_buf topic; + struct aws_byte_buf payload; +}; + +static void s_request_response_protocol_adapter_incoming_publish_event_record_init( + struct request_response_protocol_adapter_incoming_publish_event_record *record, + struct aws_allocator *allocator, + struct aws_byte_cursor topic, + struct aws_byte_cursor payload) { + + aws_byte_buf_init_copy_from_cursor(&record->topic, allocator, topic); + aws_byte_buf_init_copy_from_cursor(&record->payload, allocator, payload); +} + +static void s_request_response_protocol_adapter_incoming_publish_event_record_cleanup(struct request_response_protocol_adapter_incoming_publish_event_record *record) { + aws_byte_buf_clean_up(&record->topic); + aws_byte_buf_clean_up(&record->payload); +} + +struct request_response_protocol_adapter_connection_event_record { + enum aws_protocol_adapter_connection_event_type event_type; + bool rejoined_session; +}; + +struct request_response_protocol_adapter_subscription_event_record { + enum aws_protocol_adapter_subscription_event_type event_type; + struct aws_byte_buf topic_filter; +}; + +static void s_request_response_protocol_adapter_subscription_event_record_init( + struct request_response_protocol_adapter_subscription_event_record *record, + struct aws_allocator *allocator, + struct aws_byte_cursor topic_filter) { + + aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); +} + +static void s_request_response_protocol_adapter_subscription_event_record_cleanup(struct request_response_protocol_adapter_subscription_event_record *record) { + aws_byte_buf_clean_up(&record->topic_filter); +} + +struct aws_request_response_mqtt5_adapter_test_fixture { + struct aws_allocator *allocator; + struct aws_mqtt5_client_mock_test_fixture mqtt5_fixture; + + struct aws_mqtt_protocol_adapter *protocol_adapter; + + struct aws_array_list incoming_publish_events; + struct aws_array_list connection_events; + struct aws_array_list subscription_events; + + bool adapter_terminated; + + struct aws_mutex lock; + struct aws_condition_variable signal; +}; + + +static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event(struct aws_protocol_adapter_subscription_event *event, void *user_data) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + + struct request_response_protocol_adapter_subscription_event_record record = { + .event_type = event->event_type + }; + s_request_response_protocol_adapter_subscription_event_record_init(&record, fixture->allocator, event->topic_filter); + + aws_mutex_lock(&fixture->lock); + aws_array_list_push_back(&fixture->subscription_events, &record); + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static void s_rr_mqtt5_protocol_adapter_test_on_incoming_publish(struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + + struct request_response_protocol_adapter_incoming_publish_event_record record; + AWS_ZERO_STRUCT(record); + s_request_response_protocol_adapter_incoming_publish_event_record_init(&record, fixture->allocator, publish->topic, publish->payload); + + aws_mutex_lock(&fixture->lock); + aws_array_list_push_back(&fixture->incoming_publish_events, &record); + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static void s_rr_mqtt5_protocol_adapter_test_on_terminate_callback(void *user_data) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + + aws_mutex_lock(&fixture->lock); + fixture->adapter_terminated = true; + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static void s_rr_mqtt5_protocol_adapter_test_on_connection_event(struct aws_protocol_adapter_connection_event *event, void *user_data) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + + struct request_response_protocol_adapter_connection_event_record record = { + .event_type = event->event_type, + .rejoined_session = event->rejoined_session + }; + + aws_mutex_lock(&fixture->lock); + aws_array_list_push_back(&fixture->connection_events, &record); + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static int s_aws_request_response_mqtt5_adapter_test_fixture_init( + struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct aws_allocator *allocator, + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options *mqtt5_fixture_config) { + + AWS_ZERO_STRUCT(*fixture); + + fixture->allocator = allocator; + + if (aws_mqtt5_client_mock_test_fixture_init(&fixture->mqtt5_fixture, allocator, mqtt5_fixture_config)) { + return AWS_OP_ERR; + } + + struct aws_mqtt_protocol_adapter_options protocol_adapter_options = { + .subscription_event_callback = s_rr_mqtt5_protocol_adapter_test_on_subscription_event, + .incoming_publish_callback = s_rr_mqtt5_protocol_adapter_test_on_incoming_publish, + .terminate_callback = s_rr_mqtt5_protocol_adapter_test_on_terminate_callback, + .connection_event_callback = s_rr_mqtt5_protocol_adapter_test_on_connection_event, + .user_data = fixture + }; + + fixture->protocol_adapter = aws_mqtt_protocol_adapter_new_from_5(allocator, &protocol_adapter_options, fixture->mqtt5_fixture.client); + AWS_FATAL_ASSERT(fixture->protocol_adapter != NULL); + + aws_array_list_init_dynamic(&fixture->incoming_publish_events, allocator, 10, sizeof(struct request_response_protocol_adapter_incoming_publish_event_record)); + aws_array_list_init_dynamic(&fixture->connection_events, allocator, 10, sizeof(struct request_response_protocol_adapter_connection_event_record)); + aws_array_list_init_dynamic(&fixture->subscription_events, allocator, 10, sizeof(struct request_response_protocol_adapter_subscription_event_record)); + + aws_mutex_init(&fixture->lock); + aws_condition_variable_init(&fixture->signal); + + return AWS_OP_SUCCESS; +} + +static bool s_is_adapter_terminated(void *context) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = context; + + return fixture->adapter_terminated; +} + +static void s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { + aws_mqtt_protocol_adapter_delete(fixture->protocol_adapter); + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_is_adapter_terminated, fixture); + aws_mutex_unlock(&fixture->lock); + + aws_mqtt5_client_mock_test_fixture_clean_up(&fixture->mqtt5_fixture); + + for (size_t i = 0; i < aws_array_list_length(&fixture->subscription_events); ++i) { + struct request_response_protocol_adapter_subscription_event_record record; + aws_array_list_get_at(&fixture->subscription_events, &record, i); + s_request_response_protocol_adapter_subscription_event_record_cleanup(&record); + } + aws_array_list_clean_up(&fixture->subscription_events); + + for (size_t i = 0; i < aws_array_list_length(&fixture->incoming_publish_events); ++i) { + struct request_response_protocol_adapter_incoming_publish_event_record record; + aws_array_list_get_at(&fixture->incoming_publish_events, &record, i); + s_request_response_protocol_adapter_incoming_publish_event_record_cleanup(&record); + } + aws_array_list_clean_up(&fixture->incoming_publish_events); + + aws_array_list_clean_up(&fixture->connection_events); + + aws_mutex_clean_up(&fixture->lock); + aws_condition_variable_clean_up(&fixture->signal); +} + +struct test_subscription_event_wait_context { + struct request_response_protocol_adapter_subscription_event_record *expected_event; + size_t expected_count; + struct aws_request_response_mqtt5_adapter_test_fixture *fixture; +}; + +static bool s_do_subscription_events_contain(void *context) { + struct test_subscription_event_wait_context *wait_context = context; + + size_t found = 0; + + size_t num_events = aws_array_list_length(&wait_context->fixture->subscription_events); + for (size_t i = 0; i < num_events; ++i) { + struct request_response_protocol_adapter_subscription_event_record record; + aws_array_list_get_at(&wait_context->fixture->subscription_events, &record, i); + + if (record.event_type == wait_context->expected_event->event_type) { + struct aws_byte_cursor record_topic_filter = aws_byte_cursor_from_buf(&record.topic_filter); + struct aws_byte_cursor expected_topic_filter = aws_byte_cursor_from_buf(&wait_context->expected_event->topic_filter); + if (aws_byte_cursor_eq(&record_topic_filter, &expected_topic_filter)) { + ++found; + } + } + } + + return found >= wait_context->expected_count; +} + +static void s_wait_for_subscription_events_contains(struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct request_response_protocol_adapter_subscription_event_record *expected_event, + size_t expected_count) { + + struct test_subscription_event_wait_context context = { + .expected_event = expected_event, + .expected_count = expected_count, + .fixture = fixture, + }; + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_do_subscription_events_contain, &context); + aws_mutex_unlock(&fixture->lock); +} + +struct test_connection_event_wait_context { + struct request_response_protocol_adapter_connection_event_record *expected_event; + size_t expected_count; + struct aws_request_response_mqtt5_adapter_test_fixture *fixture; +}; + +static bool s_do_connection_events_contain(void *context) { + struct test_connection_event_wait_context *wait_context = context; + + size_t found = 0; + + size_t num_events = aws_array_list_length(&wait_context->fixture->connection_events); + for (size_t i = 0; i < num_events; ++i) { + struct request_response_protocol_adapter_connection_event_record record; + aws_array_list_get_at(&wait_context->fixture->connection_events, &record, i); + + if (record.event_type == wait_context->expected_event->event_type && record.rejoined_session == wait_context->expected_event->rejoined_session) { + ++found; + } + } + + return found >= wait_context->expected_count; +} + +static void s_wait_for_connection_events_contains(struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct request_response_protocol_adapter_connection_event_record *expected_event, + size_t expected_count) { + + struct test_connection_event_wait_context context = { + .expected_event = expected_event, + .expected_count = expected_count, + .fixture = fixture, + }; + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_do_connection_events_contain, &context); + aws_mutex_unlock(&fixture->lock); +} + +struct test_incoming_publish_event_wait_context { + struct request_response_protocol_adapter_incoming_publish_event_record *expected_event; + size_t expected_count; + struct aws_request_response_mqtt5_adapter_test_fixture *fixture; +}; + +static bool s_do_incoming_publish_events_contain(void *context) { + struct test_incoming_publish_event_wait_context *wait_context = context; + + size_t found = 0; + + size_t num_events = aws_array_list_length(&wait_context->fixture->incoming_publish_events); + for (size_t i = 0; i < num_events; ++i) { + struct request_response_protocol_adapter_incoming_publish_event_record record; + aws_array_list_get_at(&wait_context->fixture->incoming_publish_events, &record, i); + + struct aws_byte_cursor record_topic = aws_byte_cursor_from_buf(&record.topic); + struct aws_byte_cursor expected_topic = aws_byte_cursor_from_buf(&wait_context->expected_event->topic); + if (!aws_byte_cursor_eq(&record_topic, &expected_topic)) { + continue; + } + + struct aws_byte_cursor record_payload = aws_byte_cursor_from_buf(&record.payload); + struct aws_byte_cursor expected_payload = aws_byte_cursor_from_buf(&wait_context->expected_event->payload); + if (!aws_byte_cursor_eq(&record_payload, &expected_payload)) { + continue; + } + + ++found; + } + + return found >= wait_context->expected_count; +} + +static void s_wait_for_incoming_publish_events_contains(struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct request_response_protocol_adapter_incoming_publish_event_record *expected_event, + size_t expected_count) { + + struct test_incoming_publish_event_wait_context context = { + .expected_event = expected_event, + .expected_count = expected_count, + .fixture = fixture, + }; + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_do_incoming_publish_events_contain, &context); + aws_mutex_unlock(&fixture->lock); +} + +static int s_request_response_mqtt5_protocol_adapter_subscribe_success_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options test_options; + aws_mqtt5_client_test_init_default_options(&test_options); + + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + aws_mqtt5_server_send_suback_on_subscribe; + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options mqtt5_test_fixture_options = { + .client_options = &test_options.client_options, + .server_function_table = &test_options.server_function_table, + }; + + struct aws_request_response_mqtt5_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + + struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + ASSERT_SUCCESS(aws_mqtt5_client_start(client)); + + aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + + struct request_response_protocol_adapter_subscription_event_record expected_outcome = { + .event_type = AWS_PASET_SUBSCRIBE_SUCCESS, + }; + + aws_byte_buf_init_copy_from_cursor(&expected_outcome.topic_filter, allocator, aws_byte_cursor_from_c_str("hello/world")); + + struct aws_protocol_adapter_subscribe_options subscribe_options = { + .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), + .ack_timeout_seconds = 5, + }; + + aws_mqtt_protocol_adapter_subscribe(fixture.protocol_adapter, &subscribe_options); + + s_wait_for_subscription_events_contains(&fixture, &expected_outcome, 1); + + s_request_response_protocol_adapter_subscription_event_record_cleanup(&expected_outcome); + + s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_subscribe_success, + s_request_response_mqtt5_protocol_adapter_subscribe_success_fn) \ No newline at end of file diff --git a/tests/v5/mqtt5_client_tests.c b/tests/v5/mqtt5_client_tests.c index 06367d3d..7049dbea 100644 --- a/tests/v5/mqtt5_client_tests.c +++ b/tests/v5/mqtt5_client_tests.c @@ -1864,7 +1864,7 @@ static void s_wait_for_suback_received(struct aws_mqtt5_client_mock_test_fixture aws_mutex_unlock(&test_context->lock); } -static int s_aws_mqtt5_server_send_suback_on_subscribe( +int aws_mqtt5_server_send_suback_on_subscribe( void *packet, struct aws_mqtt5_server_mock_connection_context *connection, void *user_data) { @@ -1892,7 +1892,7 @@ static int s_mqtt5_client_subscribe_success_fn(struct aws_allocator *allocator, aws_mqtt5_client_test_init_default_options(&test_options); test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = - s_aws_mqtt5_server_send_suback_on_subscribe; + aws_mqtt5_server_send_suback_on_subscribe; struct aws_mqtt5_client_mock_test_fixture test_context; @@ -3241,7 +3241,7 @@ static int s_do_sub_pub_unsub_test(struct aws_allocator *allocator, enum aws_mqt test_options.client_options.publish_received_handler_user_data = &full_test_context; test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = - s_aws_mqtt5_server_send_suback_on_subscribe; + aws_mqtt5_server_send_suback_on_subscribe; test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = aws_mqtt5_mock_server_handle_publish_puback_and_forward; test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = @@ -4139,7 +4139,7 @@ static int s_mqtt5_client_statistics_subscribe_fn(struct aws_allocator *allocato aws_mqtt5_client_test_init_default_options(&test_options); test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = - s_aws_mqtt5_server_send_suback_on_subscribe; + aws_mqtt5_server_send_suback_on_subscribe; struct aws_mqtt5_client_mock_test_fixture test_context; diff --git a/tests/v5/mqtt5_testing_utils.h b/tests/v5/mqtt5_testing_utils.h index 0e30f246..74cd0f8d 100644 --- a/tests/v5/mqtt5_testing_utils.h +++ b/tests/v5/mqtt5_testing_utils.h @@ -223,6 +223,11 @@ int aws_mqtt5_mock_server_send_packet( enum aws_mqtt5_packet_type packet_type, void *packet); +int aws_mqtt5_server_send_suback_on_subscribe( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data); + extern const struct aws_string *g_default_client_id; #define RECONNECT_TEST_MIN_BACKOFF 500 diff --git a/tests/v5/mqtt5_to_mqtt3_adapter_tests.c b/tests/v5/mqtt5_to_mqtt3_adapter_tests.c index e3de0802..b067267a 100644 --- a/tests/v5/mqtt5_to_mqtt3_adapter_tests.c +++ b/tests/v5/mqtt5_to_mqtt3_adapter_tests.c @@ -9,18 +9,14 @@ #include #include #include -#include #include #include #include -#include #include -#include #include #include -#include enum aws_mqtt3_lifecycle_event_type { AWS_MQTT3_LET_CONNECTION_COMPLETE, diff --git a/tests/v5/request_response_protocol_adapter_tests.c b/tests/v5/request_response_protocol_adapter_tests.c deleted file mode 100644 index f635e126..00000000 --- a/tests/v5/request_response_protocol_adapter_tests.c +++ /dev/null @@ -1,185 +0,0 @@ -/** -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0. -*/ - -#include "mqtt5_testing_utils.h" -#include - -struct request_response_protocol_adapter_incoming_publish_event_record { - struct aws_byte_buf topic; - struct aws_byte_buf payload; -}; - -static void s_request_response_protocol_adapter_incoming_publish_event_record_init( - struct request_response_protocol_adapter_incoming_publish_event_record *record, - struct aws_allocator *allocator, - struct aws_byte_cursor topic, - struct aws_byte_cursor payload) { - - aws_byte_buf_init_copy_from_cursor(&record->topic, allocator, topic); - aws_byte_buf_init_copy_from_cursor(&record->payload, allocator, payload); -} - -static void s_request_response_protocol_adapter_incoming_publish_event_record_cleanup(struct request_response_protocol_adapter_incoming_publish_event_record *record) { - aws_byte_buf_clean_up(&record->topic); - aws_byte_buf_clean_up(&record->payload); -} - -struct request_response_protocol_adapter_connection_event_record { - enum aws_protocol_adapter_connection_event_type event_type; - bool rejoined_session; -}; - -struct request_response_protocol_adapter_subscription_event_record { - enum aws_protocol_adapter_subscription_event_type event_type; - struct aws_byte_buf topic_filter; -}; - -static void s_request_response_protocol_adapter_incoming_subscription_event_record_init( - struct request_response_protocol_adapter_subscription_event_record *record, - struct aws_allocator *allocator, - struct aws_byte_cursor topic_filter) { - - aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); -} - -static void s_request_response_protocol_adapter_incoming_subscription_event_record_cleanup(struct request_response_protocol_adapter_subscription_event_record *record) { - aws_byte_buf_clean_up(&record->topic_filter); -} - -struct aws_request_response_mqtt5_adapter_test_fixture { - struct aws_allocator *allocator; - struct aws_mqtt5_client_mock_test_fixture mqtt5_fixture; - - struct aws_mqtt_protocol_adapter *protocol_adapter; - - struct aws_array_list incoming_publish_events; - struct aws_array_list connection_events; - struct aws_array_list subscription_events; - - bool adapter_terminated; - - struct aws_mutex lock; - struct aws_condition_variable signal; -}; - - -static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event(struct aws_protocol_adapter_subscription_event *event, void *user_data) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; - - struct request_response_protocol_adapter_subscription_event_record record = { - .event_type = event->event_type - }; - s_request_response_protocol_adapter_incoming_subscription_event_record_init(&record, fixture->allocator, event->topic_filter); - - aws_mutex_lock(&fixture->lock); - aws_array_list_push_back(&fixture->subscription_events, &record); - aws_mutex_unlock(&fixture->lock); - aws_condition_variable_notify_all(&fixture->signal); -} - -static void s_rr_mqtt5_protocol_adapter_test_on_incoming_publish(struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; - - struct request_response_protocol_adapter_incoming_publish_event_record record; - AWS_ZERO_STRUCT(record); - s_request_response_protocol_adapter_incoming_publish_event_record_init(&record, fixture->allocator, publish->topic, publish->payload); - - aws_mutex_lock(&fixture->lock); - aws_array_list_push_back(&fixture->incoming_publish_events, &record); - aws_mutex_unlock(&fixture->lock); - aws_condition_variable_notify_all(&fixture->signal); -} - -static void s_rr_mqtt5_protocol_adapter_test_on_terminate_callback(void *user_data) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; - - aws_mutex_lock(&fixture->lock); - fixture->adapter_terminated = true; - aws_mutex_unlock(&fixture->lock); - aws_condition_variable_notify_all(&fixture->signal); -} - -static void s_rr_mqtt5_protocol_adapter_test_on_connection_event(struct aws_protocol_adapter_connection_event *event, void *user_data) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; - - struct request_response_protocol_adapter_connection_event_record record = { - .event_type = event->event_type, - .rejoined_session = event->rejoined_session - }; - - aws_mutex_lock(&fixture->lock); - aws_array_list_push_back(&fixture->connection_events, &record); - aws_mutex_unlock(&fixture->lock); - aws_condition_variable_notify_all(&fixture->signal); -} - -static int s_aws_request_response_mqtt5_adapter_test_fixture_init( - struct aws_request_response_mqtt5_adapter_test_fixture *fixture, - struct aws_allocator *allocator, - struct aws_mqtt5_client_mqtt5_mock_test_fixture_options *mqtt5_fixture_config) { - - AWS_ZERO_STRUCT(*fixture); - - fixture->allocator = allocator; - - if (aws_mqtt5_client_mock_test_fixture_init(&fixture->mqtt5_fixture, allocator, mqtt5_fixture_config)) { - return AWS_OP_ERR; - } - - struct aws_mqtt_protocol_adapter_options protocol_adapter_options = { - .subscription_event_callback = s_rr_mqtt5_protocol_adapter_test_on_subscription_event, - .incoming_publish_callback = s_rr_mqtt5_protocol_adapter_test_on_incoming_publish, - .terminate_callback = s_rr_mqtt5_protocol_adapter_test_on_terminate_callback, - .connection_event_callback = s_rr_mqtt5_protocol_adapter_test_on_connection_event, - .user_data = fixture - }; - - fixture->protocol_adapter = aws_mqtt_protocol_adapter_new_from_5(allocator, &protocol_adapter_options, fixture->mqtt5_fixture.client); - AWS_FATAL_ASSERT(fixture->protocol_adapter != NULL); - - aws_array_list_init_dynamic(&fixture->incoming_publish_events, allocator, 10, sizeof(struct request_response_protocol_adapter_incoming_publish_event_record)); - aws_array_list_init_dynamic(&fixture->connection_events, allocator, 10, sizeof(struct request_response_protocol_adapter_connection_event_record)); - aws_array_list_init_dynamic(&fixture->subscription_events, allocator, 10, sizeof(struct request_response_protocol_adapter_subscription_event_record)); - - aws_mutex_init(&fixture->lock); - aws_condition_variable_init(&fixture->signal); - - return AWS_OP_SUCCESS; -} - -static bool s_is_adapter_terminated(void *context) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = context; - - return fixture->adapter_terminated; -} - -static void s_aws_mqtt5_to_mqtt3_adapter_test_fixture_clean_up(struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { - aws_mqtt_protocol_adapter_delete(fixture->protocol_adapter); - - aws_mutex_lock(&fixture->lock); - aws_condition_variable_wait_pred(&fixture->signal, &fixture->signal, s_is_adapter_terminated, fixture); - aws_mutex_unlock(&fixture->lock); - - aws_mqtt5_client_mock_test_fixture_clean_up(&fixture->mqtt5_fixture); - - for (size_t i = 0; i < aws_array_list_length(&fixture->subscription_events); ++i) { - struct request_response_protocol_adapter_subscription_event_record record; - aws_array_list_get_at(&fixture->subscription_events, &record, i); - s_request_response_protocol_adapter_incoming_subscription_event_record_cleanup(&record); - } - aws_array_list_clean_up(&fixture->subscription_events); - - for (size_t i = 0; i < aws_array_list_length(&fixture->incoming_publish_events); ++i) { - struct request_response_protocol_adapter_incoming_publish_event_record record; - aws_array_list_get_at(&fixture->incoming_publish_events, &record, i); - s_request_response_protocol_adapter_incoming_publish_event_record_cleanup(&record); - } - aws_array_list_clean_up(&fixture->incoming_publish_events); - - aws_array_list_clean_up(&fixture->connection_events); - - aws_mutex_clean_up(&fixture->lock); - aws_condition_variable_clean_up(&fixture->signal); -} From 384a380a2b374851d415f140ff87cb224db41392 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 18 Jan 2024 15:04:30 -0800 Subject: [PATCH 007/124] Initial subscribe tests --- tests/CMakeLists.txt | 6 +- .../request_response_protocol_adapter_tests.c | 101 ++++++++++++++++-- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f1d90e22..500cf8fb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -443,9 +443,9 @@ add_test_case(mqtt_subscription_set_publish_multi_level_wildcards) add_test_case(mqtt_subscription_set_get_subscriptions) add_test_case(request_response_mqtt5_protocol_adapter_subscribe_success) -#add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_error_code) -#add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code) -#add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_timeout) +add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_error_code) +add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code) +add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_timeout) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/request_response_protocol_adapter_tests.c b/tests/request_response_protocol_adapter_tests.c index da53747b..a5d3d1e1 100644 --- a/tests/request_response_protocol_adapter_tests.c +++ b/tests/request_response_protocol_adapter_tests.c @@ -319,16 +319,52 @@ static void s_wait_for_incoming_publish_events_contains(struct aws_request_respo aws_mutex_unlock(&fixture->lock); } -static int s_request_response_mqtt5_protocol_adapter_subscribe_success_fn(struct aws_allocator *allocator, void *ctx) { - (void)ctx; +enum protocol_adapter_operation_test_type { + PAOTT_SUCCESS, + PAOTT_FAILURE_TIMEOUT, + PAOTT_FAILURE_REASON_CODE, + PAOTT_FAILURE_ERROR_CODE, +}; + +static enum aws_mqtt5_suback_reason_code s_failed_suback_reason_codes[] = { + AWS_MQTT5_SARC_NOT_AUTHORIZED, +}; + +static int s_aws_mqtt5_server_send_failed_suback_on_subscribe( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data) { + (void)packet; + (void)user_data; + struct aws_mqtt5_packet_subscribe_view *subscribe_view = packet; + + struct aws_mqtt5_packet_suback_view suback_view = { + .packet_id = subscribe_view->packet_id, + .reason_code_count = AWS_ARRAY_SIZE(s_failed_suback_reason_codes), + .reason_codes = s_failed_suback_reason_codes, + }; + + return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view); +} + +static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test(struct aws_allocator *allocator, enum protocol_adapter_operation_test_type test_type) { aws_mqtt_library_init(allocator); struct mqtt5_client_test_options test_options; aws_mqtt5_client_test_init_default_options(&test_options); - test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = - aws_mqtt5_server_send_suback_on_subscribe; + if (test_type == PAOTT_SUCCESS) { + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + aws_mqtt5_server_send_suback_on_subscribe; + } else if (test_type == PAOTT_FAILURE_REASON_CODE) { + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + s_aws_mqtt5_server_send_failed_suback_on_subscribe; + } + + if (test_type == PAOTT_FAILURE_ERROR_CODE) { + test_options.client_options.offline_queue_behavior = AWS_MQTT5_COQBT_FAIL_ALL_ON_DISCONNECT; + } struct aws_mqtt5_client_mqtt5_mock_test_fixture_options mqtt5_test_fixture_options = { .client_options = &test_options.client_options, @@ -339,19 +375,22 @@ static int s_request_response_mqtt5_protocol_adapter_subscribe_success_fn(struct ASSERT_SUCCESS(s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; - ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + if (test_type != PAOTT_FAILURE_ERROR_CODE) { + ASSERT_SUCCESS(aws_mqtt5_client_start(client)); + + aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + } struct request_response_protocol_adapter_subscription_event_record expected_outcome = { - .event_type = AWS_PASET_SUBSCRIBE_SUCCESS, + .event_type = (test_type == PAOTT_SUCCESS) ? AWS_PASET_SUBSCRIBE_SUCCESS : AWS_PASET_SUBSCRIBE_FAILURE, }; aws_byte_buf_init_copy_from_cursor(&expected_outcome.topic_filter, allocator, aws_byte_cursor_from_c_str("hello/world")); struct aws_protocol_adapter_subscribe_options subscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), - .ack_timeout_seconds = 5, + .ack_timeout_seconds = 2, }; aws_mqtt_protocol_adapter_subscribe(fixture.protocol_adapter, &subscribe_options); @@ -367,6 +406,50 @@ static int s_request_response_mqtt5_protocol_adapter_subscribe_success_fn(struct return AWS_OP_SUCCESS; } +static int s_request_response_mqtt5_protocol_adapter_subscribe_success_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_subscribe_test(allocator, PAOTT_SUCCESS)); + + return AWS_OP_SUCCESS; +} + AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_subscribe_success, - s_request_response_mqtt5_protocol_adapter_subscribe_success_fn) \ No newline at end of file + s_request_response_mqtt5_protocol_adapter_subscribe_success_fn) + +static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_timeout_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_TIMEOUT)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_subscribe_failure_timeout, + s_request_response_mqtt5_protocol_adapter_subscribe_failure_timeout_fn) + +static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_REASON_CODE)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code, + s_request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code_fn) + +static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_error_code_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_ERROR_CODE)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_subscribe_failure_error_code, + s_request_response_mqtt5_protocol_adapter_subscribe_failure_error_code_fn) From 86de21944016253628ec9159a2da88f75fc66652 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 19 Jan 2024 13:04:40 -0800 Subject: [PATCH 008/124] Tests complete --- .../request-response/protocol_adapter.h | 34 +- .../mqtt/private/request-response/weak_ref.h | 1 - source/request-response/protocol_adapter.c | 109 +-- source/request-response/weak_ref.c | 6 +- tests/CMakeLists.txt | 11 + .../request_response_protocol_adapter_tests.c | 658 ++++++++++++++++-- tests/v5/mqtt5_client_tests.c | 10 +- tests/v5/mqtt5_testing_utils.h | 10 + 8 files changed, 734 insertions(+), 105 deletions(-) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 86d5a44f..8a61d351 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -6,8 +6,8 @@ * SPDX-License-Identifier: Apache-2.0. */ -#include #include +#include #include @@ -61,10 +61,14 @@ struct aws_protocol_adapter_connection_event { bool rejoined_session; }; -typedef void(aws_protocol_adapter_subscription_event_fn)(struct aws_protocol_adapter_subscription_event *event, void *user_data); -typedef void(aws_protocol_adapter_incoming_publish_fn)(struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data); +typedef void( + aws_protocol_adapter_subscription_event_fn)(struct aws_protocol_adapter_subscription_event *event, void *user_data); +typedef void(aws_protocol_adapter_incoming_publish_fn)( + struct aws_protocol_adapter_incoming_publish_event *publish, + void *user_data); typedef void(aws_protocol_adapter_terminate_callback_fn)(void *user_data); -typedef void(aws_protocol_adapter_connection_event_fn)(struct aws_protocol_adapter_connection_event *event, void *user_data); +typedef void( + aws_protocol_adapter_connection_event_fn)(struct aws_protocol_adapter_connection_event *event, void *user_data); struct aws_mqtt_protocol_adapter_options { aws_protocol_adapter_subscription_event_fn *subscription_event_callback; @@ -93,17 +97,29 @@ struct aws_mqtt_protocol_adapter { AWS_EXTERN_C_BEGIN -AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt_client_connection *connection); +AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( + struct aws_allocator *allocator, + struct aws_mqtt_protocol_adapter_options *options, + struct aws_mqtt_client_connection *connection); -AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt5_client *client); +AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( + struct aws_allocator *allocator, + struct aws_mqtt_protocol_adapter_options *options, + struct aws_mqtt5_client *client); AWS_MQTT_API void aws_mqtt_protocol_adapter_delete(struct aws_mqtt_protocol_adapter *adapter); -AWS_MQTT_API int aws_mqtt_protocol_adapter_subscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_subscribe_options *options); +AWS_MQTT_API int aws_mqtt_protocol_adapter_subscribe( + struct aws_mqtt_protocol_adapter *adapter, + struct aws_protocol_adapter_subscribe_options *options); -AWS_MQTT_API int aws_mqtt_protocol_adapter_unsubscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_unsubscribe_options *options); +AWS_MQTT_API int aws_mqtt_protocol_adapter_unsubscribe( + struct aws_mqtt_protocol_adapter *adapter, + struct aws_protocol_adapter_unsubscribe_options *options); -AWS_MQTT_API int aws_mqtt_protocol_adapter_publish(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_publish_options *options); +AWS_MQTT_API int aws_mqtt_protocol_adapter_publish( + struct aws_mqtt_protocol_adapter *adapter, + struct aws_protocol_adapter_publish_options *options); AWS_EXTERN_C_END diff --git a/include/aws/mqtt/private/request-response/weak_ref.h b/include/aws/mqtt/private/request-response/weak_ref.h index 34968874..0872de69 100644 --- a/include/aws/mqtt/private/request-response/weak_ref.h +++ b/include/aws/mqtt/private/request-response/weak_ref.h @@ -26,5 +26,4 @@ AWS_MQTT_API void aws_weak_ref_zero_reference(struct aws_weak_ref *weak_ref); AWS_EXTERN_C_END - #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_WEAK_REF_H */ diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index d1a649b0..a4dc656c 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -21,9 +21,9 @@ * * Entries are not tracked with the exception of eventstream impl which needs the stream handles to close. * A subscribe failure should not trigger an unsubscribe, only notify the status callback. - * Subscription event callback should be {subscribe_success, subscribe_failure, unsubscribe_success, unsubscribe_failure}. - * The sub manager is responsible for calling Unsubscribe on all its entries when shutting down (before releasing - * hold of the adapter). + * Subscription event callback should be {subscribe_success, subscribe_failure, unsubscribe_success, + * unsubscribe_failure}. The sub manager is responsible for calling Unsubscribe on all its entries when shutting down + * (before releasing hold of the adapter). * * How do we know not to retry unsubscribe failures because a subscribe came in? Well, we don't retry failures; let * the manager make that decision. No retry when the weak ref is zeroed either. The potential for things to go wrong @@ -33,8 +33,10 @@ * decide what to do. */ - -struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt_client_connection *connection) { +struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( + struct aws_allocator *allocator, + struct aws_mqtt_protocol_adapter_options *options, + struct aws_mqtt_client_connection *connection) { (void)allocator; (void)options; (void)connection; @@ -71,8 +73,12 @@ struct aws_mqtt_protocol_adapter_5_subscription_op_data { struct aws_weak_ref *callback_ref; }; -static struct aws_mqtt_protocol_adapter_5_subscription_op_data *s_aws_mqtt_protocol_adapter_5_subscription_op_data_new(struct aws_allocator *allocator, struct aws_byte_cursor topic_filter, struct aws_weak_ref *callback_ref) { - struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_subscription_op_data)); +static struct aws_mqtt_protocol_adapter_5_subscription_op_data *s_aws_mqtt_protocol_adapter_5_subscription_op_data_new( + struct aws_allocator *allocator, + struct aws_byte_cursor topic_filter, + struct aws_weak_ref *callback_ref) { + struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_subscription_op_data)); subscribe_data->allocator = allocator; subscribe_data->callback_ref = aws_weak_ref_acquire(callback_ref); @@ -81,7 +87,8 @@ static struct aws_mqtt_protocol_adapter_5_subscription_op_data *s_aws_mqtt_proto return subscribe_data; } -static void s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data) { +static void s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete( + struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data) { aws_weak_ref_release(subscribe_data->callback_ref); aws_byte_buf_clean_up(&subscribe_data->topic_filter); @@ -90,9 +97,10 @@ static void s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(struct aws /* Subscribe */ -static void s_protocol_adapter_5_subscribe_completion(const struct aws_mqtt5_packet_suback_view *suback, - int error_code, - void *complete_ctx) { +static void s_protocol_adapter_5_subscribe_completion( + const struct aws_mqtt5_packet_suback_view *suback, + int error_code, + void *complete_ctx) { struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = complete_ctx; struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(subscribe_data->callback_ref); @@ -100,7 +108,8 @@ static void s_protocol_adapter_5_subscribe_completion(const struct aws_mqtt5_pac goto done; } - bool success = error_code == AWS_ERROR_SUCCESS && suback != NULL && suback->reason_code_count == 1 && suback->reason_codes[0] <= AWS_MQTT5_SARC_GRANTED_QOS_2; + bool success = error_code == AWS_ERROR_SUCCESS && suback != NULL && suback->reason_code_count == 1 && + suback->reason_codes[0] <= AWS_MQTT5_SARC_GRANTED_QOS_2; struct aws_protocol_adapter_subscription_event subscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), @@ -117,7 +126,9 @@ static void s_protocol_adapter_5_subscribe_completion(const struct aws_mqtt5_pac int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = s_aws_mqtt_protocol_adapter_5_subscription_op_data_new(adapter->allocator, options->topic_filter, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = + s_aws_mqtt_protocol_adapter_5_subscription_op_data_new( + adapter->allocator, options->topic_filter, adapter->callback_ref); struct aws_mqtt5_subscription_view subscription_view = { .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, @@ -150,9 +161,10 @@ int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adap /* Unsubscribe */ -static void s_protocol_adapter_5_unsubscribe_completion(const struct aws_mqtt5_packet_unsuback_view *unsuback, - int error_code, - void *complete_ctx) { +static void s_protocol_adapter_5_unsubscribe_completion( + const struct aws_mqtt5_packet_unsuback_view *unsuback, + int error_code, + void *complete_ctx) { struct aws_mqtt_protocol_adapter_5_subscription_op_data *unsubscribe_data = complete_ctx; struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(unsubscribe_data->callback_ref); @@ -160,7 +172,8 @@ static void s_protocol_adapter_5_unsubscribe_completion(const struct aws_mqtt5_p goto done; } - bool success = error_code == AWS_ERROR_SUCCESS && unsuback != NULL && unsuback->reason_code_count == 1 && unsuback->reason_codes[0] < 128; + bool success = error_code == AWS_ERROR_SUCCESS && unsuback != NULL && unsuback->reason_code_count == 1 && + unsuback->reason_codes[0] < 128; struct aws_protocol_adapter_subscription_event unsubscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), @@ -177,7 +190,9 @@ static void s_protocol_adapter_5_unsubscribe_completion(const struct aws_mqtt5_p int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_5_subscription_op_data *unsubscribe_data = s_aws_mqtt_protocol_adapter_5_subscription_op_data_new(adapter->allocator, options->topic_filter, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_5_subscription_op_data *unsubscribe_data = + s_aws_mqtt_protocol_adapter_5_subscription_op_data_new( + adapter->allocator, options->topic_filter, adapter->callback_ref); struct aws_mqtt5_packet_unsubscribe_view unsubscribe_view = { .topic_filters = &options->topic_filter, @@ -213,8 +228,12 @@ struct aws_mqtt_protocol_adapter_5_publish_op_data { void *user_data; }; -static struct aws_mqtt_protocol_adapter_5_publish_op_data *s_aws_mqtt_protocol_adapter_5_publish_op_data_new(struct aws_allocator *allocator, const struct aws_protocol_adapter_publish_options *publish_options, struct aws_weak_ref *callback_ref) { - struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_publish_op_data)); +static struct aws_mqtt_protocol_adapter_5_publish_op_data *s_aws_mqtt_protocol_adapter_5_publish_op_data_new( + struct aws_allocator *allocator, + const struct aws_protocol_adapter_publish_options *publish_options, + struct aws_weak_ref *callback_ref) { + struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_publish_op_data)); publish_data->allocator = allocator; publish_data->callback_ref = aws_weak_ref_acquire(callback_ref); @@ -224,7 +243,8 @@ static struct aws_mqtt_protocol_adapter_5_publish_op_data *s_aws_mqtt_protocol_a return publish_data; } -static void s_aws_mqtt_protocol_adapter_5_publish_op_data_delete(struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data) { +static void s_aws_mqtt_protocol_adapter_5_publish_op_data_delete( + struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data) { aws_weak_ref_release(publish_data->callback_ref); aws_mem_release(publish_data->allocator, publish_data); @@ -259,13 +279,11 @@ static void s_protocol_adapter_5_publish_completion( int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = s_aws_mqtt_protocol_adapter_5_publish_op_data_new(adapter->allocator, options, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = + s_aws_mqtt_protocol_adapter_5_publish_op_data_new(adapter->allocator, options, adapter->callback_ref); struct aws_mqtt5_packet_publish_view publish_view = { - .topic = options->topic, - .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, - .payload = options->payload - }; + .topic = options->topic, .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, .payload = options->payload}; struct aws_mqtt5_publish_completion_options completion_options = { .ack_timeout_seconds_override = options->ack_timeout_seconds, @@ -286,13 +304,13 @@ int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapte return AWS_OP_ERR; } -static bool s_protocol_adapter_mqtt5_listener_publish_received(const struct aws_mqtt5_packet_publish_view *publish, void *user_data) { +static bool s_protocol_adapter_mqtt5_listener_publish_received( + const struct aws_mqtt5_packet_publish_view *publish, + void *user_data) { struct aws_mqtt_protocol_adapter_5_impl *adapter = user_data; struct aws_protocol_adapter_incoming_publish_event publish_event = { - .topic = publish->topic, - .payload = publish->payload - }; + .topic = publish->topic, .payload = publish->payload}; (*adapter->config.incoming_publish_callback)(&publish_event, adapter->config.user_data); @@ -346,8 +364,12 @@ static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt5_vtable = .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_5_publish, }; -struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt5_client *client) { - struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_impl)); +struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( + struct aws_allocator *allocator, + struct aws_mqtt_protocol_adapter_options *options, + struct aws_mqtt5_client *client) { + struct aws_mqtt_protocol_adapter_5_impl *adapter = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_impl)); adapter->allocator = allocator; adapter->base.impl = adapter; @@ -359,12 +381,11 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5(struct aw struct aws_mqtt5_listener_config listener_options = { .client = client, - .listener_callbacks = { - .listener_publish_received_handler = s_protocol_adapter_mqtt5_listener_publish_received, - .listener_publish_received_handler_user_data = adapter, - .lifecycle_event_handler = s_protocol_adapter_mqtt5_lifecycle_event_callback, - .lifecycle_event_handler_user_data = adapter - }, + .listener_callbacks = + {.listener_publish_received_handler = s_protocol_adapter_mqtt5_listener_publish_received, + .listener_publish_received_handler_user_data = adapter, + .lifecycle_event_handler = s_protocol_adapter_mqtt5_lifecycle_event_callback, + .lifecycle_event_handler_user_data = adapter}, .termination_callback = s_protocol_adapter_mqtt5_listener_termination_callback, .termination_callback_user_data = adapter, }; @@ -378,14 +399,20 @@ void aws_mqtt_protocol_adapter_delete(struct aws_mqtt_protocol_adapter *adapter) (*adapter->vtable->aws_mqtt_protocol_adapter_delete_fn)(adapter->impl); } -int aws_mqtt_protocol_adapter_subscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_subscribe_options *options) { +int aws_mqtt_protocol_adapter_subscribe( + struct aws_mqtt_protocol_adapter *adapter, + struct aws_protocol_adapter_subscribe_options *options) { return (*adapter->vtable->aws_mqtt_protocol_adapter_subscribe_fn)(adapter->impl, options); } -int aws_mqtt_protocol_adapter_unsubscribe(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_unsubscribe_options *options) { +int aws_mqtt_protocol_adapter_unsubscribe( + struct aws_mqtt_protocol_adapter *adapter, + struct aws_protocol_adapter_unsubscribe_options *options) { return (*adapter->vtable->aws_mqtt_protocol_adapter_unsubscribe_fn)(adapter->impl, options); } -int aws_mqtt_protocol_adapter_publish(struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_publish_options *options) { +int aws_mqtt_protocol_adapter_publish( + struct aws_mqtt_protocol_adapter *adapter, + struct aws_protocol_adapter_publish_options *options) { return (*adapter->vtable->aws_mqtt_protocol_adapter_publish_fn)(adapter->impl, options); } diff --git a/source/request-response/weak_ref.c b/source/request-response/weak_ref.c index 97561ea9..949014e7 100644 --- a/source/request-response/weak_ref.c +++ b/source/request-response/weak_ref.c @@ -1,7 +1,7 @@ /** -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0. -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 500cf8fb..587495c4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -446,6 +446,17 @@ add_test_case(request_response_mqtt5_protocol_adapter_subscribe_success) add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_error_code) add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code) add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_timeout) +add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_success) +add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_failure_error_code) +add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code) +add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_failure_timeout) +add_test_case(request_response_mqtt5_protocol_adapter_publish_success) +add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_error_code) +add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_reason_code) +add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_timeout) +add_test_case(request_response_mqtt5_protocol_adapter_connection_event_sequence) +add_test_case(request_response_mqtt5_protocol_adapter_incoming_publish) +add_test_case(request_response_mqtt5_protocol_adapter_shutdown_while_pending) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/request_response_protocol_adapter_tests.c b/tests/request_response_protocol_adapter_tests.c index a5d3d1e1..572c8345 100644 --- a/tests/request_response_protocol_adapter_tests.c +++ b/tests/request_response_protocol_adapter_tests.c @@ -1,7 +1,7 @@ /** -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0. -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ #include "v5/mqtt5_testing_utils.h" #include @@ -25,7 +25,8 @@ static void s_request_response_protocol_adapter_incoming_publish_event_record_in aws_byte_buf_init_copy_from_cursor(&record->payload, allocator, payload); } -static void s_request_response_protocol_adapter_incoming_publish_event_record_cleanup(struct request_response_protocol_adapter_incoming_publish_event_record *record) { +static void s_request_response_protocol_adapter_incoming_publish_event_record_clean_up( + struct request_response_protocol_adapter_incoming_publish_event_record *record) { aws_byte_buf_clean_up(&record->topic); aws_byte_buf_clean_up(&record->payload); } @@ -48,7 +49,8 @@ static void s_request_response_protocol_adapter_subscription_event_record_init( aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); } -static void s_request_response_protocol_adapter_subscription_event_record_cleanup(struct request_response_protocol_adapter_subscription_event_record *record) { +static void s_request_response_protocol_adapter_subscription_event_record_cleanup( + struct request_response_protocol_adapter_subscription_event_record *record) { aws_byte_buf_clean_up(&record->topic_filter); } @@ -61,6 +63,7 @@ struct aws_request_response_mqtt5_adapter_test_fixture { struct aws_array_list incoming_publish_events; struct aws_array_list connection_events; struct aws_array_list subscription_events; + struct aws_array_list publish_results; bool adapter_terminated; @@ -68,14 +71,14 @@ struct aws_request_response_mqtt5_adapter_test_fixture { struct aws_condition_variable signal; }; - -static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event(struct aws_protocol_adapter_subscription_event *event, void *user_data) { +static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event( + struct aws_protocol_adapter_subscription_event *event, + void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; - struct request_response_protocol_adapter_subscription_event_record record = { - .event_type = event->event_type - }; - s_request_response_protocol_adapter_subscription_event_record_init(&record, fixture->allocator, event->topic_filter); + struct request_response_protocol_adapter_subscription_event_record record = {.event_type = event->event_type}; + s_request_response_protocol_adapter_subscription_event_record_init( + &record, fixture->allocator, event->topic_filter); aws_mutex_lock(&fixture->lock); aws_array_list_push_back(&fixture->subscription_events, &record); @@ -83,12 +86,15 @@ static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event(struct aws_pr aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_incoming_publish(struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data) { +static void s_rr_mqtt5_protocol_adapter_test_on_incoming_publish( + struct aws_protocol_adapter_incoming_publish_event *publish, + void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; struct request_response_protocol_adapter_incoming_publish_event_record record; AWS_ZERO_STRUCT(record); - s_request_response_protocol_adapter_incoming_publish_event_record_init(&record, fixture->allocator, publish->topic, publish->payload); + s_request_response_protocol_adapter_incoming_publish_event_record_init( + &record, fixture->allocator, publish->topic, publish->payload); aws_mutex_lock(&fixture->lock); aws_array_list_push_back(&fixture->incoming_publish_events, &record); @@ -105,13 +111,13 @@ static void s_rr_mqtt5_protocol_adapter_test_on_terminate_callback(void *user_da aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_connection_event(struct aws_protocol_adapter_connection_event *event, void *user_data) { +static void s_rr_mqtt5_protocol_adapter_test_on_connection_event( + struct aws_protocol_adapter_connection_event *event, + void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; struct request_response_protocol_adapter_connection_event_record record = { - .event_type = event->event_type, - .rejoined_session = event->rejoined_session - }; + .event_type = event->event_type, .rejoined_session = event->rejoined_session}; aws_mutex_lock(&fixture->lock); aws_array_list_push_back(&fixture->connection_events, &record); @@ -119,6 +125,15 @@ static void s_rr_mqtt5_protocol_adapter_test_on_connection_event(struct aws_prot aws_condition_variable_notify_all(&fixture->signal); } +static void s_rr_mqtt5_protocol_adapter_test_on_publish_result(bool success, void *user_data) { + struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + + aws_mutex_lock(&fixture->lock); + aws_array_list_push_back(&fixture->publish_results, &success); + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + static int s_aws_request_response_mqtt5_adapter_test_fixture_init( struct aws_request_response_mqtt5_adapter_test_fixture *fixture, struct aws_allocator *allocator, @@ -137,15 +152,28 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( .incoming_publish_callback = s_rr_mqtt5_protocol_adapter_test_on_incoming_publish, .terminate_callback = s_rr_mqtt5_protocol_adapter_test_on_terminate_callback, .connection_event_callback = s_rr_mqtt5_protocol_adapter_test_on_connection_event, - .user_data = fixture - }; + .user_data = fixture}; - fixture->protocol_adapter = aws_mqtt_protocol_adapter_new_from_5(allocator, &protocol_adapter_options, fixture->mqtt5_fixture.client); + fixture->protocol_adapter = + aws_mqtt_protocol_adapter_new_from_5(allocator, &protocol_adapter_options, fixture->mqtt5_fixture.client); AWS_FATAL_ASSERT(fixture->protocol_adapter != NULL); - aws_array_list_init_dynamic(&fixture->incoming_publish_events, allocator, 10, sizeof(struct request_response_protocol_adapter_incoming_publish_event_record)); - aws_array_list_init_dynamic(&fixture->connection_events, allocator, 10, sizeof(struct request_response_protocol_adapter_connection_event_record)); - aws_array_list_init_dynamic(&fixture->subscription_events, allocator, 10, sizeof(struct request_response_protocol_adapter_subscription_event_record)); + aws_array_list_init_dynamic( + &fixture->incoming_publish_events, + allocator, + 10, + sizeof(struct request_response_protocol_adapter_incoming_publish_event_record)); + aws_array_list_init_dynamic( + &fixture->connection_events, + allocator, + 10, + sizeof(struct request_response_protocol_adapter_connection_event_record)); + aws_array_list_init_dynamic( + &fixture->subscription_events, + allocator, + 10, + sizeof(struct request_response_protocol_adapter_subscription_event_record)); + aws_array_list_init_dynamic(&fixture->publish_results, allocator, 10, sizeof(bool)); aws_mutex_init(&fixture->lock); aws_condition_variable_init(&fixture->signal); @@ -159,12 +187,22 @@ static bool s_is_adapter_terminated(void *context) { return fixture->adapter_terminated; } -static void s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { - aws_mqtt_protocol_adapter_delete(fixture->protocol_adapter); +static void s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters( + struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { + if (fixture->protocol_adapter != NULL) { + aws_mqtt_protocol_adapter_delete(fixture->protocol_adapter); - aws_mutex_lock(&fixture->lock); - aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_is_adapter_terminated, fixture); - aws_mutex_unlock(&fixture->lock); + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_is_adapter_terminated, fixture); + aws_mutex_unlock(&fixture->lock); + fixture->protocol_adapter = NULL; + } +} + +static void s_aws_request_response_mqtt5_adapter_test_fixture_clean_up( + struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { + + s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters(fixture); aws_mqtt5_client_mock_test_fixture_clean_up(&fixture->mqtt5_fixture); @@ -178,11 +216,12 @@ static void s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(struct aw for (size_t i = 0; i < aws_array_list_length(&fixture->incoming_publish_events); ++i) { struct request_response_protocol_adapter_incoming_publish_event_record record; aws_array_list_get_at(&fixture->incoming_publish_events, &record, i); - s_request_response_protocol_adapter_incoming_publish_event_record_cleanup(&record); + s_request_response_protocol_adapter_incoming_publish_event_record_clean_up(&record); } aws_array_list_clean_up(&fixture->incoming_publish_events); aws_array_list_clean_up(&fixture->connection_events); + aws_array_list_clean_up(&fixture->publish_results); aws_mutex_clean_up(&fixture->lock); aws_condition_variable_clean_up(&fixture->signal); @@ -206,7 +245,8 @@ static bool s_do_subscription_events_contain(void *context) { if (record.event_type == wait_context->expected_event->event_type) { struct aws_byte_cursor record_topic_filter = aws_byte_cursor_from_buf(&record.topic_filter); - struct aws_byte_cursor expected_topic_filter = aws_byte_cursor_from_buf(&wait_context->expected_event->topic_filter); + struct aws_byte_cursor expected_topic_filter = + aws_byte_cursor_from_buf(&wait_context->expected_event->topic_filter); if (aws_byte_cursor_eq(&record_topic_filter, &expected_topic_filter)) { ++found; } @@ -216,9 +256,10 @@ static bool s_do_subscription_events_contain(void *context) { return found >= wait_context->expected_count; } -static void s_wait_for_subscription_events_contains(struct aws_request_response_mqtt5_adapter_test_fixture *fixture, - struct request_response_protocol_adapter_subscription_event_record *expected_event, - size_t expected_count) { +static void s_wait_for_subscription_events_contains( + struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct request_response_protocol_adapter_subscription_event_record *expected_event, + size_t expected_count) { struct test_subscription_event_wait_context context = { .expected_event = expected_event, @@ -247,7 +288,8 @@ static bool s_do_connection_events_contain(void *context) { struct request_response_protocol_adapter_connection_event_record record; aws_array_list_get_at(&wait_context->fixture->connection_events, &record, i); - if (record.event_type == wait_context->expected_event->event_type && record.rejoined_session == wait_context->expected_event->rejoined_session) { + if (record.event_type == wait_context->expected_event->event_type && + record.rejoined_session == wait_context->expected_event->rejoined_session) { ++found; } } @@ -255,9 +297,10 @@ static bool s_do_connection_events_contain(void *context) { return found >= wait_context->expected_count; } -static void s_wait_for_connection_events_contains(struct aws_request_response_mqtt5_adapter_test_fixture *fixture, - struct request_response_protocol_adapter_connection_event_record *expected_event, - size_t expected_count) { +static void s_wait_for_connection_events_contains( + struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct request_response_protocol_adapter_connection_event_record *expected_event, + size_t expected_count) { struct test_connection_event_wait_context context = { .expected_event = expected_event, @@ -304,9 +347,10 @@ static bool s_do_incoming_publish_events_contain(void *context) { return found >= wait_context->expected_count; } -static void s_wait_for_incoming_publish_events_contains(struct aws_request_response_mqtt5_adapter_test_fixture *fixture, - struct request_response_protocol_adapter_incoming_publish_event_record *expected_event, - size_t expected_count) { +static void s_wait_for_incoming_publish_events_contains( + struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct request_response_protocol_adapter_incoming_publish_event_record *expected_event, + size_t expected_count) { struct test_incoming_publish_event_wait_context context = { .expected_event = expected_event, @@ -319,6 +363,46 @@ static void s_wait_for_incoming_publish_events_contains(struct aws_request_respo aws_mutex_unlock(&fixture->lock); } +struct test_publish_result_wait_context { + bool expected_success; + size_t expected_count; + struct aws_request_response_mqtt5_adapter_test_fixture *fixture; +}; + +static bool s_do_publish_results_contain(void *context) { + struct test_publish_result_wait_context *wait_context = context; + + size_t found = 0; + + size_t num_events = aws_array_list_length(&wait_context->fixture->publish_results); + for (size_t i = 0; i < num_events; ++i) { + bool success = false; + aws_array_list_get_at(&wait_context->fixture->publish_results, &success, i); + + if (success == wait_context->expected_success) { + ++found; + } + } + + return found >= wait_context->expected_count; +} + +static void s_wait_for_publish_results_contains( + struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + bool success, + size_t expected_count) { + + struct test_publish_result_wait_context context = { + .expected_success = success, + .expected_count = expected_count, + .fixture = fixture, + }; + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_do_publish_results_contain, &context); + aws_mutex_unlock(&fixture->lock); +} + enum protocol_adapter_operation_test_type { PAOTT_SUCCESS, PAOTT_FAILURE_TIMEOUT, @@ -348,7 +432,9 @@ static int s_aws_mqtt5_server_send_failed_suback_on_subscribe( return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view); } -static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test(struct aws_allocator *allocator, enum protocol_adapter_operation_test_type test_type) { +static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( + struct aws_allocator *allocator, + enum protocol_adapter_operation_test_type test_type) { aws_mqtt_library_init(allocator); struct mqtt5_client_test_options test_options; @@ -372,7 +458,8 @@ static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test(struct aw }; struct aws_request_response_mqtt5_adapter_test_fixture fixture; - ASSERT_SUCCESS(s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + ASSERT_SUCCESS( + s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; @@ -386,7 +473,8 @@ static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test(struct aw .event_type = (test_type == PAOTT_SUCCESS) ? AWS_PASET_SUBSCRIBE_SUCCESS : AWS_PASET_SUBSCRIBE_FAILURE, }; - aws_byte_buf_init_copy_from_cursor(&expected_outcome.topic_filter, allocator, aws_byte_cursor_from_c_str("hello/world")); + aws_byte_buf_init_copy_from_cursor( + &expected_outcome.topic_filter, allocator, aws_byte_cursor_from_c_str("hello/world")); struct aws_protocol_adapter_subscribe_options subscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -418,7 +506,9 @@ AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_subscribe_success, s_request_response_mqtt5_protocol_adapter_subscribe_success_fn) -static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_timeout_fn(struct aws_allocator *allocator, void *ctx) { +static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_timeout_fn( + struct aws_allocator *allocator, + void *ctx) { (void)ctx; ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_TIMEOUT)); @@ -430,7 +520,9 @@ AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_subscribe_failure_timeout, s_request_response_mqtt5_protocol_adapter_subscribe_failure_timeout_fn) -static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code_fn(struct aws_allocator *allocator, void *ctx) { +static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code_fn( + struct aws_allocator *allocator, + void *ctx) { (void)ctx; ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_REASON_CODE)); @@ -442,7 +534,9 @@ AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code, s_request_response_mqtt5_protocol_adapter_subscribe_failure_reason_code_fn) -static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_error_code_fn(struct aws_allocator *allocator, void *ctx) { +static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_error_code_fn( + struct aws_allocator *allocator, + void *ctx) { (void)ctx; ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_ERROR_CODE)); @@ -453,3 +547,475 @@ static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_error_cod AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_subscribe_failure_error_code, s_request_response_mqtt5_protocol_adapter_subscribe_failure_error_code_fn) + +static enum aws_mqtt5_unsuback_reason_code s_failed_unsuback_reason_codes[] = { + AWS_MQTT5_UARC_NOT_AUTHORIZED, +}; + +static int s_aws_mqtt5_server_send_failed_unsuback_on_unsubscribe( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data) { + (void)packet; + (void)user_data; + + struct aws_mqtt5_packet_subscribe_view *unsubscribe_view = packet; + + struct aws_mqtt5_packet_unsuback_view unsuback_view = { + .packet_id = unsubscribe_view->packet_id, + .reason_code_count = AWS_ARRAY_SIZE(s_failed_unsuback_reason_codes), + .reason_codes = s_failed_unsuback_reason_codes, + }; + + return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_UNSUBACK, &unsuback_view); +} + +static int s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test( + struct aws_allocator *allocator, + enum protocol_adapter_operation_test_type test_type) { + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options test_options; + aws_mqtt5_client_test_init_default_options(&test_options); + + if (test_type == PAOTT_SUCCESS) { + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = + aws_mqtt5_mock_server_handle_unsubscribe_unsuback_success; + } else if (test_type == PAOTT_FAILURE_REASON_CODE) { + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = + s_aws_mqtt5_server_send_failed_unsuback_on_unsubscribe; + } + + if (test_type == PAOTT_FAILURE_ERROR_CODE) { + test_options.client_options.offline_queue_behavior = AWS_MQTT5_COQBT_FAIL_ALL_ON_DISCONNECT; + } + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options mqtt5_test_fixture_options = { + .client_options = &test_options.client_options, + .server_function_table = &test_options.server_function_table, + }; + + struct aws_request_response_mqtt5_adapter_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + + struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + + if (test_type != PAOTT_FAILURE_ERROR_CODE) { + ASSERT_SUCCESS(aws_mqtt5_client_start(client)); + + aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + } + + struct request_response_protocol_adapter_subscription_event_record expected_outcome = { + .event_type = (test_type == PAOTT_SUCCESS) ? AWS_PASET_UNSUBSCRIBE_SUCCESS : AWS_PASET_UNSUBSCRIBE_FAILURE, + }; + + aws_byte_buf_init_copy_from_cursor( + &expected_outcome.topic_filter, allocator, aws_byte_cursor_from_c_str("hello/world")); + + struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { + .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), + .ack_timeout_seconds = 2, + }; + + aws_mqtt_protocol_adapter_unsubscribe(fixture.protocol_adapter, &unsubscribe_options); + + s_wait_for_subscription_events_contains(&fixture, &expected_outcome, 1); + + s_request_response_protocol_adapter_subscription_event_record_cleanup(&expected_outcome); + + s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_request_response_mqtt5_protocol_adapter_unsubscribe_success_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test(allocator, PAOTT_SUCCESS)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_unsubscribe_success, + s_request_response_mqtt5_protocol_adapter_unsubscribe_success_fn) + +static int s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_timeout_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test(allocator, PAOTT_FAILURE_TIMEOUT)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_unsubscribe_failure_timeout, + s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_timeout_fn) + +static int s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test(allocator, PAOTT_FAILURE_REASON_CODE)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code, + s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_fn) + +static int s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_error_code_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test(allocator, PAOTT_FAILURE_ERROR_CODE)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_unsubscribe_failure_error_code, + s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_error_code_fn) + +static int s_aws_mqtt5_server_send_failed_puback_on_publish( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data) { + (void)packet; + (void)user_data; + + struct aws_mqtt5_packet_publish_view *publish_view = packet; + + if (publish_view->qos == AWS_MQTT5_QOS_AT_LEAST_ONCE) { + struct aws_mqtt5_packet_puback_view puback_view = { + .packet_id = publish_view->packet_id, + .reason_code = AWS_MQTT5_PARC_NOT_AUTHORIZED, + }; + + return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_PUBACK, &puback_view); + } + + return AWS_OP_SUCCESS; +} + +static int s_do_request_response_mqtt5_protocol_adapter_publish_test( + struct aws_allocator *allocator, + enum protocol_adapter_operation_test_type test_type) { + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options test_options; + aws_mqtt5_client_test_init_default_options(&test_options); + + if (test_type == PAOTT_SUCCESS) { + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = + aws_mqtt5_mock_server_handle_publish_puback; + } else if (test_type == PAOTT_FAILURE_REASON_CODE) { + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = + s_aws_mqtt5_server_send_failed_puback_on_publish; + } + + if (test_type == PAOTT_FAILURE_ERROR_CODE) { + test_options.client_options.offline_queue_behavior = AWS_MQTT5_COQBT_FAIL_ALL_ON_DISCONNECT; + } + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options mqtt5_test_fixture_options = { + .client_options = &test_options.client_options, + .server_function_table = &test_options.server_function_table, + }; + + struct aws_request_response_mqtt5_adapter_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + + struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + + if (test_type != PAOTT_FAILURE_ERROR_CODE) { + ASSERT_SUCCESS(aws_mqtt5_client_start(client)); + + aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + } + + struct aws_protocol_adapter_publish_options publish_options = { + .topic = aws_byte_cursor_from_c_str("hello/world"), + .payload = aws_byte_cursor_from_c_str("SomePayload"), + .ack_timeout_seconds = 2, + .completion_callback_fn = s_rr_mqtt5_protocol_adapter_test_on_publish_result, + .user_data = &fixture}; + + aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); + + s_wait_for_publish_results_contains(&fixture, test_type == PAOTT_SUCCESS, 1); + + s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_request_response_mqtt5_protocol_adapter_publish_success_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_publish_test(allocator, PAOTT_SUCCESS)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_publish_success, + s_request_response_mqtt5_protocol_adapter_publish_success_fn) + +static int s_request_response_mqtt5_protocol_adapter_publish_failure_timeout_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_publish_test(allocator, PAOTT_FAILURE_TIMEOUT)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_publish_failure_timeout, + s_request_response_mqtt5_protocol_adapter_publish_failure_timeout_fn) + +static int s_request_response_mqtt5_protocol_adapter_publish_failure_reason_code_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_publish_test(allocator, PAOTT_FAILURE_REASON_CODE)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_publish_failure_reason_code, + s_request_response_mqtt5_protocol_adapter_publish_failure_reason_code_fn) + +static int s_request_response_mqtt5_protocol_adapter_publish_failure_error_code_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_publish_test(allocator, PAOTT_FAILURE_ERROR_CODE)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_publish_failure_error_code, + s_request_response_mqtt5_protocol_adapter_publish_failure_error_code_fn) + +static int s_request_response_mqtt5_protocol_adapter_connection_event_sequence_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options test_options; + aws_mqtt5_client_test_init_default_options(&test_options); + + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_CONNECT] = + aws_mqtt5_mock_server_handle_connect_honor_session_unconditional; + test_options.client_options.session_behavior = AWS_MQTT5_CSBT_REJOIN_POST_SUCCESS; + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options mqtt5_test_fixture_options = { + .client_options = &test_options.client_options, + .server_function_table = &test_options.server_function_table, + }; + + struct aws_request_response_mqtt5_adapter_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + + struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + + struct request_response_protocol_adapter_connection_event_record online_record1 = { + .event_type = AWS_PACET_ONLINE, + .rejoined_session = false, + }; + + ASSERT_SUCCESS(aws_mqtt5_client_start(client)); + s_wait_for_connection_events_contains(&fixture, &online_record1, 1); + + struct request_response_protocol_adapter_connection_event_record offline_record = { + .event_type = AWS_PACET_OFFLINE, + }; + + ASSERT_SUCCESS(aws_mqtt5_client_stop(client, NULL, NULL)); + s_wait_for_connection_events_contains(&fixture, &offline_record, 1); + + struct request_response_protocol_adapter_connection_event_record online_record2 = { + .event_type = AWS_PACET_ONLINE, + .rejoined_session = true, + }; + + ASSERT_SUCCESS(aws_mqtt5_client_start(client)); + s_wait_for_connection_events_contains(&fixture, &online_record2, 1); + + ASSERT_SUCCESS(aws_mqtt5_client_stop(client, NULL, NULL)); + s_wait_for_connection_events_contains(&fixture, &offline_record, 2); + + s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_connection_event_sequence, + s_request_response_mqtt5_protocol_adapter_connection_event_sequence_fn) + +static int s_request_response_mqtt5_protocol_adapter_incoming_publish_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options test_options; + aws_mqtt5_client_test_init_default_options(&test_options); + + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = + aws_mqtt5_mock_server_handle_publish_puback_and_forward; + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options mqtt5_test_fixture_options = { + .client_options = &test_options.client_options, + .server_function_table = &test_options.server_function_table, + }; + + struct aws_request_response_mqtt5_adapter_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + + struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + + ASSERT_SUCCESS(aws_mqtt5_client_start(client)); + + aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + + struct request_response_protocol_adapter_incoming_publish_event_record expected_publish; + AWS_ZERO_STRUCT(expected_publish); + + s_request_response_protocol_adapter_incoming_publish_event_record_init( + &expected_publish, + allocator, + aws_byte_cursor_from_c_str("hello/world"), + aws_byte_cursor_from_c_str("SomePayload")); + + struct aws_protocol_adapter_publish_options publish_options = { + .topic = aws_byte_cursor_from_buf(&expected_publish.topic), + .payload = aws_byte_cursor_from_buf(&expected_publish.payload), + .ack_timeout_seconds = 2, + .completion_callback_fn = s_rr_mqtt5_protocol_adapter_test_on_publish_result, + .user_data = &fixture}; + + aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); + + s_wait_for_incoming_publish_events_contains(&fixture, &expected_publish, 1); + + s_request_response_protocol_adapter_incoming_publish_event_record_clean_up(&expected_publish); + + s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_incoming_publish, + s_request_response_mqtt5_protocol_adapter_incoming_publish_fn) + +static int s_request_response_mqtt5_protocol_adapter_shutdown_while_pending_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options test_options; + aws_mqtt5_client_test_init_default_options(&test_options); + + // don't respond to anything + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = NULL; + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = NULL; + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = NULL; + + test_options.client_options.offline_queue_behavior = AWS_MQTT5_COQBT_FAIL_ALL_ON_DISCONNECT; + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options mqtt5_test_fixture_options = { + .client_options = &test_options.client_options, + .server_function_table = &test_options.server_function_table, + }; + + struct aws_request_response_mqtt5_adapter_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + + struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + + ASSERT_SUCCESS(aws_mqtt5_client_start(client)); + + aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + + // publish + struct aws_protocol_adapter_publish_options publish_options = { + .topic = aws_byte_cursor_from_c_str("hello/world"), + .payload = aws_byte_cursor_from_c_str("SomePayload"), + .ack_timeout_seconds = 5, + .completion_callback_fn = s_rr_mqtt5_protocol_adapter_test_on_publish_result, + .user_data = &fixture}; + + aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); + + // subscribe + struct aws_protocol_adapter_subscribe_options subscribe_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .ack_timeout_seconds = 5, + }; + + aws_mqtt_protocol_adapter_subscribe(fixture.protocol_adapter, &subscribe_options); + + // unsubscribe + struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .ack_timeout_seconds = 5, + }; + + aws_mqtt_protocol_adapter_unsubscribe(fixture.protocol_adapter, &unsubscribe_options); + + // tear down the adapter, leaving the in-progress operations with nothing to call back into + s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters(&fixture); + + // stop the mqtt client, which fails the pending MQTT operations + ASSERT_SUCCESS(aws_mqtt5_client_stop(client, NULL, NULL)); + + // wait for the stop to complete, which implies all the operations have been completed without calling back + // into a deleted adapter + aws_wait_for_n_lifecycle_events(&fixture.mqtt5_fixture, AWS_MQTT5_CLET_STOPPED, 1); + + // nothing to verify, we just don't want to crash + + s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_shutdown_while_pending, + s_request_response_mqtt5_protocol_adapter_shutdown_while_pending_fn) diff --git a/tests/v5/mqtt5_client_tests.c b/tests/v5/mqtt5_client_tests.c index 7049dbea..69159cb2 100644 --- a/tests/v5/mqtt5_client_tests.c +++ b/tests/v5/mqtt5_client_tests.c @@ -2710,7 +2710,7 @@ static int s_aws_mqtt5_mock_server_handle_connect_honor_session_after_success( return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_CONNACK, &connack_view); } -static int s_aws_mqtt5_mock_server_handle_connect_honor_session_unconditional( +int aws_mqtt5_mock_server_handle_connect_honor_session_unconditional( void *packet, struct aws_mqtt5_server_mock_connection_context *connection, void *user_data) { @@ -2752,7 +2752,7 @@ static bool s_received_n_lifecycle_events(void *arg) { return matching_events >= context->expected_event_count; } -static void s_wait_for_n_lifecycle_events( +void aws_wait_for_n_lifecycle_events( struct aws_mqtt5_client_mock_test_fixture *test_fixture, enum aws_mqtt5_client_lifecycle_event_type event_type, size_t expected_event_count) { @@ -2809,7 +2809,7 @@ static int s_do_mqtt5_client_session_resumption_test( test_options.client_options.session_behavior = session_behavior; test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_CONNECT] = - s_aws_mqtt5_mock_server_handle_connect_honor_session_unconditional; + aws_mqtt5_mock_server_handle_connect_honor_session_unconditional; struct aws_mqtt5_client_mqtt5_mock_test_fixture_options test_fixture_options = { .client_options = &test_options.client_options, @@ -2823,7 +2823,7 @@ static int s_do_mqtt5_client_session_resumption_test( for (size_t i = 0; i < SESSION_RESUMPTION_CONNECT_COUNT; ++i) { ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - s_wait_for_n_lifecycle_events(&test_context, AWS_MQTT5_CLET_CONNECTION_SUCCESS, i + 1); + aws_wait_for_n_lifecycle_events(&test_context, AWS_MQTT5_CLET_CONNECTION_SUCCESS, i + 1); /* not technically truly safe to query depending on memory model. Remove if it becomes a problem. */ bool expected_rejoined_session = s_compute_expected_rejoined_session(session_behavior, i); @@ -2832,7 +2832,7 @@ static int s_do_mqtt5_client_session_resumption_test( /* can't use stop as that wipes session state */ aws_channel_shutdown(test_context.server_channel, AWS_ERROR_UNKNOWN); - s_wait_for_n_lifecycle_events(&test_context, AWS_MQTT5_CLET_DISCONNECTION, i + 1); + aws_wait_for_n_lifecycle_events(&test_context, AWS_MQTT5_CLET_DISCONNECTION, i + 1); } struct aws_mqtt5_packet_connect_storage clean_start_connect_storage; diff --git a/tests/v5/mqtt5_testing_utils.h b/tests/v5/mqtt5_testing_utils.h index 74cd0f8d..b1f016b8 100644 --- a/tests/v5/mqtt5_testing_utils.h +++ b/tests/v5/mqtt5_testing_utils.h @@ -228,6 +228,16 @@ int aws_mqtt5_server_send_suback_on_subscribe( struct aws_mqtt5_server_mock_connection_context *connection, void *user_data); +int aws_mqtt5_mock_server_handle_connect_honor_session_unconditional( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data); + +void aws_wait_for_n_lifecycle_events( + struct aws_mqtt5_client_mock_test_fixture *test_fixture, + enum aws_mqtt5_client_lifecycle_event_type event_type, + size_t expected_event_count); + extern const struct aws_string *g_default_client_id; #define RECONNECT_TEST_MIN_BACKOFF 500 From 30ed0c36bf6ed46c9b8fdcdac6b1d066f8392576 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 19 Jan 2024 13:15:29 -0800 Subject: [PATCH 009/124] Move test file --- tests/CMakeLists.txt | 4 ++-- .../request_response_protocol_adapter_tests.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename tests/{ => request-response}/request_response_protocol_adapter_tests.c (99%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 587495c4..ab5057b7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,8 +3,8 @@ include(AwsTestHarness) include(AwsLibFuzzer) enable_testing() -file(GLOB TEST_HDRS "v3/*.h v5/*.h") -set(TEST_SRC v3/*.c v5/*.c *.c) +file(GLOB TEST_HDRS "v3/*.h" "v5/*.h" "request-response/*.h") +set(TEST_SRC "v3/*.c" "v5/*.c" "request-response/*.c" "*.c") file(GLOB TESTS ${TEST_HDRS} ${TEST_SRC}) add_test_case(mqtt_packet_puback) diff --git a/tests/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c similarity index 99% rename from tests/request_response_protocol_adapter_tests.c rename to tests/request-response/request_response_protocol_adapter_tests.c index 572c8345..c2fd65fa 100644 --- a/tests/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0. */ -#include "v5/mqtt5_testing_utils.h" +#include "../v5/mqtt5_testing_utils.h" #include #include "aws/mqtt/private/request-response/protocol_adapter.h" From 8c23d4b5749dcc670e0294bf79d0c4171e99af8d Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 19 Jan 2024 14:48:17 -0800 Subject: [PATCH 010/124] Commenting --- .../request-response/protocol_adapter.h | 72 ++++++++++++++++++- .../mqtt/private/request-response/weak_ref.h | 33 +++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 8a61d351..4b439925 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -15,25 +15,54 @@ struct aws_allocator; struct aws_mqtt_client_connection; struct aws_mqtt5_client; +/* + * The request-response protocol adapter is a translation layer that sits between the request-response native client + * implementation and a protocol client capable of subscribing, unsubscribing, and publishing MQTT messages. + * Valid protocol clients include the CRT MQTT5 client, the CRT MQTT311 client, and an eventstream RPC connection + * that belongs to a Greengrass IPC client. Each of these protocol clients has a different (or even implicit) + * contract for carrying out pub-sub operations. The protocol adapter abstracts these details with a simple, + * minimal interface based on the requirements identigied in the request-response design documents. + */ + +/* + * Minimal MQTT subscribe options + */ struct aws_protocol_adapter_subscribe_options { struct aws_byte_cursor topic_filter; uint32_t ack_timeout_seconds; }; +/* + * Minimal MQTT unsubscribe options + */ struct aws_protocol_adapter_unsubscribe_options { struct aws_byte_cursor topic_filter; uint32_t ack_timeout_seconds; }; +/* + * Minimal MQTT publish options + */ struct aws_protocol_adapter_publish_options { struct aws_byte_cursor topic; struct aws_byte_cursor payload; + uint32_t ack_timeout_seconds; + /* + * Invoked on success/failure of the publish itself. Our implementations use QoS1 which means that success + * will be on puback receipt. + */ void (*completion_callback_fn)(bool, void *); + + /* + * User data to pass in when invoking the completion callback + */ void *user_data; - uint32_t ack_timeout_seconds; }; +/* + * Describes the type of subscription event (relative to a topic filter) + */ enum aws_protocol_adapter_subscription_event_type { AWS_PASET_SUBSCRIBE_SUCCESS, AWS_PASET_SUBSCRIBE_FAILURE, @@ -41,21 +70,36 @@ enum aws_protocol_adapter_subscription_event_type { AWS_PASET_UNSUBSCRIBE_FAILURE, }; +/* + * An event emitted by the protocol adapter when a subscribe or unsubscribe is completed by the adapted protocol + * client. + */ struct aws_protocol_adapter_subscription_event { struct aws_byte_cursor topic_filter; enum aws_protocol_adapter_subscription_event_type event_type; }; +/* + * An event emitted by the protocol adapter whenever a publish is received by the protocol client. This will + * potentially include messages that are completely unrelated to MQTT request-response. The topic is the first + * thing that should be checked for relevance. + */ struct aws_protocol_adapter_incoming_publish_event { struct aws_byte_cursor topic; struct aws_byte_cursor payload; }; +/* + * Describes the type of connection event emitted by the protocol adapter + */ enum aws_protocol_adapter_connection_event_type { AWS_PACET_OFFLINE, AWS_PACET_ONLINE, }; +/* + * An event emitted by the protocol adapter whenever the protocol client encounters a change in connectivity state. + */ struct aws_protocol_adapter_connection_event { enum aws_protocol_adapter_connection_event_type event_type; bool rejoined_session; @@ -70,12 +114,19 @@ typedef void(aws_protocol_adapter_terminate_callback_fn)(void *user_data); typedef void( aws_protocol_adapter_connection_event_fn)(struct aws_protocol_adapter_connection_event *event, void *user_data); +/* + * Set of callbacks invoked by the protocol adapter. These must all be set. + */ struct aws_mqtt_protocol_adapter_options { aws_protocol_adapter_subscription_event_fn *subscription_event_callback; aws_protocol_adapter_incoming_publish_fn *incoming_publish_callback; aws_protocol_adapter_terminate_callback_fn *terminate_callback; aws_protocol_adapter_connection_event_fn *connection_event_callback; + /* + * User data to pass into all singleton protocol adapter callbacks. Likely either the request-response client + * or the subscription manager component of the request-response client. + */ void *user_data; }; @@ -97,26 +148,45 @@ struct aws_mqtt_protocol_adapter { AWS_EXTERN_C_BEGIN +/* + * Creates a new request-response protocol adapter from an MQTT311 client + */ AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt_client_connection *connection); +/* + * Creates a new request-response protocol adapter from an MQTT5 client + */ AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt5_client *client); +/* + * Destroys a request-response protocol adapter. Destruction is an asynchronous process and the caller must + * wait for the termination callback to be invoked before assuming that no further callbacks will be invoked. + */ AWS_MQTT_API void aws_mqtt_protocol_adapter_delete(struct aws_mqtt_protocol_adapter *adapter); +/* + * Asks the adapted protocol client to perform an MQTT subscribe operation + */ AWS_MQTT_API int aws_mqtt_protocol_adapter_subscribe( struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_subscribe_options *options); +/* + * Asks the adapted protocol client to perform an MQTT unsubscribe operation + */ AWS_MQTT_API int aws_mqtt_protocol_adapter_unsubscribe( struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_unsubscribe_options *options); +/* + * Asks the adapted protocol client to perform an MQTT publish operation + */ AWS_MQTT_API int aws_mqtt_protocol_adapter_publish( struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_publish_options *options); diff --git a/include/aws/mqtt/private/request-response/weak_ref.h b/include/aws/mqtt/private/request-response/weak_ref.h index 0872de69..42a96764 100644 --- a/include/aws/mqtt/private/request-response/weak_ref.h +++ b/include/aws/mqtt/private/request-response/weak_ref.h @@ -10,18 +10,51 @@ #include +/* + * This is a simplification of the notion of a weak reference particular to the needs of the request-response + * MQTT service clients. This is not suitable for general use but could be extended + * for general use in the future. Until then, it stays private, here. + * + * This weak reference is a ref-counted object with an opaque value. The opaque value may be cleared or + * queried. These two operations *do not* provide any thread safety. + * + * The primary use is to allow one object to safely use asynchronous callback-driven APIs on a second object, despite + * the fact that the first object may get destroyed unpredictably. The two objects must be exclusive to a single + * event loop (because there's no thread safety or mutual exclusion on the opaque value held by the weak ref). + * + * The initial use is the request-response protocol adapter submitting operations to an MQTT client or an + * eventstream RPC connection. We use a single weak ref to the protocol adapter and zero its opaque value when + * the protocol adapter is destroyed. Operation callbacks that subsequently resolve can then short circuit and do + * nothing rather than call into garbage and crash. + */ struct aws_weak_ref; AWS_EXTERN_C_BEGIN +/* + * Creates a new weak reference to an opaque value. + */ AWS_MQTT_API struct aws_weak_ref *aws_weak_ref_new(struct aws_allocator *allocator, void *referenced); +/* + * Acquires a reference to the weak ref object. + */ AWS_MQTT_API struct aws_weak_ref *aws_weak_ref_acquire(struct aws_weak_ref *weak_ref); +/* + * Removes a reference to the weak ref object. When the last reference is removed, the weak ref object will be + * destroyed. This has no effect on the opaque value held by the weak ref. + */ AWS_MQTT_API struct aws_weak_ref *aws_weak_ref_release(struct aws_weak_ref *weak_ref); +/* + * Gets the current value of the opaque data held by the weak ref. + */ AWS_MQTT_API void *aws_weak_ref_get_reference(struct aws_weak_ref *weak_ref); +/* + * Clears the opaque data held by the weak ref. + */ AWS_MQTT_API void aws_weak_ref_zero_reference(struct aws_weak_ref *weak_ref); AWS_EXTERN_C_END From fa655f3624f80f833a7042c74517a8dd43078047 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 19 Jan 2024 14:51:58 -0800 Subject: [PATCH 011/124] Formatting --- source/request-response/protocol_adapter.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index a4dc656c..b8758723 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -310,7 +310,9 @@ static bool s_protocol_adapter_mqtt5_listener_publish_received( struct aws_mqtt_protocol_adapter_5_impl *adapter = user_data; struct aws_protocol_adapter_incoming_publish_event publish_event = { - .topic = publish->topic, .payload = publish->payload}; + .topic = publish->topic, + .payload = publish->payload, + }; (*adapter->config.incoming_publish_callback)(&publish_event, adapter->config.user_data); @@ -382,10 +384,12 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( struct aws_mqtt5_listener_config listener_options = { .client = client, .listener_callbacks = - {.listener_publish_received_handler = s_protocol_adapter_mqtt5_listener_publish_received, - .listener_publish_received_handler_user_data = adapter, - .lifecycle_event_handler = s_protocol_adapter_mqtt5_lifecycle_event_callback, - .lifecycle_event_handler_user_data = adapter}, + { + .listener_publish_received_handler = s_protocol_adapter_mqtt5_listener_publish_received, + .listener_publish_received_handler_user_data = adapter, + .lifecycle_event_handler = s_protocol_adapter_mqtt5_lifecycle_event_callback, + .lifecycle_event_handler_user_data = adapter, + }, .termination_callback = s_protocol_adapter_mqtt5_listener_termination_callback, .termination_callback_user_data = adapter, }; From 65229df5ba62ebed4189b8cf89602d1ad47c1eb8 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 19 Jan 2024 14:58:54 -0800 Subject: [PATCH 012/124] Explanation why we use new approach --- include/aws/mqtt/private/request-response/weak_ref.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/aws/mqtt/private/request-response/weak_ref.h b/include/aws/mqtt/private/request-response/weak_ref.h index 42a96764..cff0a2d8 100644 --- a/include/aws/mqtt/private/request-response/weak_ref.h +++ b/include/aws/mqtt/private/request-response/weak_ref.h @@ -26,6 +26,10 @@ * eventstream RPC connection. We use a single weak ref to the protocol adapter and zero its opaque value when * the protocol adapter is destroyed. Operation callbacks that subsequently resolve can then short circuit and do * nothing rather than call into garbage and crash. + * + * We use this rather than explicitly tracking and zeroing all pending operations (like the 3-to-5 adapter does) + * because this approach is simpler and our usage does not require any of these callbacks to be invoked once the + * request-response client is destroyed. */ struct aws_weak_ref; From 5160eb4ed09e41bee2c4f552d4ba42a5d36431fb Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 22 Jan 2024 09:20:48 -0800 Subject: [PATCH 013/124] array size macro doesn't seem to work on windows? --- tests/v5/mqtt5_client_tests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v5/mqtt5_client_tests.c b/tests/v5/mqtt5_client_tests.c index 69159cb2..b876adda 100644 --- a/tests/v5/mqtt5_client_tests.c +++ b/tests/v5/mqtt5_client_tests.c @@ -3103,7 +3103,7 @@ int aws_mqtt5_mock_server_handle_unsubscribe_unsuback_success( struct aws_mqtt5_packet_unsuback_view unsuback_view = { .packet_id = unsubscribe_view->packet_id, - .reason_code_count = AWS_ARRAY_SIZE(mqtt5_unsuback_codes), + .reason_code_count = unsubscribe_view->topic_filter_count, .reason_codes = mqtt5_unsuback_codes, }; From 29016f1d3917b2e4c05e2fdb0c2243e038160106 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 22 Jan 2024 09:37:57 -0800 Subject: [PATCH 014/124] newline --- tests/v5/mqtt5_testing_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v5/mqtt5_testing_utils.c b/tests/v5/mqtt5_testing_utils.c index 4b682d83..9d1163c9 100644 --- a/tests/v5/mqtt5_testing_utils.c +++ b/tests/v5/mqtt5_testing_utils.c @@ -1765,4 +1765,4 @@ int aws_mqtt5_mock_server_send_packet( } return AWS_OP_SUCCESS; -} \ No newline at end of file +} From 76364e919d186da2c987900aeaa1f10aba5ba704 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 22 Jan 2024 14:51:35 -0800 Subject: [PATCH 015/124] Sync point --- include/aws/mqtt/private/client_impl.h | 4 + include/aws/mqtt/private/mqtt311_listener.h | 183 ++++++++++++++ source/client.c | 5 + source/mqtt311_listener.c | 251 ++++++++++++++++++++ 4 files changed, 443 insertions(+) create mode 100644 include/aws/mqtt/private/mqtt311_listener.h create mode 100644 source/mqtt311_listener.c diff --git a/include/aws/mqtt/private/client_impl.h b/include/aws/mqtt/private/client_impl.h index 1d0dd67a..ce041b2d 100644 --- a/include/aws/mqtt/private/client_impl.h +++ b/include/aws/mqtt/private/client_impl.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -255,6 +256,9 @@ struct aws_mqtt_client_connection_311_impl { aws_mqtt_on_operation_statistics_fn *on_any_operation_statistics; void *on_any_operation_statistics_ud; + /* listener callbacks */ + struct aws_mqtt311_callback_set_manager callback_manager; + /* Connection tasks. */ struct aws_mqtt_reconnect_task *reconnect_task; struct aws_channel_task ping_task; diff --git a/include/aws/mqtt/private/mqtt311_listener.h b/include/aws/mqtt/private/mqtt311_listener.h new file mode 100644 index 00000000..a5c9bba9 --- /dev/null +++ b/include/aws/mqtt/private/mqtt311_listener.h @@ -0,0 +1,183 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#ifndef AWS_MQTT_MQTT311_LISTENER_H +#define AWS_MQTT_MQTT311_LISTENER_H + +#include + +#include +#include + +AWS_PUSH_SANE_WARNING_LEVEL + +/** + * Callback signature for when an mqtt311 listener has completely destroyed itself. + */ +typedef void(aws_mqtt311_listener_termination_completion_fn)(void *complete_ctx); + +/** + * A record that tracks MQTT311 client connection callbacks which can be dynamically injected via a listener. + * + * All the callbacks that are supported here are invoked only on the 311 connection's event loop. With the + * add/remove callback set also on the event loop, everything is correctly serialized without data races. + */ +struct aws_mqtt311_callback_set { + + /* Called from s_packet_handler_publish which is event-loop invoked */ + aws_mqtt_client_publish_received_fn *publish_received_handler; + + /* Called from s_mqtt_client_shutdown which is event-loop invoked */ + aws_mqtt_client_on_connection_interrupted_fn *connection_interrupted_handler; + + /* Called from s_packet_handler_connack which is event-loop invoked */ + aws_mqtt_client_on_connection_interrupted_fn *connection_resumed_handler; + + /* Also called from s_packet_handler_connack which is event-loop invoked */ + aws_mqtt_client_on_connection_success_fn *connection_success_handler; + + void *user_data; +}; + +/** + * An internal type for managing chains of callbacks attached to an mqtt311 client connection. Supports chains for + * lifecycle events and incoming publish packet handling. + * + * Assumed to be owned and used only by an MQTT311 client connection. + */ +struct aws_mqtt311_callback_set_manager { + struct aws_allocator *allocator; + + struct aws_mqtt_client_connection *connection; + + struct aws_linked_list callback_set_entries; + + uint64_t next_callback_set_entry_id; +}; + + +/** + * Configuration options for MQTT311 listener objects. + */ +struct aws_mqtt311_listener_config { + + /** + * MQTT311 client connection to listen to events on + */ + struct aws_mqtt_client_connection *connection; + + /** + * Callbacks to invoke when events occur on the MQTT311 client connection + */ + struct aws_mqtt311_callback_set listener_callbacks; + + /** + * Listener destruction is asynchronous and thus requires a termination callback and associated user data + * to notify the user that the listener has been fully destroyed and no further events will be received. + */ + aws_mqtt311_listener_termination_completion_fn *termination_callback; + void *termination_callback_user_data; +}; + +AWS_EXTERN_C_BEGIN + +/** + * Creates a new MQTT311 listener object. For as long as the listener lives, incoming publishes and lifecycle events + * will be forwarded to the callbacks configured on the listener. + * + * @param allocator allocator to use + * @param config listener configuration + * @return a new aws_mqtt311_listener object + */ +AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_new( + struct aws_allocator *allocator, + struct aws_mqtt311_listener_config *config); + +/** + * Adds a reference to an mqtt311 listener. + * + * @param listener listener to add a reference to + * @return the listener object + */ +AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_acquire(struct aws_mqtt311_listener *listener); + +/** + * Removes a reference to an mqtt311 listener. When the reference count drops to zero, the listener's asynchronous + * destruction will be started. + * + * @param listener listener to remove a reference from + * @return NULL + */ +AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_release(struct aws_mqtt311_listener *listener); + + +/** + * Initializes a callback set manager + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_init( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_allocator *allocator, + struct aws_mqtt_client_connection *connection); + +/** + * Cleans up a callback set manager. + * + * aws_mqtt311_callback_set_manager_init must have been previously called or this will crash. + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_clean_up(struct aws_mqtt311_callback_set_manager *manager); + +/** + * Adds a callback set to the front of the handler chain. Returns an integer id that can be used to selectively + * remove the callback set from the manager. + * + * May only be called on the client's event loop thread. + */ +AWS_MQTT_API +uint64_t aws_mqtt311_callback_set_manager_push_front( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_mqtt311_callback_set *callback_set); + +/** + * Removes a callback set from the handler chain. + * + * May only be called on the client's event loop thread. + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_remove(struct aws_mqtt311_callback_set_manager *manager, uint64_t callback_set_id); + +/** + * Walks the incoming publish handler chain for an MQTT311 connection. The chain's callbacks will be invoked + * until either the end is reached or one of the callbacks returns true. + * + * May only be called on the client's event loop thread. + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_on_publish_received( + struct aws_mqtt311_callback_set_manager *manager, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain); + +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_on_connection_interrupted( + struct aws_mqtt311_callback_set_manager *manager); + +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_on_connection_resumed( + struct aws_mqtt311_callback_set_manager *manager); + +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_on_connection_success( + struct aws_mqtt311_callback_set_manager *manager); + +AWS_EXTERN_C_END + +AWS_POP_SANE_WARNING_LEVEL + +#endif /* AWS_MQTT_MQTT311_LISTENER_H */ diff --git a/source/client.c b/source/client.c index 42ab634c..a5161afe 100644 --- a/source/client.c +++ b/source/client.c @@ -259,6 +259,7 @@ static void s_mqtt_client_shutdown( (void)channel; struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection->loop)); AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, "id=%p: Channel has been shutdown with error code %d", (void *)connection, error_code); @@ -801,6 +802,8 @@ static void s_mqtt_client_connection_destroy_final(struct aws_mqtt_client_connec termination_handler_user_data = connection->on_termination_ud; } + aws_mqtt311_callback_set_manager_clean_up(&connection->callback_manager); + /* If the reconnect_task isn't freed, free it */ if (connection->reconnect_task) { aws_mem_release(connection->reconnect_task->allocator, connection->reconnect_task); @@ -3351,6 +3354,8 @@ struct aws_mqtt_client_connection *aws_mqtt_client_connection_new(struct aws_mqt connection->handler.vtable = aws_mqtt_get_client_channel_vtable(); connection->handler.impl = connection; + aws_mqtt311_callback_set_manager_init(&connection->callback_manager, connection->allocator, connection); + return &connection->base; failed_init_outstanding_requests_table: diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c new file mode 100644 index 00000000..b2fcab92 --- /dev/null +++ b/source/mqtt311_listener.c @@ -0,0 +1,251 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include +#include +#include +#include +#include + +#include + +struct aws_mqtt311_listener { + struct aws_allocator *allocator; + + struct aws_ref_count ref_count; + + struct aws_mqtt311_listener_config config; + + uint64_t callback_set_id; + + struct aws_task initialize_task; + struct aws_task terminate_task; +}; + +static void s_mqtt311_listener_destroy(struct aws_mqtt311_listener *listener) { + + aws_mqtt_client_connection_release(listener->config.connection); + + aws_mqtt311_listener_termination_completion_fn *termination_callback = listener->config.termination_callback; + void *termination_callback_user_data = listener->config.termination_callback_user_data; + + aws_mem_release(listener->allocator, listener); + + if (termination_callback != NULL) { + (*termination_callback)(termination_callback_user_data); + } +} + +static void s_mqtt311_listener_initialize_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { + (void)task; + + struct aws_mqtt311_listener *listener = arg; + + if (task_status == AWS_TASK_STATUS_RUN_READY) { + struct aws_mqtt_client_connection_311_impl *connection_impl = listener->config.connection->impl; + listener->callback_set_id = aws_mqtt311_callback_set_manager_push_front( + &connection_impl->callback_manager, &listener->config.listener_callbacks); + AWS_LOGF_INFO( + AWS_LS_MQTT_GENERAL, + "id=%p: Mqtt311 Listener initialized, listener id=%p", + (void *)listener->config.connection, + (void *)listener); + aws_mqtt311_listener_release(listener); + } else { + s_mqtt311_listener_destroy(listener); + } +} + +static void s_mqtt311_listener_terminate_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { + (void)task; + + struct aws_mqtt311_listener *listener = arg; + + if (task_status == AWS_TASK_STATUS_RUN_READY) { + struct aws_mqtt_client_connection_311_impl *connection_impl = listener->config.connection->impl; + aws_mqtt311_callback_set_manager_remove(&connection_impl->callback_manager, listener->callback_set_id); + } + + AWS_LOGF_INFO( + AWS_LS_MQTT_GENERAL, + "id=%p: Mqtt311 Listener terminated, listener id=%p", + (void *)listener->config.connection, + (void *)listener); + + s_mqtt311_listener_destroy(listener); +} + +static void s_aws_mqtt311_listener_on_zero_ref_count(void *context) { + struct aws_mqtt311_listener *listener = context; + struct aws_mqtt_client_connection_311_impl *connection_impl = listener->config.connection->impl; + + aws_event_loop_schedule_task_now(connection_impl->loop, &listener->terminate_task); +} + +struct aws_mqtt311_listener *aws_mqtt311_listener_new( + struct aws_allocator *allocator, + struct aws_mqtt311_listener_config *config) { + if (config->connection == NULL) { + return NULL; + } + + if (aws_mqtt_client_connection_get_impl_type(config->connection) != AWS_MQTT311_IT_311_CONNECTION_IMPL) { + return NULL; + } + + struct aws_mqtt_client_connection_311_impl *connection_impl = config->connection->impl; + struct aws_mqtt311_listener *listener = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt311_listener)); + + listener->allocator = allocator; + listener->config = *config; + + aws_mqtt_client_connection_acquire(config->connection); + aws_ref_count_init(&listener->ref_count, listener, s_aws_mqtt311_listener_on_zero_ref_count); + + aws_task_init(&listener->initialize_task, s_mqtt311_listener_initialize_task_fn, listener, "Mqtt311ListenerInitialize"); + aws_task_init(&listener->terminate_task, s_mqtt311_listener_terminate_task_fn, listener, "Mqtt311ListenerTerminate"); + + aws_mqtt311_listener_acquire(listener); + aws_event_loop_schedule_task_now(connection_impl->loop, &listener->initialize_task); + + return listener; +} + +struct aws_mqtt311_listener *aws_mqtt311_listener_acquire(struct aws_mqtt311_listener *listener) { + if (listener != NULL) { + aws_ref_count_acquire(&listener->ref_count); + } + + return listener; +} + +struct aws_mqtt311_listener *aws_mqtt311_listener_release(struct aws_mqtt311_listener *listener) { + if (listener != NULL) { + aws_ref_count_release(&listener->ref_count); + } + + return NULL; +} + + +struct aws_mqtt311_callback_set_entry { + struct aws_allocator *allocator; + + struct aws_linked_list_node node; + + uint64_t id; + + struct aws_mqtt311_callback_set callbacks; +}; + +void aws_mqtt311_callback_set_manager_init( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_allocator *allocator, + struct aws_mqtt_client_connection *connection) { + + manager->allocator = allocator; + manager->connection = connection; /* no need to ref count, this is assumed to be owned by the client connection */ + manager->next_callback_set_entry_id = 1; + + aws_linked_list_init(&manager->callback_set_entries); +} + +void aws_mqtt311_callback_set_manager_clean_up(struct aws_mqtt311_callback_set_manager *manager) { + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); + + aws_linked_list_remove(&entry->node); + aws_mem_release(entry->allocator, entry); + } +} + +static struct aws_mqtt311_callback_set_entry *s_new_311_callback_set_entry( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_mqtt311_callback_set *callback_set) { + struct aws_mqtt311_callback_set_entry *entry = + aws_mem_calloc(manager->allocator, 1, sizeof(struct aws_mqtt311_callback_set_entry)); + + entry->allocator = manager->allocator; + entry->id = manager->next_callback_set_entry_id++; + entry->callbacks = *callback_set; + + AWS_LOGF_INFO( + AWS_LS_MQTT5_GENERAL, + "id=%p: callback manager created new entry :%" PRIu64, + (void *)manager->connection, + entry->id); + + return entry; +} + +uint64_t aws_mqtt5_callback_set_manager_push_front( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_mqtt311_callback_set *callback_set) { + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(manager->client->loop)); + + struct aws_mqtt311_callback_set_entry *entry = s_new_311_callback_set_entry(manager, callback_set); + + aws_linked_list_push_front(&manager->callback_set_entries, &entry->node); + + return entry->id; +} + +void aws_mqtt311_callback_set_manager_remove(struct aws_mqtt311_callback_set_manager *manager, uint64_t callback_set_id) { + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(manager->client->loop)); + + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); + + if (entry->id == callback_set_id) { + aws_linked_list_remove(&entry->node); + + AWS_LOGF_INFO( + AWS_LS_MQTT5_GENERAL, + "id=%p: callback manager removed entry id=%" PRIu64, + (void *)manager->connection, + entry->id); + aws_mem_release(entry->allocator, entry); + return; + } + } + AWS_LOGF_INFO( + AWS_LS_MQTT5_GENERAL, + "id=%p: callback manager failed to remove entry id=%" PRIu64 ", callback set id not found.", + (void *)manager->connection, + callback_set_id); +} + +void aws_mqtt311_callback_set_manager_on_publish_received( + struct aws_mqtt311_callback_set_manager *manager, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain) { + +} + +void aws_mqtt311_callback_set_manager_on_connection_interrupted( + struct aws_mqtt311_callback_set_manager *manager) { + +} + +void aws_mqtt311_callback_set_manager_on_connection_resumed( + struct aws_mqtt311_callback_set_manager *manager) { + +} + +void aws_mqtt311_callback_set_manager_on_connection_success( + struct aws_mqtt311_callback_set_manager *manager) { + +} \ No newline at end of file From f418237c8edf516bab7012df4dc82dbcd3c0de4e Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 24 Jan 2024 09:39:53 -0800 Subject: [PATCH 016/124] Refactor 311 tests to share functionality --- include/aws/mqtt/private/client_impl_shared.h | 4 +- include/aws/mqtt/private/mqtt311_listener.h | 22 +- source/client.c | 4 +- source/client_channel_handler.c | 4 + source/client_impl_shared.c | 2 +- source/mqtt311_listener.c | 54 +- source/v5/mqtt5_to_mqtt3_adapter.c | 2 +- tests/v3/connection_state_test.c | 1241 ++++------------- tests/v3/mqtt311_testing_utils.c | 582 ++++++++ tests/v3/mqtt311_testing_utils.h | 155 ++ 10 files changed, 1094 insertions(+), 976 deletions(-) create mode 100644 tests/v3/mqtt311_testing_utils.c create mode 100644 tests/v3/mqtt311_testing_utils.h diff --git a/include/aws/mqtt/private/client_impl_shared.h b/include/aws/mqtt/private/client_impl_shared.h index 9f28894f..47c5bbd1 100644 --- a/include/aws/mqtt/private/client_impl_shared.h +++ b/include/aws/mqtt/private/client_impl_shared.h @@ -122,7 +122,7 @@ struct aws_mqtt_client_connection_vtable { int (*get_stats_fn)(void *impl, struct aws_mqtt_connection_operation_statistics *stats); - enum aws_mqtt311_impl_type (*get_impl_type)(void *impl); + enum aws_mqtt311_impl_type (*get_impl_type)(const void *impl); }; struct aws_mqtt_client_connection { @@ -131,7 +131,7 @@ struct aws_mqtt_client_connection { }; AWS_MQTT_API enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type( - struct aws_mqtt_client_connection *connection); + const struct aws_mqtt_client_connection *connection); AWS_MQTT_API uint64_t aws_mqtt_hash_uint16_t(const void *item); diff --git a/include/aws/mqtt/private/mqtt311_listener.h b/include/aws/mqtt/private/mqtt311_listener.h index a5c9bba9..5e000148 100644 --- a/include/aws/mqtt/private/mqtt311_listener.h +++ b/include/aws/mqtt/private/mqtt311_listener.h @@ -23,20 +23,17 @@ typedef void(aws_mqtt311_listener_termination_completion_fn)(void *complete_ctx) * * All the callbacks that are supported here are invoked only on the 311 connection's event loop. With the * add/remove callback set also on the event loop, everything is correctly serialized without data races. + * + * We only listen to connection-resumed because the only connection-level event we care about is a failure + * to rejoin a session (which invalidates all subscriptions that were considered valid) */ struct aws_mqtt311_callback_set { /* Called from s_packet_handler_publish which is event-loop invoked */ aws_mqtt_client_publish_received_fn *publish_received_handler; - /* Called from s_mqtt_client_shutdown which is event-loop invoked */ - aws_mqtt_client_on_connection_interrupted_fn *connection_interrupted_handler; - /* Called from s_packet_handler_connack which is event-loop invoked */ - aws_mqtt_client_on_connection_interrupted_fn *connection_resumed_handler; - - /* Also called from s_packet_handler_connack which is event-loop invoked */ - aws_mqtt_client_on_connection_success_fn *connection_success_handler; + aws_mqtt_client_on_connection_resumed_fn *connection_resumed_handler; void *user_data; }; @@ -164,17 +161,12 @@ void aws_mqtt311_callback_set_manager_on_publish_received( enum aws_mqtt_qos qos, bool retain); -AWS_MQTT_API -void aws_mqtt311_callback_set_manager_on_connection_interrupted( - struct aws_mqtt311_callback_set_manager *manager); AWS_MQTT_API void aws_mqtt311_callback_set_manager_on_connection_resumed( - struct aws_mqtt311_callback_set_manager *manager); - -AWS_MQTT_API -void aws_mqtt311_callback_set_manager_on_connection_success( - struct aws_mqtt311_callback_set_manager *manager); + struct aws_mqtt311_callback_set_manager *manager, + enum aws_mqtt_connect_return_code return_code, + bool rejoined_session); AWS_EXTERN_C_END diff --git a/source/client.c b/source/client.c index a5161afe..a77836f1 100644 --- a/source/client.c +++ b/source/client.c @@ -3223,7 +3223,7 @@ static void s_aws_mqtt_client_connection_311_release(void *impl) { aws_ref_count_release(&connection->ref_count); } -enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_3_get_impl(void *impl) { +enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_3_get_impl(const void *impl) { (void)impl; return AWS_MQTT311_IT_311_CONNECTION_IMPL; @@ -3354,7 +3354,7 @@ struct aws_mqtt_client_connection *aws_mqtt_client_connection_new(struct aws_mqt connection->handler.vtable = aws_mqtt_get_client_channel_vtable(); connection->handler.impl = connection; - aws_mqtt311_callback_set_manager_init(&connection->callback_manager, connection->allocator, connection); + aws_mqtt311_callback_set_manager_init(&connection->callback_manager, connection->allocator, &connection->base); return &connection->base; diff --git a/source/client_channel_handler.c b/source/client_channel_handler.c index 543f0950..f1d91835 100644 --- a/source/client_channel_handler.c +++ b/source/client_channel_handler.c @@ -240,6 +240,8 @@ static int s_packet_handler_connack(struct aws_byte_cursor message_cursor, void (void *)connection); MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_resumed, connack.connect_return_code, connack.session_present); + + aws_mqtt311_callback_set_manager_on_connection_resumed(&connection->callback_manager, connack.connect_return_code, connack.session_present); } else { aws_create_reconnect_task(connection); @@ -291,6 +293,8 @@ static int s_packet_handler_publish(struct aws_byte_cursor message_cursor, void MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_any_publish, &publish.topic_name, &publish.payload, dup, qos, retain); + aws_mqtt311_callback_set_manager_on_publish_received(&connection->callback_manager, &publish.topic_name, &publish.payload, dup, qos, retain); + AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, "id=%p: publish received with msg id=%" PRIu16 " dup=%d qos=%d retain=%d payload-size=%zu topic=" PRInSTR, diff --git a/source/client_impl_shared.c b/source/client_impl_shared.c index 0f0e70a2..7c4fc52e 100644 --- a/source/client_impl_shared.c +++ b/source/client_impl_shared.c @@ -204,7 +204,7 @@ int aws_mqtt_client_connection_get_stats( return (*connection->vtable->get_stats_fn)(connection->impl, stats); } -enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type(struct aws_mqtt_client_connection *connection) { +enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type(const struct aws_mqtt_client_connection *connection) { return (*connection->vtable->get_impl_type)(connection->impl); } diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c index b2fcab92..f061a3a7 100644 --- a/source/mqtt311_listener.c +++ b/source/mqtt311_listener.c @@ -13,6 +13,14 @@ #include +static struct aws_event_loop *s_mqtt_client_connection_get_event_loop(const struct aws_mqtt_client_connection *connection) { + AWS_FATAL_ASSERT(aws_mqtt_client_connection_get_impl_type(connection) == AWS_MQTT311_IT_311_CONNECTION_IMPL); + + struct aws_mqtt_client_connection_311_impl *connection_impl = connection->impl; + + return connection_impl->loop; +} + struct aws_mqtt311_listener { struct aws_allocator *allocator; @@ -81,9 +89,8 @@ static void s_mqtt311_listener_terminate_task_fn(struct aws_task *task, void *ar static void s_aws_mqtt311_listener_on_zero_ref_count(void *context) { struct aws_mqtt311_listener *listener = context; - struct aws_mqtt_client_connection_311_impl *connection_impl = listener->config.connection->impl; - aws_event_loop_schedule_task_now(connection_impl->loop, &listener->terminate_task); + aws_event_loop_schedule_task_now(s_mqtt_client_connection_get_event_loop(listener->config.connection), &listener->terminate_task); } struct aws_mqtt311_listener *aws_mqtt311_listener_new( @@ -97,7 +104,6 @@ struct aws_mqtt311_listener *aws_mqtt311_listener_new( return NULL; } - struct aws_mqtt_client_connection_311_impl *connection_impl = config->connection->impl; struct aws_mqtt311_listener *listener = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt311_listener)); listener->allocator = allocator; @@ -110,7 +116,7 @@ struct aws_mqtt311_listener *aws_mqtt311_listener_new( aws_task_init(&listener->terminate_task, s_mqtt311_listener_terminate_task_fn, listener, "Mqtt311ListenerTerminate"); aws_mqtt311_listener_acquire(listener); - aws_event_loop_schedule_task_now(connection_impl->loop, &listener->initialize_task); + aws_event_loop_schedule_task_now(s_mqtt_client_connection_get_event_loop(config->connection), &listener->initialize_task); return listener; } @@ -184,11 +190,11 @@ static struct aws_mqtt311_callback_set_entry *s_new_311_callback_set_entry( return entry; } -uint64_t aws_mqtt5_callback_set_manager_push_front( +uint64_t aws_mqtt311_callback_set_manager_push_front( struct aws_mqtt311_callback_set_manager *manager, struct aws_mqtt311_callback_set *callback_set) { - AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(manager->client->loop)); + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(s_mqtt_client_connection_get_event_loop(manager->connection))); struct aws_mqtt311_callback_set_entry *entry = s_new_311_callback_set_entry(manager, callback_set); @@ -199,7 +205,7 @@ uint64_t aws_mqtt5_callback_set_manager_push_front( void aws_mqtt311_callback_set_manager_remove(struct aws_mqtt311_callback_set_manager *manager, uint64_t callback_set_id) { - AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(manager->client->loop)); + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(s_mqtt_client_connection_get_event_loop(manager->connection))); struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); while (node != aws_linked_list_end(&manager->callback_set_entries)) { @@ -233,19 +239,37 @@ void aws_mqtt311_callback_set_manager_on_publish_received( enum aws_mqtt_qos qos, bool retain) { -} + struct aws_mqtt_client_connection_311_impl *connection_impl = manager->connection->impl; + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection_impl->loop)); -void aws_mqtt311_callback_set_manager_on_connection_interrupted( - struct aws_mqtt311_callback_set_manager *manager) { + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); + struct aws_mqtt311_callback_set *callback_set = &entry->callbacks; + if (callback_set->publish_received_handler != NULL) { + (*callback_set->publish_received_handler)(manager->connection, topic, payload, dup, qos, retain, callback_set->user_data); + } + } } void aws_mqtt311_callback_set_manager_on_connection_resumed( - struct aws_mqtt311_callback_set_manager *manager) { + struct aws_mqtt311_callback_set_manager *manager, + enum aws_mqtt_connect_return_code return_code, + bool rejoined_session) { -} + struct aws_mqtt_client_connection_311_impl *connection_impl = manager->connection->impl; + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection_impl->loop)); -void aws_mqtt311_callback_set_manager_on_connection_success( - struct aws_mqtt311_callback_set_manager *manager) { + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); -} \ No newline at end of file + struct aws_mqtt311_callback_set *callback_set = &entry->callbacks; + if (callback_set->connection_resumed_handler != NULL) { + (*callback_set->connection_resumed_handler)(manager->connection, return_code, rejoined_session, callback_set->user_data); + } + } +} diff --git a/source/v5/mqtt5_to_mqtt3_adapter.c b/source/v5/mqtt5_to_mqtt3_adapter.c index fb39e444..45464647 100644 --- a/source/v5/mqtt5_to_mqtt3_adapter.c +++ b/source/v5/mqtt5_to_mqtt3_adapter.c @@ -2854,7 +2854,7 @@ static uint16_t s_aws_mqtt_5_resubscribe_existing_topics( return 0; } -enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_5_get_impl(void *impl) { +enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_5_get_impl(const void *impl) { (void)impl; return AWS_MQTT311_IT_5_ADAPTER_IMPL; diff --git a/tests/v3/connection_state_test.c b/tests/v3/connection_state_test.c index 6a3e086f..8c5cbc8a 100644 --- a/tests/v3/connection_state_test.c +++ b/tests/v3/connection_state_test.c @@ -3,661 +3,22 @@ * SPDX-License-Identifier: Apache-2.0. */ -#include "mqtt_mock_server_handler.h" - -#include +#include -#include -#include -#include +#include "mqtt_mock_server_handler.h" +#include "mqtt311_testing_utils.h" #include -#include - #include -#include - -static const int TEST_LOG_SUBJECT = 60000; -static const int ONE_SEC = 1000000000; -// The value is extract from aws-c-mqtt/source/client.c -static const int AWS_RESET_RECONNECT_BACKOFF_DELAY_SECONDS = 10; -static const uint64_t RECONNECT_BACKOFF_DELAY_ERROR_MARGIN_NANO_SECONDS = 500000000; -#define DEFAULT_MIN_RECONNECT_DELAY_SECONDS 1 - -#define DEFAULT_TEST_PING_TIMEOUT_MS 1000 -#define DEFAULT_TEST_KEEP_ALIVE_S 2 - -struct received_publish_packet { - struct aws_byte_buf topic; - struct aws_byte_buf payload; - bool dup; - enum aws_mqtt_qos qos; - bool retain; -}; - -struct mqtt_connection_state_test { - struct aws_allocator *allocator; - struct aws_channel *server_channel; - struct aws_channel_handler *mock_server; - struct aws_client_bootstrap *client_bootstrap; - struct aws_server_bootstrap *server_bootstrap; - struct aws_event_loop_group *el_group; - struct aws_host_resolver *host_resolver; - struct aws_socket_endpoint endpoint; - struct aws_socket *listener; - struct aws_mqtt_client *mqtt_client; - struct aws_mqtt_client_connection *mqtt_connection; - struct aws_socket_options socket_options; - - bool session_present; - bool connection_completed; - bool connection_success; - bool connection_failure; - bool client_disconnect_completed; - bool server_disconnect_completed; - bool connection_interrupted; - bool connection_resumed; - bool subscribe_completed; - bool listener_destroyed; - bool connection_terminated; - int interruption_error; - int subscribe_complete_error; - int op_complete_error; - enum aws_mqtt_connect_return_code mqtt_return_code; - int error; - struct aws_condition_variable cvar; - struct aws_mutex lock; - /* any published messages from mock server, that you may not subscribe to. (Which should not happen in real life) */ - struct aws_array_list any_published_messages; /* list of struct received_publish_packet */ - size_t any_publishes_received; - size_t expected_any_publishes; - /* the published messages from mock server, that you did subscribe to. */ - struct aws_array_list published_messages; /* list of struct received_publish_packet */ - size_t publishes_received; - size_t expected_publishes; - /* The returned QoS from mock server */ - struct aws_array_list qos_returned; /* list of uint_8 */ - size_t ops_completed; - size_t expected_ops_completed; - size_t connection_close_calls; /* All of the times on_connection_closed has been called */ - - size_t connection_termination_calls; /* How many times on_connection_termination has been called, should be 1 */ -}; - static struct mqtt_connection_state_test test_data = {0}; -static void s_on_any_publish_received( - struct aws_mqtt_client_connection *connection, - const struct aws_byte_cursor *topic, - const struct aws_byte_cursor *payload, - bool dup, - enum aws_mqtt_qos qos, - bool retain, - void *userdata); - -static void s_on_incoming_channel_setup_fn( - struct aws_server_bootstrap *bootstrap, - int error_code, - struct aws_channel *channel, - void *user_data) { - (void)bootstrap; - struct mqtt_connection_state_test *state_test_data = user_data; - - state_test_data->error = error_code; - - if (!error_code) { - aws_mutex_lock(&state_test_data->lock); - state_test_data->server_disconnect_completed = false; - aws_mutex_unlock(&state_test_data->lock); - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "server channel setup completed"); - - state_test_data->server_channel = channel; - struct aws_channel_slot *test_handler_slot = aws_channel_slot_new(channel); - aws_channel_slot_insert_end(channel, test_handler_slot); - mqtt_mock_server_handler_update_slot(state_test_data->mock_server, test_handler_slot); - aws_channel_slot_set_handler(test_handler_slot, state_test_data->mock_server); - } -} - -static void s_on_incoming_channel_shutdown_fn( - struct aws_server_bootstrap *bootstrap, - int error_code, - struct aws_channel *channel, - void *user_data) { - (void)bootstrap; - (void)error_code; - (void)channel; - struct mqtt_connection_state_test *state_test_data = user_data; - aws_mutex_lock(&state_test_data->lock); - state_test_data->server_disconnect_completed = true; - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "server channel shutdown completed"); - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static void s_on_listener_destroy(struct aws_server_bootstrap *bootstrap, void *user_data) { - (void)bootstrap; - struct mqtt_connection_state_test *state_test_data = user_data; - aws_mutex_lock(&state_test_data->lock); - state_test_data->listener_destroyed = true; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_listener_destroyed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->listener_destroyed; -} - -static void s_wait_on_listener_cleanup(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_listener_destroyed, state_test_data); - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_connection_interrupted(struct aws_mqtt_client_connection *connection, int error_code, void *userdata) { - (void)connection; - (void)error_code; - struct mqtt_connection_state_test *state_test_data = userdata; - - aws_mutex_lock(&state_test_data->lock); - state_test_data->connection_interrupted = true; - state_test_data->interruption_error = error_code; - aws_mutex_unlock(&state_test_data->lock); - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "connection interrupted"); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_connection_interrupted(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_interrupted; -} - -static void s_wait_for_interrupt_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_interrupted, state_test_data); - state_test_data->connection_interrupted = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_connection_resumed( - struct aws_mqtt_client_connection *connection, - enum aws_mqtt_connect_return_code return_code, - bool session_present, - void *userdata) { - (void)connection; - (void)return_code; - (void)session_present; - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "reconnect completed"); - - struct mqtt_connection_state_test *state_test_data = userdata; - - aws_mutex_lock(&state_test_data->lock); - state_test_data->connection_resumed = true; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_connection_resumed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_resumed; -} - -static void s_wait_for_reconnect_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_resumed, state_test_data); - state_test_data->connection_resumed = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_connection_success( - struct aws_mqtt_client_connection *connection, - enum aws_mqtt_connect_return_code return_code, - bool session_present, - void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - aws_mutex_lock(&state_test_data->lock); - - state_test_data->session_present = session_present; - state_test_data->mqtt_return_code = return_code; - state_test_data->connection_success = true; - aws_mutex_unlock(&state_test_data->lock); - - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static void s_on_connection_failure(struct aws_mqtt_client_connection *connection, int error_code, void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - aws_mutex_lock(&state_test_data->lock); - - state_test_data->error = error_code; - state_test_data->connection_failure = true; - aws_mutex_unlock(&state_test_data->lock); - - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_connection_succeed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_success; -} - -static bool s_is_connection_failed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_failure; -} - -static void s_wait_for_connection_to_succeed(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_succeed, state_test_data); - state_test_data->connection_success = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_wait_for_connection_to_fail(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_failed, state_test_data); - state_test_data->connection_failure = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static bool s_is_termination_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_terminated; -} - -static void s_wait_for_termination_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_termination_completed, state_test_data); - state_test_data->connection_terminated = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_connection_termination_fn(void *userdata) { - struct mqtt_connection_state_test *state_test_data = (struct mqtt_connection_state_test *)userdata; - - aws_mutex_lock(&state_test_data->lock); - state_test_data->connection_termination_calls += 1; - state_test_data->connection_terminated = true; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -/** sets up a unix domain socket server and socket options. Creates an mqtt connection configured to use - * the domain socket. - */ static int s_setup_mqtt_server_fn(struct aws_allocator *allocator, void *ctx) { - aws_mqtt_library_init(allocator); - - struct mqtt_connection_state_test *state_test_data = ctx; - - AWS_ZERO_STRUCT(*state_test_data); - - state_test_data->allocator = allocator; - state_test_data->el_group = aws_event_loop_group_new_default(allocator, 1, NULL); - - state_test_data->mock_server = new_mqtt_mock_server(allocator); - ASSERT_NOT_NULL(state_test_data->mock_server); - - state_test_data->server_bootstrap = aws_server_bootstrap_new(allocator, state_test_data->el_group); - ASSERT_NOT_NULL(state_test_data->server_bootstrap); - - struct aws_socket_options socket_options = { - .connect_timeout_ms = 100, - .domain = AWS_SOCKET_LOCAL, - }; - - state_test_data->socket_options = socket_options; - ASSERT_SUCCESS(aws_condition_variable_init(&state_test_data->cvar)); - ASSERT_SUCCESS(aws_mutex_init(&state_test_data->lock)); - - aws_socket_endpoint_init_local_address_for_test(&state_test_data->endpoint); - - struct aws_server_socket_channel_bootstrap_options server_bootstrap_options = { - .bootstrap = state_test_data->server_bootstrap, - .host_name = state_test_data->endpoint.address, - .port = state_test_data->endpoint.port, - .socket_options = &state_test_data->socket_options, - .incoming_callback = s_on_incoming_channel_setup_fn, - .shutdown_callback = s_on_incoming_channel_shutdown_fn, - .destroy_callback = s_on_listener_destroy, - .user_data = state_test_data, - }; - state_test_data->listener = aws_server_bootstrap_new_socket_listener(&server_bootstrap_options); - - ASSERT_NOT_NULL(state_test_data->listener); - - struct aws_host_resolver_default_options resolver_options = { - .el_group = state_test_data->el_group, - .max_entries = 1, - }; - state_test_data->host_resolver = aws_host_resolver_new_default(allocator, &resolver_options); - - struct aws_client_bootstrap_options bootstrap_options = { - .event_loop_group = state_test_data->el_group, - .user_data = state_test_data, - .host_resolver = state_test_data->host_resolver, - }; - - state_test_data->client_bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); - - state_test_data->mqtt_client = aws_mqtt_client_new(allocator, state_test_data->client_bootstrap); - state_test_data->mqtt_connection = aws_mqtt_client_connection_new(state_test_data->mqtt_client); - ASSERT_NOT_NULL(state_test_data->mqtt_connection); - - ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_interruption_handlers( - state_test_data->mqtt_connection, - s_on_connection_interrupted, - state_test_data, - s_on_connection_resumed, - state_test_data)); - - ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_result_handlers( - state_test_data->mqtt_connection, - s_on_connection_success, - state_test_data, - s_on_connection_failure, - state_test_data)); - - ASSERT_SUCCESS(aws_mqtt_client_connection_set_on_any_publish_handler( - state_test_data->mqtt_connection, s_on_any_publish_received, state_test_data)); - - ASSERT_SUCCESS(aws_array_list_init_dynamic( - &state_test_data->published_messages, allocator, 4, sizeof(struct received_publish_packet))); - ASSERT_SUCCESS(aws_array_list_init_dynamic( - &state_test_data->any_published_messages, allocator, 4, sizeof(struct received_publish_packet))); - ASSERT_SUCCESS(aws_array_list_init_dynamic(&state_test_data->qos_returned, allocator, 2, sizeof(uint8_t))); - - ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_termination_handler( - state_test_data->mqtt_connection, s_on_connection_termination_fn, state_test_data)); - - return AWS_OP_SUCCESS; -} - -static void s_received_publish_packet_list_clean_up(struct aws_array_list *list) { - for (size_t i = 0; i < aws_array_list_length(list); ++i) { - struct received_publish_packet *val_ptr = NULL; - aws_array_list_get_at_ptr(list, (void **)&val_ptr, i); - aws_byte_buf_clean_up(&val_ptr->payload); - aws_byte_buf_clean_up(&val_ptr->topic); - } - aws_array_list_clean_up(list); + return aws_test311_setup_mqtt_server_fn(allocator, ctx); } static int s_clean_up_mqtt_server_fn(struct aws_allocator *allocator, int setup_result, void *ctx) { - (void)allocator; - - if (!setup_result) { - struct mqtt_connection_state_test *state_test_data = ctx; - - s_received_publish_packet_list_clean_up(&state_test_data->published_messages); - s_received_publish_packet_list_clean_up(&state_test_data->any_published_messages); - aws_array_list_clean_up(&state_test_data->qos_returned); - aws_mqtt_client_connection_release(state_test_data->mqtt_connection); - - s_wait_for_termination_to_complete(state_test_data); - ASSERT_UINT_EQUALS(1, state_test_data->connection_termination_calls); - - aws_mqtt_client_release(state_test_data->mqtt_client); - aws_client_bootstrap_release(state_test_data->client_bootstrap); - aws_host_resolver_release(state_test_data->host_resolver); - aws_server_bootstrap_destroy_socket_listener(state_test_data->server_bootstrap, state_test_data->listener); - s_wait_on_listener_cleanup(state_test_data); - aws_server_bootstrap_release(state_test_data->server_bootstrap); - aws_event_loop_group_release(state_test_data->el_group); - destroy_mqtt_mock_server(state_test_data->mock_server); - } - - aws_mqtt_library_clean_up(); - return AWS_OP_SUCCESS; -} - -static void s_on_connection_complete_fn( - struct aws_mqtt_client_connection *connection, - int error_code, - enum aws_mqtt_connect_return_code return_code, - bool session_present, - void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - aws_mutex_lock(&state_test_data->lock); - - state_test_data->session_present = session_present; - state_test_data->mqtt_return_code = return_code; - state_test_data->error = error_code; - state_test_data->connection_completed = true; - aws_mutex_unlock(&state_test_data->lock); - - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_connection_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_completed; -} - -static void s_wait_for_connection_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_completed, state_test_data); - state_test_data->connection_completed = false; - aws_mutex_unlock(&state_test_data->lock); -} - -void s_on_disconnect_fn(struct aws_mqtt_client_connection *connection, void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "disconnect completed"); - aws_mutex_lock(&state_test_data->lock); - state_test_data->client_disconnect_completed = true; - aws_mutex_unlock(&state_test_data->lock); - - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_disconnect_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->client_disconnect_completed && state_test_data->server_disconnect_completed; -} - -static void s_wait_for_disconnect_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_disconnect_completed, state_test_data); - state_test_data->client_disconnect_completed = false; - state_test_data->server_disconnect_completed = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_any_publish_received( - struct aws_mqtt_client_connection *connection, - const struct aws_byte_cursor *topic, - const struct aws_byte_cursor *payload, - bool dup, - enum aws_mqtt_qos qos, - bool retain, - void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - - struct aws_byte_buf payload_cp; - aws_byte_buf_init_copy_from_cursor(&payload_cp, state_test_data->allocator, *payload); - struct aws_byte_buf topic_cp; - aws_byte_buf_init_copy_from_cursor(&topic_cp, state_test_data->allocator, *topic); - struct received_publish_packet received_packet = { - .payload = payload_cp, - .topic = topic_cp, - .dup = dup, - .qos = qos, - .retain = retain, - }; - - aws_mutex_lock(&state_test_data->lock); - aws_array_list_push_back(&state_test_data->any_published_messages, &received_packet); - state_test_data->any_publishes_received++; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_any_publish_received(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->any_publishes_received == state_test_data->expected_any_publishes; -} - -static void s_wait_for_any_publish(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_any_publish_received, state_test_data); - state_test_data->any_publishes_received = 0; - state_test_data->expected_any_publishes = 0; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_publish_received( - struct aws_mqtt_client_connection *connection, - const struct aws_byte_cursor *topic, - const struct aws_byte_cursor *payload, - bool dup, - enum aws_mqtt_qos qos, - bool retain, - void *userdata) { - - (void)connection; - (void)topic; - struct mqtt_connection_state_test *state_test_data = userdata; - - struct aws_byte_buf payload_cp; - aws_byte_buf_init_copy_from_cursor(&payload_cp, state_test_data->allocator, *payload); - struct aws_byte_buf topic_cp; - aws_byte_buf_init_copy_from_cursor(&topic_cp, state_test_data->allocator, *topic); - struct received_publish_packet received_packet = { - .payload = payload_cp, - .topic = topic_cp, - .dup = dup, - .qos = qos, - .retain = retain, - }; - - aws_mutex_lock(&state_test_data->lock); - aws_array_list_push_back(&state_test_data->published_messages, &received_packet); - state_test_data->publishes_received++; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_publish_received(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->publishes_received == state_test_data->expected_publishes; -} - -static void s_wait_for_publish(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_publish_received, state_test_data); - state_test_data->publishes_received = 0; - state_test_data->expected_publishes = 0; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_suback( - struct aws_mqtt_client_connection *connection, - uint16_t packet_id, - const struct aws_byte_cursor *topic, - enum aws_mqtt_qos qos, - int error_code, - void *userdata) { - (void)connection; - (void)packet_id; - (void)topic; - - struct mqtt_connection_state_test *state_test_data = userdata; - - aws_mutex_lock(&state_test_data->lock); - if (!error_code) { - aws_array_list_push_back(&state_test_data->qos_returned, &qos); - } - state_test_data->subscribe_completed = true; - state_test_data->subscribe_complete_error = error_code; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_subscribe_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->subscribe_completed; -} - -static void s_wait_for_subscribe_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_subscribe_completed, state_test_data); - state_test_data->subscribe_completed = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_multi_suback( - struct aws_mqtt_client_connection *connection, - uint16_t packet_id, - const struct aws_array_list *topic_subacks, /* contains aws_mqtt_topic_subscription pointers */ - int error_code, - void *userdata) { - (void)connection; - (void)packet_id; - (void)topic_subacks; - (void)error_code; - - struct mqtt_connection_state_test *state_test_data = userdata; - - aws_mutex_lock(&state_test_data->lock); - state_test_data->subscribe_completed = true; - if (!error_code) { - size_t length = aws_array_list_length(topic_subacks); - for (size_t i = 0; i < length; ++i) { - struct aws_mqtt_topic_subscription *subscription = NULL; - aws_array_list_get_at(topic_subacks, &subscription, i); - aws_array_list_push_back(&state_test_data->qos_returned, &subscription->qos); - } - } - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static void s_on_op_complete( - struct aws_mqtt_client_connection *connection, - uint16_t packet_id, - int error_code, - void *userdata) { - (void)connection; - (void)packet_id; - - struct mqtt_connection_state_test *state_test_data = userdata; - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "pub op completed"); - aws_mutex_lock(&state_test_data->lock); - state_test_data->ops_completed++; - state_test_data->op_complete_error = error_code; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_ops_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->ops_completed == state_test_data->expected_ops_completed; -} - -static void s_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_for_pred( - &state_test_data->cvar, &state_test_data->lock, 10000000000, s_is_ops_completed, state_test_data); - aws_mutex_unlock(&state_test_data->lock); + return aws_test311_clean_up_mqtt_server_fn(allocator, setup_result, ctx); } /* @@ -673,14 +34,14 @@ static int s_test_mqtt_connect_disconnect_fn(struct aws_allocator *allocator, vo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -730,14 +91,14 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -766,7 +127,7 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator /* Connect to the mock server again. If set will&loggin message is not called before the next connect, the * will&loggin message will still be there and be sent to the server again */ ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); /* The second CONNECT packet */ @@ -785,8 +146,8 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator /* disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* set new will & loggin message, before next connect, the next CONNECT packet will contain the new information */ struct aws_byte_cursor new_will_payload = aws_byte_cursor_from_c_str("this is a new will."); @@ -803,7 +164,7 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator /* connect again */ ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); /* The third CONNECT packet */ @@ -822,8 +183,8 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator /* disconnect. FINISHED */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -852,14 +213,14 @@ static int s_test_mqtt_connection_interrupted_fn(struct aws_allocator *allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; aws_mqtt_client_connection_set_reconnect_timeout( state_test_data->mqtt_connection, MIN_RECONNECT_DELAY_SECONDS, MAX_RECONNECT_DELAY_SECONDS); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* shut it down and make sure the client automatically reconnects.*/ uint64_t now = 0; @@ -867,7 +228,7 @@ static int s_test_mqtt_connection_interrupted_fn(struct aws_allocator *allocator uint64_t start_shutdown = now; aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&now); uint64_t reconnect_complete = now; @@ -878,8 +239,8 @@ static int s_test_mqtt_connection_interrupted_fn(struct aws_allocator *allocator MIN_RECONNECT_DELAY_SECONDS); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -922,21 +283,21 @@ static int s_test_mqtt_connection_timeout_fn(struct aws_allocator *allocator, vo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, }; mqtt_mock_server_set_max_ping_resp(state_test_data->mock_server, 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* this should take about 1.1 seconds for the timeout and reconnect.*/ - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_TIMEOUT, state_test_data->interruption_error); @@ -962,14 +323,14 @@ static int s_test_mqtt_connection_any_publish_fn(struct aws_allocator *allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor topic_1 = aws_byte_cursor_from_c_str("/test/topic1"); struct aws_byte_cursor topic_2 = aws_byte_cursor_from_c_str("/test/topic2"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* NOTE: mock server sends to client with no subscription at all, which should not happen in the real world! */ state_test_data->expected_any_publishes = 2; @@ -990,12 +351,12 @@ static int s_test_mqtt_connection_any_publish_fn(struct aws_allocator *allocator AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1048,14 +409,14 @@ static int s_test_mqtt_connection_connack_timeout_fn(struct aws_allocator *alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, }; mqtt_mock_server_set_max_connack(state_test_data->mock_server, 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_TIMEOUT, state_test_data->error); @@ -1080,14 +441,14 @@ static int s_test_mqtt_connection_failure_callback_fn(struct aws_allocator *allo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, }; mqtt_mock_server_set_max_connack(state_test_data->mock_server, 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_fail(state_test_data); + aws_test311_wait_for_connection_to_fail(state_test_data); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_TIMEOUT, state_test_data->error); @@ -1112,11 +473,11 @@ static int s_test_mqtt_connection_success_callback_fn(struct aws_allocator *allo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_succeed(state_test_data); + aws_test311_wait_for_connection_to_succeed(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1130,8 +491,8 @@ static int s_test_mqtt_connection_success_callback_fn(struct aws_allocator *allo // Disconnect and finish ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -1155,7 +516,7 @@ static int s_test_mqtt_subscribe_fn(struct aws_allocator *allocator, void *ctx) .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor sub_topic = aws_byte_cursor_from_c_str("/test/topic"); @@ -1164,17 +525,17 @@ static int s_test_mqtt_subscribe_fn(struct aws_allocator *allocator, void *ctx) state_test_data->mqtt_connection, &sub_topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); state_test_data->expected_publishes = 2; state_test_data->expected_any_publishes = 2; @@ -1195,13 +556,13 @@ static int s_test_mqtt_subscribe_fn(struct aws_allocator *allocator, void *ctx) AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_publish(state_test_data); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1282,7 +643,7 @@ static int s_test_mqtt_subscribe_incoming_dup_fn(struct aws_allocator *allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor subscribed_topic = aws_byte_cursor_from_c_str("/test/topic"); @@ -1292,16 +653,16 @@ static int s_test_mqtt_subscribe_incoming_dup_fn(struct aws_allocator *allocator state_test_data->mqtt_connection, &subscribed_topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); state_test_data->expected_publishes = 4; state_test_data->expected_any_publishes = 8; @@ -1330,13 +691,13 @@ static int s_test_mqtt_subscribe_incoming_dup_fn(struct aws_allocator *allocator false /*retain*/)); } - s_wait_for_publish(state_test_data); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 8); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1419,11 +780,11 @@ static int s_test_mqtt_connect_subscribe_fail_from_broker_fn(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, we will send failure SUBACK */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -1434,15 +795,15 @@ static int s_test_mqtt_connect_subscribe_fail_from_broker_fn(struct aws_allocato state_test_data->mqtt_connection, &sub_topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id > 0); ASSERT_SUCCESS(mqtt_mock_server_send_single_suback(state_test_data->mock_server, packet_id, AWS_MQTT_QOS_FAILURE)); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); /* Check the subscribe returned QoS is failure */ size_t length = aws_array_list_length(&state_test_data->qos_returned); ASSERT_UINT_EQUALS(1, length); @@ -1451,8 +812,8 @@ static int s_test_mqtt_connect_subscribe_fail_from_broker_fn(struct aws_allocato ASSERT_UINT_EQUALS(AWS_MQTT_QOS_FAILURE, qos); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -1476,7 +837,7 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor sub_topic_1 = aws_byte_cursor_from_c_str("/test/topic1"); @@ -1485,14 +846,14 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void struct aws_mqtt_topic_subscription sub1 = { .topic = sub_topic_1, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; struct aws_mqtt_topic_subscription sub2 = { .topic = sub_topic_2, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; @@ -1506,13 +867,13 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void aws_array_list_push_back(&topic_filters, &sub2); uint16_t packet_id = aws_mqtt_client_connection_subscribe_multiple( - state_test_data->mqtt_connection, &topic_filters, s_on_multi_suback, state_test_data); + state_test_data->mqtt_connection, &topic_filters, aws_test311_on_multi_suback, state_test_data); ASSERT_TRUE(packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); /* Check the subscribe returned QoS is expected */ size_t length = aws_array_list_length(&state_test_data->qos_returned); ASSERT_UINT_EQUALS(2, length); @@ -1539,7 +900,7 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void false /*dup*/, AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_publish(state_test_data); + aws_test311_wait_for_publish(state_test_data); /* Let's do another publish on a topic that is not subscribed by client. * This can happen if the Server automatically assigned a subscription to the Client */ @@ -1553,13 +914,13 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void false /*dup*/, AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 3); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1627,7 +988,7 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor sub_topic_1 = aws_byte_cursor_from_c_str("/test/topic1"); @@ -1636,14 +997,14 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx struct aws_mqtt_topic_subscription sub1 = { .topic = sub_topic_1, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; struct aws_mqtt_topic_subscription sub2 = { .topic = sub_topic_2, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; @@ -1657,13 +1018,13 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx aws_array_list_push_back(&topic_filters, &sub2); uint16_t sub_packet_id = aws_mqtt_client_connection_subscribe_multiple( - state_test_data->mqtt_connection, &topic_filters, s_on_multi_suback, state_test_data); + state_test_data->mqtt_connection, &topic_filters, aws_test311_on_multi_suback, state_test_data); ASSERT_TRUE(sub_packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); state_test_data->expected_any_publishes = 2; struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); @@ -1682,7 +1043,7 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx false /*dup*/, AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); aws_mutex_lock(&state_test_data->lock); @@ -1690,7 +1051,7 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx aws_mutex_unlock(&state_test_data->lock); /* unsubscribe to the first topic */ uint16_t unsub_packet_id = aws_mqtt_client_connection_unsubscribe( - state_test_data->mqtt_connection, &sub_topic_1, s_on_op_complete, state_test_data); + state_test_data->mqtt_connection, &sub_topic_1, aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(unsub_packet_id > 0); /* Even when the UNSUBACK has not received, the client will not invoke the on_pub callback for that topic */ ASSERT_SUCCESS(mqtt_mock_server_send_publish( @@ -1708,13 +1069,13 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); state_test_data->expected_any_publishes = 2; - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1797,7 +1158,7 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor sub_topic_1 = aws_byte_cursor_from_c_str("/test/topic1"); @@ -1807,21 +1168,21 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx struct aws_mqtt_topic_subscription sub1 = { .topic = sub_topic_1, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; struct aws_mqtt_topic_subscription sub2 = { .topic = sub_topic_2, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; struct aws_mqtt_topic_subscription sub3 = { .topic = sub_topic_3, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; @@ -1836,31 +1197,31 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx aws_array_list_push_back(&topic_filters, &sub3); uint16_t sub_packet_id = aws_mqtt_client_connection_subscribe_multiple( - state_test_data->mqtt_connection, &topic_filters, s_on_multi_suback, state_test_data); + state_test_data->mqtt_connection, &topic_filters, aws_test311_on_multi_suback, state_test_data); ASSERT_TRUE(sub_packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 1; aws_mutex_unlock(&state_test_data->lock); /* unsubscribe to the first topic */ uint16_t unsub_packet_id = aws_mqtt_client_connection_unsubscribe( - state_test_data->mqtt_connection, &sub_topic_1, s_on_op_complete, state_test_data); + state_test_data->mqtt_connection, &sub_topic_1, aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(unsub_packet_id > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* client still subscribes to topic_2 & topic_3 */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* reconnection to the same server */ ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Get all the packets out of the way */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1871,13 +1232,13 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx /* resubscribes to topic_2 & topic_3 */ uint16_t resub_packet_id = - aws_mqtt_resubscribe_existing_topics(state_test_data->mqtt_connection, s_on_multi_suback, state_test_data); + aws_mqtt_resubscribe_existing_topics(state_test_data->mqtt_connection, aws_test311_on_multi_suback, state_test_data); ASSERT_TRUE(resub_packet_id > 0); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); struct mqtt_decoded_packet *received_packet = @@ -1915,7 +1276,7 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); @@ -1923,7 +1284,7 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { struct aws_byte_cursor payload_2 = aws_byte_cursor_from_c_str("Test Message 2"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 3; @@ -1934,7 +1295,7 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); uint16_t packet_id_2 = aws_mqtt_client_connection_publish( @@ -1943,7 +1304,7 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_2, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_2 > 0); @@ -1954,15 +1315,15 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { AWS_MQTT_QOS_AT_LEAST_ONCE, false, NULL, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_3 > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -2012,7 +1373,7 @@ static int s_test_mqtt_publish_payload_fn(struct aws_allocator *allocator, void .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); @@ -2022,7 +1383,7 @@ static int s_test_mqtt_publish_payload_fn(struct aws_allocator *allocator, void struct aws_byte_cursor payload_curser = aws_byte_cursor_from_buf(&buf_payload); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 1; @@ -2033,17 +1394,17 @@ static int s_test_mqtt_publish_payload_fn(struct aws_allocator *allocator, void AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_curser, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id > 0); /* clean up the payload buf, as user don't need to manage the buf and keep it valid until publish completes */ aws_byte_buf_clean_up(&buf_payload); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -2087,19 +1448,19 @@ static int s_test_mqtt_connection_offline_publish_fn(struct aws_allocator *alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); mqtt_mock_server_set_max_connack(state_test_data->mock_server, 0); /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_interrupt_to_complete(state_test_data); + aws_test311_wait_for_interrupt_to_complete(state_test_data); state_test_data->server_disconnect_completed = false; @@ -2118,7 +1479,7 @@ static int s_test_mqtt_connection_offline_publish_fn(struct aws_allocator *alloc AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_publish( @@ -2127,21 +1488,21 @@ static int s_test_mqtt_connection_offline_publish_fn(struct aws_allocator *alloc AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_2, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); aws_mutex_lock(&state_test_data->lock); ASSERT_FALSE(state_test_data->connection_resumed); aws_mutex_unlock(&state_test_data->lock); mqtt_mock_server_set_max_connack(state_test_data->mock_server, SIZE_MAX); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); aws_mutex_lock(&state_test_data->lock); ASSERT_TRUE(state_test_data->connection_resumed); aws_mutex_unlock(&state_test_data->lock); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -2205,19 +1566,19 @@ static int s_test_mqtt_connection_disconnect_while_reconnecting(struct aws_alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); mqtt_mock_server_set_max_connack(state_test_data->mock_server, 0); /* shut it down and the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_interrupt_to_complete(state_test_data); + aws_test311_wait_for_interrupt_to_complete(state_test_data); struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); @@ -2234,7 +1595,7 @@ static int s_test_mqtt_connection_disconnect_while_reconnecting(struct aws_alloc AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_publish( @@ -2243,15 +1604,15 @@ static int s_test_mqtt_connection_disconnect_while_reconnecting(struct aws_alloc AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_2, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); aws_mqtt_client_connection_release(state_test_data->mqtt_connection); state_test_data->mqtt_connection = NULL; - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); return AWS_OP_SUCCESS; } @@ -2281,7 +1642,7 @@ static int s_test_mqtt_connection_closes_while_making_requests_fn(struct aws_all .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; @@ -2290,7 +1651,7 @@ static int s_test_mqtt_connection_closes_while_making_requests_fn(struct aws_all struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 1; @@ -2310,16 +1671,16 @@ static int s_test_mqtt_connection_closes_while_making_requests_fn(struct aws_all AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); - s_wait_for_reconnect_to_complete(state_test_data); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2381,7 +1742,7 @@ static int s_test_mqtt_connection_resend_packets_fn(struct aws_allocator *alloca .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; @@ -2393,7 +1754,7 @@ static int s_test_mqtt_connection_resend_packets_fn(struct aws_allocator *alloca struct aws_byte_cursor payload_3 = aws_byte_cursor_from_c_str("Test Message 3"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -2408,10 +1769,10 @@ static int s_test_mqtt_connection_resend_packets_fn(struct aws_allocator *alloca state_test_data->mqtt_connection, &sub_topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_ids[1] > 0); @@ -2435,15 +1796,15 @@ static int s_test_mqtt_connection_resend_packets_fn(struct aws_allocator *alloca /* shutdown the channel for some error */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* Wait again, and ensure the publishes have been resent */ aws_thread_current_sleep(ONE_SEC); ASSERT_SUCCESS(s_check_resend_packets( state_test_data->mock_server, packet_count, true, packet_ids, AWS_ARRAY_SIZE(packet_ids))); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2468,7 +1829,7 @@ static int s_test_mqtt_connection_not_retry_publish_QoS_0_fn(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ }; @@ -2477,7 +1838,7 @@ static int s_test_mqtt_connection_not_retry_publish_QoS_0_fn(struct aws_allocato struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* kill the connection */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); @@ -2493,14 +1854,14 @@ static int s_test_mqtt_connection_not_retry_publish_QoS_0_fn(struct aws_allocato AWS_MQTT_QOS_AT_MOST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); /* publish should complete after the shutdown */ - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* wait for reconnect */ - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* Check all received packets, no publish packets ever received by the server. Because the connection lost before it * ever get sent. */ @@ -2508,8 +1869,8 @@ static int s_test_mqtt_connection_not_retry_publish_QoS_0_fn(struct aws_allocato ASSERT_NULL( mqtt_mock_server_find_decoded_packet_by_type(state_test_data->mock_server, 0, AWS_MQTT_PACKET_PUBLISH, NULL)); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2535,7 +1896,7 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .protocol_operation_timeout_ms = 3000, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ @@ -2546,7 +1907,7 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato struct aws_byte_cursor sub_topic = aws_byte_cursor_from_c_str("/test/topic"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ @@ -2563,7 +1924,7 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); /* make another subscribe */ @@ -2574,12 +1935,12 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato NULL, NULL, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id_2 > 0); /* wait for reconnect */ - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* Wait for 1 sec. ensure all the requests have been received by the server */ aws_thread_current_sleep(ONE_SEC); @@ -2594,9 +1955,9 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato mqtt_mock_server_enable_auto_ack(handler); /* Kill the connection again, the requests will be retried since the response has not been received yet. */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* subscribe should be able to completed now */ - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); /* Check all received packets, subscribe and publish has been resent */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(handler)); @@ -2607,8 +1968,8 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato ASSERT_NOT_NULL(mqtt_mock_server_find_decoded_packet_by_type(handler, packet_count, AWS_MQTT_PACKET_PUBLISH, NULL)); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2635,7 +1996,7 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; @@ -2645,7 +2006,7 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( struct aws_byte_cursor sub_topic = aws_byte_cursor_from_c_str("/test/topic"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ @@ -2661,7 +2022,7 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); /* make another subscribe */ @@ -2672,7 +2033,7 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( NULL, NULL, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id_2 > 0); @@ -2694,8 +2055,8 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( } ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2723,17 +2084,17 @@ static int s_test_mqtt_connection_destory_pending_requests_fn(struct aws_allocat AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_subscribe( state_test_data->mqtt_connection, &topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data) > 0); return AWS_OP_SUCCESS; @@ -2757,13 +2118,13 @@ static int s_test_mqtt_clean_session_not_retry_fn(struct aws_allocator *allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; mqtt_mock_server_disable_auto_ack(handler); @@ -2777,24 +2138,24 @@ static int s_test_mqtt_clean_session_not_retry_fn(struct aws_allocator *allocato AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_subscribe( state_test_data->mqtt_connection, &topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data) > 0); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 1; aws_mutex_unlock(&state_test_data->lock); /* Shutdown the connection */ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_interrupt_to_complete(state_test_data); + aws_test311_wait_for_interrupt_to_complete(state_test_data); /* Once the connection lost, the requests will fail */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION); @@ -2802,8 +2163,8 @@ static int s_test_mqtt_clean_session_not_retry_fn(struct aws_allocator *allocato /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2826,7 +2187,7 @@ static int s_test_mqtt_clean_session_discard_previous_fn(struct aws_allocator *a .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ }; @@ -2844,25 +2205,25 @@ static int s_test_mqtt_clean_session_discard_previous_fn(struct aws_allocator *a AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_subscribe( state_test_data->mqtt_connection, &topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data) > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; - s_wait_for_ops_completed(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION); ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION); @@ -2873,8 +2234,8 @@ static int s_test_mqtt_clean_session_discard_previous_fn(struct aws_allocator *a /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2896,7 +2257,7 @@ static int s_test_mqtt_clean_session_keep_next_session_fn(struct aws_allocator * .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ }; @@ -2916,23 +2277,23 @@ static int s_test_mqtt_clean_session_keep_next_session_fn(struct aws_allocator * AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_subscribe( state_test_data->mqtt_connection, &topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data) > 0); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; - s_wait_for_ops_completed(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_SUCCESS); ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_SUCCESS); @@ -2943,8 +2304,8 @@ static int s_test_mqtt_clean_session_keep_next_session_fn(struct aws_allocator * /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2968,7 +2329,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_fn(struct aws_allocator * .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .protocol_operation_timeout_ms = 3000, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ @@ -2978,7 +2339,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_fn(struct aws_allocator * struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -2993,17 +2354,17 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_fn(struct aws_allocator * AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); /* publish should complete after the shutdown */ - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Check the publish has been completed with timeout error */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_TIMEOUT); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3028,7 +2389,7 @@ static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocat .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .protocol_operation_timeout_ms = 3000, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ @@ -3037,7 +2398,7 @@ static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocat struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -3048,16 +2409,16 @@ static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocat /* unsubscribe to the first topic */ uint16_t unsub_packet_id = aws_mqtt_client_connection_unsubscribe( - state_test_data->mqtt_connection, &pub_topic, s_on_op_complete, state_test_data); + state_test_data->mqtt_connection, &pub_topic, aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(unsub_packet_id > 0); /* publish should complete after the shutdown */ - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Check the publish has been completed with timeout error */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_TIMEOUT); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3084,7 +2445,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .protocol_operation_timeout_ms = 3000, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ @@ -3094,7 +2455,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -3109,7 +2470,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); @@ -3117,7 +2478,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim aws_thread_current_sleep((uint64_t)ONE_SEC * 2); /* Kill the connection, the requests will be retried and the timeout will be reset. */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* sleep for 2 sec again, in total the response has not received for more than 4 sec, timeout should happen if the * lost of connection not reset the timeout */ aws_thread_current_sleep((uint64_t)ONE_SEC * 2); @@ -3125,12 +2486,12 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim ASSERT_SUCCESS(mqtt_mock_server_send_puback(state_test_data->mock_server, packet_id_1)); /* publish should complete after the shutdown */ - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Check the publish has been completed successfully since the lost of the connection reset the timeout */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_SUCCESS); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3170,21 +2531,21 @@ static int s_test_mqtt_connection_close_callback_simple_fn(struct aws_allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; aws_mqtt_client_connection_set_connection_closed_handler( state_test_data->mqtt_connection, s_on_connection_closed_fn, state_test_data); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* sleep for 2 sec, just to make sure the connection is stable */ aws_thread_current_sleep((uint64_t)ONE_SEC * 2); /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Make sure the callback was called and the value is what we expect */ ASSERT_UINT_EQUALS(1, state_test_data->connection_close_calls); @@ -3212,25 +2573,25 @@ static int s_test_mqtt_connection_close_callback_interrupted_fn(struct aws_alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; aws_mqtt_client_connection_set_connection_closed_handler( state_test_data->mqtt_connection, s_on_connection_closed_fn, state_test_data); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Kill the connection */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* sleep for 2 sec, just to make sure the connection is stable */ aws_thread_current_sleep((uint64_t)ONE_SEC * 2); /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Make sure the callback was called only ONCE and the value is what we expect */ ASSERT_UINT_EQUALS(1, state_test_data->connection_close_calls); @@ -3258,7 +2619,7 @@ static int s_test_mqtt_connection_close_callback_multi_fn(struct aws_allocator * .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; aws_mqtt_client_connection_set_connection_closed_handler( state_test_data->mqtt_connection, s_on_connection_closed_fn, state_test_data); @@ -3266,12 +2627,12 @@ static int s_test_mqtt_connection_close_callback_multi_fn(struct aws_allocator * int disconnect_amount = 10; for (int i = 0; i < disconnect_amount; i++) { ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disconnect */ ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( - state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); } /* Make sure the callback was called disconnect_amount times */ @@ -3299,11 +2660,11 @@ static int s_test_mqtt_connection_reconnection_backoff_stable(struct aws_allocat .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); uint64_t time_before = 0; uint64_t time_after = 0; @@ -3317,7 +2678,7 @@ static int s_test_mqtt_connection_reconnection_backoff_stable(struct aws_allocat /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); @@ -3331,8 +2692,8 @@ static int s_test_mqtt_connection_reconnection_backoff_stable(struct aws_allocat /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3356,11 +2717,11 @@ static int s_test_mqtt_connection_reconnection_backoff_unstable(struct aws_alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); uint64_t time_before = 0; uint64_t time_after = 0; @@ -3371,7 +2732,7 @@ static int s_test_mqtt_connection_reconnection_backoff_unstable(struct aws_alloc /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); @@ -3388,8 +2749,8 @@ static int s_test_mqtt_connection_reconnection_backoff_unstable(struct aws_alloc /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3413,11 +2774,11 @@ static int s_test_mqtt_connection_reconnection_backoff_reset(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); uint64_t time_before = 0; uint64_t time_after = 0; @@ -3429,7 +2790,7 @@ static int s_test_mqtt_connection_reconnection_backoff_reset(struct aws_allocato /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); reconnection_backoff = time_after - time_before; @@ -3449,7 +2810,7 @@ static int s_test_mqtt_connection_reconnection_backoff_reset(struct aws_allocato /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); reconnection_backoff = time_after - time_before; @@ -3461,8 +2822,8 @@ static int s_test_mqtt_connection_reconnection_backoff_reset(struct aws_allocato /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3488,11 +2849,11 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); uint64_t time_before = 0; uint64_t time_after = 0; @@ -3503,7 +2864,7 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); reconnection_backoff = time_after - time_before; @@ -3515,17 +2876,17 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection } /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* connect again */ ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_before); aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); reconnection_backoff = time_after - time_before; @@ -3537,8 +2898,8 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection /* Disconnect */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3563,7 +2924,7 @@ static int s_test_mqtt_connection_ping_norm_fn(struct aws_allocator *allocator, .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 1, .ping_timeout_ms = 100, }; @@ -3577,8 +2938,8 @@ static int s_test_mqtt_connection_ping_norm_fn(struct aws_allocator *allocator, ASSERT_INT_EQUALS(4, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3604,13 +2965,13 @@ static int s_test_mqtt_connection_ping_no_fn(struct aws_allocator *allocator, vo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 1, .ping_timeout_ms = 100, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); @@ -3630,7 +2991,7 @@ static int s_test_mqtt_connection_ping_no_fn(struct aws_allocator *allocator, vo AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id > 0); @@ -3646,8 +3007,8 @@ static int s_test_mqtt_connection_ping_no_fn(struct aws_allocator *allocator, vo ASSERT_INT_EQUALS(0, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3673,13 +3034,13 @@ static int s_test_mqtt_connection_ping_noack_fn(struct aws_allocator *allocator, .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 1, .ping_timeout_ms = 100, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); @@ -3699,7 +3060,7 @@ static int s_test_mqtt_connection_ping_noack_fn(struct aws_allocator *allocator, AWS_MQTT_QOS_AT_MOST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id > 0); @@ -3714,8 +3075,8 @@ static int s_test_mqtt_connection_ping_noack_fn(struct aws_allocator *allocator, ASSERT_INT_EQUALS(4, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3747,13 +3108,13 @@ static int s_test_mqtt_connection_ping_basic_scenario_fn(struct aws_allocator *a .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 4, .ping_timeout_ms = 100, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* PING will be in 4 seconds */ aws_thread_current_sleep(3000000000); /* Wait 3 seconds */ @@ -3771,10 +3132,10 @@ static int s_test_mqtt_connection_ping_basic_scenario_fn(struct aws_allocator *a AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Publish packet written at 3 seconds */ aws_thread_current_sleep(1250000000); /* Wait 1.25 second (the extra 0.25 is to account for jitter) */ @@ -3798,8 +3159,8 @@ static int s_test_mqtt_connection_ping_basic_scenario_fn(struct aws_allocator *a /* Disconnect and finish! */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3825,13 +3186,13 @@ static int s_test_mqtt_connection_ping_double_scenario_fn(struct aws_allocator * .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 4, .ping_timeout_ms = 100, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* PING will be in 4 seconds */ aws_thread_current_sleep(3000000000); /* Wait 3 seconds */ @@ -3849,10 +3210,10 @@ static int s_test_mqtt_connection_ping_double_scenario_fn(struct aws_allocator * AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Publish packet written at 3 seconds */ aws_thread_current_sleep(1250000000); /* Wait 1.25 second (the extra 0.25 is to account for jitter) */ @@ -3874,10 +3235,10 @@ static int s_test_mqtt_connection_ping_double_scenario_fn(struct aws_allocator * AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_2 > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Publish packet written at 2 seconds (relative to PING that was scheduled above) */ aws_thread_current_sleep(4250000000); /* Wait 4.25 (the extra 0.25 is to account for jitter) seconds */ @@ -3896,8 +3257,8 @@ static int s_test_mqtt_connection_ping_double_scenario_fn(struct aws_allocator * /* Disconnect and finish! */ ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3918,7 +3279,7 @@ static int s_test_mqtt_connection_termination_callback_simple_fn(struct aws_allo struct mqtt_connection_state_test *state_test_data = ctx; ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_termination_handler( - state_test_data->mqtt_connection, s_on_connection_termination_fn, state_test_data)); + state_test_data->mqtt_connection, aws_test311_on_connection_termination_fn, state_test_data)); return AWS_OP_SUCCESS; } @@ -3943,11 +3304,11 @@ static int s_test_mqtt_validation_failure_publish_qos_fn(struct aws_allocator *a .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_byte_cursor topic = aws_byte_cursor_from_c_str("a/b"); ASSERT_INT_EQUALS( @@ -3958,14 +3319,14 @@ static int s_test_mqtt_validation_failure_publish_qos_fn(struct aws_allocator *a (enum aws_mqtt_qos)3, true, NULL, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data)); int error_code = aws_last_error(); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_INVALID_QOS, error_code); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3990,11 +3351,11 @@ static int s_test_mqtt_validation_failure_subscribe_empty_fn(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_array_list topic_filters; size_t list_len = 2; @@ -4004,13 +3365,13 @@ static int s_test_mqtt_validation_failure_subscribe_empty_fn(struct aws_allocato ASSERT_INT_EQUALS( 0, aws_mqtt_client_connection_subscribe_multiple( - state_test_data->mqtt_connection, &topic_filters, s_on_multi_suback, state_test_data)); + state_test_data->mqtt_connection, &topic_filters, aws_test311_on_multi_suback, state_test_data)); int error_code = aws_last_error(); ASSERT_INT_EQUALS(AWS_ERROR_INVALID_ARGUMENT, error_code); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -4035,22 +3396,22 @@ static int s_test_mqtt_validation_failure_unsubscribe_null_fn(struct aws_allocat .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_INT_EQUALS( 0, aws_mqtt_client_connection_unsubscribe( - state_test_data->mqtt_connection, NULL, s_on_op_complete, state_test_data)); + state_test_data->mqtt_connection, NULL, aws_test311_on_op_complete, state_test_data)); int error_code = aws_last_error(); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_INVALID_TOPIC, error_code); ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -4078,7 +3439,7 @@ static int s_test_mqtt_validation_failure_connect_invalid_client_id_utf8_fn( .client_id = s_bad_client_id_utf8, .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_FAILS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); diff --git a/tests/v3/mqtt311_testing_utils.c b/tests/v3/mqtt311_testing_utils.c new file mode 100644 index 00000000..06d58d6e --- /dev/null +++ b/tests/v3/mqtt311_testing_utils.c @@ -0,0 +1,582 @@ +/** +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0. +*/ + +#include "mqtt311_testing_utils.h" + +#include +#include +#include +#include +#include +#include + +#include "mqtt_mock_server_handler.h" + + +static void s_on_incoming_channel_setup_fn( + struct aws_server_bootstrap *bootstrap, + int error_code, + struct aws_channel *channel, + void *user_data) { + (void)bootstrap; + struct mqtt_connection_state_test *state_test_data = user_data; + + state_test_data->error = error_code; + + if (!error_code) { + aws_mutex_lock(&state_test_data->lock); + state_test_data->server_disconnect_completed = false; + aws_mutex_unlock(&state_test_data->lock); + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "server channel setup completed"); + + state_test_data->server_channel = channel; + struct aws_channel_slot *test_handler_slot = aws_channel_slot_new(channel); + aws_channel_slot_insert_end(channel, test_handler_slot); + mqtt_mock_server_handler_update_slot(state_test_data->mock_server, test_handler_slot); + aws_channel_slot_set_handler(test_handler_slot, state_test_data->mock_server); + } +} + +static void s_on_incoming_channel_shutdown_fn( + struct aws_server_bootstrap *bootstrap, + int error_code, + struct aws_channel *channel, + void *user_data) { + (void)bootstrap; + (void)error_code; + (void)channel; + struct mqtt_connection_state_test *state_test_data = user_data; + aws_mutex_lock(&state_test_data->lock); + state_test_data->server_disconnect_completed = true; + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "server channel shutdown completed"); + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static void s_on_listener_destroy(struct aws_server_bootstrap *bootstrap, void *user_data) { + (void)bootstrap; + struct mqtt_connection_state_test *state_test_data = user_data; + aws_mutex_lock(&state_test_data->lock); + state_test_data->listener_destroyed = true; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_listener_destroyed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->listener_destroyed; +} + +static void s_wait_on_listener_cleanup(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_listener_destroyed, state_test_data); + aws_mutex_unlock(&state_test_data->lock); +} + +static void s_on_connection_interrupted(struct aws_mqtt_client_connection *connection, int error_code, void *userdata) { + (void)connection; + (void)error_code; + struct mqtt_connection_state_test *state_test_data = userdata; + + aws_mutex_lock(&state_test_data->lock); + state_test_data->connection_interrupted = true; + state_test_data->interruption_error = error_code; + aws_mutex_unlock(&state_test_data->lock); + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "connection interrupted"); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_connection_interrupted(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_interrupted; +} + +void aws_test311_wait_for_interrupt_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_interrupted, state_test_data); + state_test_data->connection_interrupted = false; + aws_mutex_unlock(&state_test_data->lock); +} + +static void s_on_connection_resumed( + struct aws_mqtt_client_connection *connection, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata) { + (void)connection; + (void)return_code; + (void)session_present; + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "reconnect completed"); + + struct mqtt_connection_state_test *state_test_data = userdata; + + aws_mutex_lock(&state_test_data->lock); + state_test_data->connection_resumed = true; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_connection_resumed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_resumed; +} + +void aws_test311_wait_for_reconnect_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_resumed, state_test_data); + state_test_data->connection_resumed = false; + aws_mutex_unlock(&state_test_data->lock); +} + +static void s_on_connection_success( + struct aws_mqtt_client_connection *connection, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + aws_mutex_lock(&state_test_data->lock); + + state_test_data->session_present = session_present; + state_test_data->mqtt_return_code = return_code; + state_test_data->connection_success = true; + aws_mutex_unlock(&state_test_data->lock); + + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static void s_on_connection_failure(struct aws_mqtt_client_connection *connection, int error_code, void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + aws_mutex_lock(&state_test_data->lock); + + state_test_data->error = error_code; + state_test_data->connection_failure = true; + aws_mutex_unlock(&state_test_data->lock); + + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_connection_succeed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_success; +} + +static bool s_is_connection_failed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_failure; +} + +void aws_test311_wait_for_connection_to_succeed(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_succeed, state_test_data); + state_test_data->connection_success = false; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_wait_for_connection_to_fail(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_failed, state_test_data); + state_test_data->connection_failure = false; + aws_mutex_unlock(&state_test_data->lock); +} + +static bool s_is_termination_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_terminated; +} + +static void s_wait_for_termination_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_termination_completed, state_test_data); + state_test_data->connection_terminated = false; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_connection_termination_fn(void *userdata) { + struct mqtt_connection_state_test *state_test_data = (struct mqtt_connection_state_test *)userdata; + + aws_mutex_lock(&state_test_data->lock); + state_test_data->connection_termination_calls += 1; + state_test_data->connection_terminated = true; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static void s_on_any_publish_received( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain, + void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + + struct aws_byte_buf payload_cp; + aws_byte_buf_init_copy_from_cursor(&payload_cp, state_test_data->allocator, *payload); + struct aws_byte_buf topic_cp; + aws_byte_buf_init_copy_from_cursor(&topic_cp, state_test_data->allocator, *topic); + struct received_publish_packet received_packet = { + .payload = payload_cp, + .topic = topic_cp, + .dup = dup, + .qos = qos, + .retain = retain, + }; + + aws_mutex_lock(&state_test_data->lock); + aws_array_list_push_back(&state_test_data->any_published_messages, &received_packet); + state_test_data->any_publishes_received++; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +/** + * sets up a unix domain socket server and socket options. Creates an mqtt connection configured to use + * the domain socket. + */ +int aws_test311_setup_mqtt_server_fn(struct aws_allocator *allocator, void *ctx) { + aws_mqtt_library_init(allocator); + + struct mqtt_connection_state_test *state_test_data = ctx; + + AWS_ZERO_STRUCT(*state_test_data); + + state_test_data->allocator = allocator; + state_test_data->el_group = aws_event_loop_group_new_default(allocator, 1, NULL); + + state_test_data->mock_server = new_mqtt_mock_server(allocator); + ASSERT_NOT_NULL(state_test_data->mock_server); + + state_test_data->server_bootstrap = aws_server_bootstrap_new(allocator, state_test_data->el_group); + ASSERT_NOT_NULL(state_test_data->server_bootstrap); + + struct aws_socket_options socket_options = { + .connect_timeout_ms = 100, + .domain = AWS_SOCKET_LOCAL, + }; + + state_test_data->socket_options = socket_options; + ASSERT_SUCCESS(aws_condition_variable_init(&state_test_data->cvar)); + ASSERT_SUCCESS(aws_mutex_init(&state_test_data->lock)); + + aws_socket_endpoint_init_local_address_for_test(&state_test_data->endpoint); + + struct aws_server_socket_channel_bootstrap_options server_bootstrap_options = { + .bootstrap = state_test_data->server_bootstrap, + .host_name = state_test_data->endpoint.address, + .port = state_test_data->endpoint.port, + .socket_options = &state_test_data->socket_options, + .incoming_callback = s_on_incoming_channel_setup_fn, + .shutdown_callback = s_on_incoming_channel_shutdown_fn, + .destroy_callback = s_on_listener_destroy, + .user_data = state_test_data, + }; + state_test_data->listener = aws_server_bootstrap_new_socket_listener(&server_bootstrap_options); + + ASSERT_NOT_NULL(state_test_data->listener); + + struct aws_host_resolver_default_options resolver_options = { + .el_group = state_test_data->el_group, + .max_entries = 1, + }; + state_test_data->host_resolver = aws_host_resolver_new_default(allocator, &resolver_options); + + struct aws_client_bootstrap_options bootstrap_options = { + .event_loop_group = state_test_data->el_group, + .user_data = state_test_data, + .host_resolver = state_test_data->host_resolver, + }; + + state_test_data->client_bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); + + state_test_data->mqtt_client = aws_mqtt_client_new(allocator, state_test_data->client_bootstrap); + state_test_data->mqtt_connection = aws_mqtt_client_connection_new(state_test_data->mqtt_client); + ASSERT_NOT_NULL(state_test_data->mqtt_connection); + + ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_interruption_handlers( + state_test_data->mqtt_connection, + s_on_connection_interrupted, + state_test_data, + s_on_connection_resumed, + state_test_data)); + + ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_result_handlers( + state_test_data->mqtt_connection, + s_on_connection_success, + state_test_data, + s_on_connection_failure, + state_test_data)); + + ASSERT_SUCCESS(aws_mqtt_client_connection_set_on_any_publish_handler( + state_test_data->mqtt_connection, s_on_any_publish_received, state_test_data)); + + ASSERT_SUCCESS(aws_array_list_init_dynamic( + &state_test_data->published_messages, allocator, 4, sizeof(struct received_publish_packet))); + ASSERT_SUCCESS(aws_array_list_init_dynamic( + &state_test_data->any_published_messages, allocator, 4, sizeof(struct received_publish_packet))); + ASSERT_SUCCESS(aws_array_list_init_dynamic(&state_test_data->qos_returned, allocator, 2, sizeof(uint8_t))); + + ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_termination_handler( + state_test_data->mqtt_connection, aws_test311_on_connection_termination_fn, state_test_data)); + + return AWS_OP_SUCCESS; +} + +static void s_received_publish_packet_list_clean_up(struct aws_array_list *list) { + for (size_t i = 0; i < aws_array_list_length(list); ++i) { + struct received_publish_packet *val_ptr = NULL; + aws_array_list_get_at_ptr(list, (void **)&val_ptr, i); + aws_byte_buf_clean_up(&val_ptr->payload); + aws_byte_buf_clean_up(&val_ptr->topic); + } + aws_array_list_clean_up(list); +} + +int aws_test311_clean_up_mqtt_server_fn(struct aws_allocator *allocator, int setup_result, void *ctx) { + (void)allocator; + + if (!setup_result) { + struct mqtt_connection_state_test *state_test_data = ctx; + + s_received_publish_packet_list_clean_up(&state_test_data->published_messages); + s_received_publish_packet_list_clean_up(&state_test_data->any_published_messages); + aws_array_list_clean_up(&state_test_data->qos_returned); + aws_mqtt_client_connection_release(state_test_data->mqtt_connection); + + s_wait_for_termination_to_complete(state_test_data); + ASSERT_UINT_EQUALS(1, state_test_data->connection_termination_calls); + + aws_mqtt_client_release(state_test_data->mqtt_client); + aws_client_bootstrap_release(state_test_data->client_bootstrap); + aws_host_resolver_release(state_test_data->host_resolver); + aws_server_bootstrap_destroy_socket_listener(state_test_data->server_bootstrap, state_test_data->listener); + s_wait_on_listener_cleanup(state_test_data); + aws_server_bootstrap_release(state_test_data->server_bootstrap); + aws_event_loop_group_release(state_test_data->el_group); + destroy_mqtt_mock_server(state_test_data->mock_server); + } + + aws_mqtt_library_clean_up(); + return AWS_OP_SUCCESS; +} + +void aws_test311_on_connection_complete_fn( + struct aws_mqtt_client_connection *connection, + int error_code, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + aws_mutex_lock(&state_test_data->lock); + + state_test_data->session_present = session_present; + state_test_data->mqtt_return_code = return_code; + state_test_data->error = error_code; + state_test_data->connection_completed = true; + aws_mutex_unlock(&state_test_data->lock); + + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_connection_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_completed; +} + +void aws_test311_wait_for_connection_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_completed, state_test_data); + state_test_data->connection_completed = false; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_disconnect_fn(struct aws_mqtt_client_connection *connection, void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "disconnect completed"); + aws_mutex_lock(&state_test_data->lock); + state_test_data->client_disconnect_completed = true; + aws_mutex_unlock(&state_test_data->lock); + + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_disconnect_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->client_disconnect_completed && state_test_data->server_disconnect_completed; +} + +void aws_test311_wait_for_disconnect_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_disconnect_completed, state_test_data); + state_test_data->client_disconnect_completed = false; + state_test_data->server_disconnect_completed = false; + aws_mutex_unlock(&state_test_data->lock); +} + + +static bool s_is_any_publish_received(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->any_publishes_received == state_test_data->expected_any_publishes; +} + +void aws_test311_wait_for_any_publish(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_any_publish_received, state_test_data); + state_test_data->any_publishes_received = 0; + state_test_data->expected_any_publishes = 0; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_publish_received( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain, + void *userdata) { + + (void)connection; + (void)topic; + struct mqtt_connection_state_test *state_test_data = userdata; + + struct aws_byte_buf payload_cp; + aws_byte_buf_init_copy_from_cursor(&payload_cp, state_test_data->allocator, *payload); + struct aws_byte_buf topic_cp; + aws_byte_buf_init_copy_from_cursor(&topic_cp, state_test_data->allocator, *topic); + struct received_publish_packet received_packet = { + .payload = payload_cp, + .topic = topic_cp, + .dup = dup, + .qos = qos, + .retain = retain, + }; + + aws_mutex_lock(&state_test_data->lock); + aws_array_list_push_back(&state_test_data->published_messages, &received_packet); + state_test_data->publishes_received++; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_publish_received(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->publishes_received == state_test_data->expected_publishes; +} + +void aws_test311_wait_for_publish(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_publish_received, state_test_data); + state_test_data->publishes_received = 0; + state_test_data->expected_publishes = 0; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_suback( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + int error_code, + void *userdata) { + (void)connection; + (void)packet_id; + (void)topic; + + struct mqtt_connection_state_test *state_test_data = userdata; + + aws_mutex_lock(&state_test_data->lock); + if (!error_code) { + aws_array_list_push_back(&state_test_data->qos_returned, &qos); + } + state_test_data->subscribe_completed = true; + state_test_data->subscribe_complete_error = error_code; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_subscribe_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->subscribe_completed; +} + +void aws_test311_wait_for_subscribe_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_subscribe_completed, state_test_data); + state_test_data->subscribe_completed = false; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_multi_suback( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + const struct aws_array_list *topic_subacks, /* contains aws_mqtt_topic_subscription pointers */ + int error_code, + void *userdata) { + (void)connection; + (void)packet_id; + (void)topic_subacks; + (void)error_code; + + struct mqtt_connection_state_test *state_test_data = userdata; + + aws_mutex_lock(&state_test_data->lock); + state_test_data->subscribe_completed = true; + if (!error_code) { + size_t length = aws_array_list_length(topic_subacks); + for (size_t i = 0; i < length; ++i) { + struct aws_mqtt_topic_subscription *subscription = NULL; + aws_array_list_get_at(topic_subacks, &subscription, i); + aws_array_list_push_back(&state_test_data->qos_returned, &subscription->qos); + } + } + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +void aws_test311_on_op_complete( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + int error_code, + void *userdata) { + (void)connection; + (void)packet_id; + + struct mqtt_connection_state_test *state_test_data = userdata; + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "pub op completed"); + aws_mutex_lock(&state_test_data->lock); + state_test_data->ops_completed++; + state_test_data->op_complete_error = error_code; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_ops_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->ops_completed == state_test_data->expected_ops_completed; +} + +void aws_test311_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_for_pred( + &state_test_data->cvar, &state_test_data->lock, 10000000000, s_is_ops_completed, state_test_data); + aws_mutex_unlock(&state_test_data->lock); +} \ No newline at end of file diff --git a/tests/v3/mqtt311_testing_utils.h b/tests/v3/mqtt311_testing_utils.h new file mode 100644 index 00000000..d3a1d8d7 --- /dev/null +++ b/tests/v3/mqtt311_testing_utils.h @@ -0,0 +1,155 @@ +/** +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0. +*/ + +#ifndef AWS_C_MQTT_MQTT311_TESTING_UTILS_H +#define AWS_C_MQTT_MQTT311_TESTING_UTILS_H + +#include + +#include +#include +#include +#include +#include + +#include + +#define TEST_LOG_SUBJECT 60000 +#define ONE_SEC 1000000000 +// The value is extract from aws-c-mqtt/source/client.c +#define AWS_RESET_RECONNECT_BACKOFF_DELAY_SECONDS 10 +#define RECONNECT_BACKOFF_DELAY_ERROR_MARGIN_NANO_SECONDS 500000000 +#define DEFAULT_MIN_RECONNECT_DELAY_SECONDS 1 + +#define DEFAULT_TEST_PING_TIMEOUT_MS 1000 +#define DEFAULT_TEST_KEEP_ALIVE_S 2 + +struct received_publish_packet { + struct aws_byte_buf topic; + struct aws_byte_buf payload; + bool dup; + enum aws_mqtt_qos qos; + bool retain; +}; + +struct mqtt_connection_state_test { + struct aws_allocator *allocator; + struct aws_channel *server_channel; + struct aws_channel_handler *mock_server; + struct aws_client_bootstrap *client_bootstrap; + struct aws_server_bootstrap *server_bootstrap; + struct aws_event_loop_group *el_group; + struct aws_host_resolver *host_resolver; + struct aws_socket_endpoint endpoint; + struct aws_socket *listener; + struct aws_mqtt_client *mqtt_client; + struct aws_mqtt_client_connection *mqtt_connection; + struct aws_socket_options socket_options; + + bool session_present; + bool connection_completed; + bool connection_success; + bool connection_failure; + bool client_disconnect_completed; + bool server_disconnect_completed; + bool connection_interrupted; + bool connection_resumed; + bool subscribe_completed; + bool listener_destroyed; + bool connection_terminated; + int interruption_error; + int subscribe_complete_error; + int op_complete_error; + enum aws_mqtt_connect_return_code mqtt_return_code; + int error; + struct aws_condition_variable cvar; + struct aws_mutex lock; + /* any published messages from mock server, that you may not subscribe to. (Which should not happen in real life) */ + struct aws_array_list any_published_messages; /* list of struct received_publish_packet */ + size_t any_publishes_received; + size_t expected_any_publishes; + /* the published messages from mock server, that you did subscribe to. */ + struct aws_array_list published_messages; /* list of struct received_publish_packet */ + size_t publishes_received; + size_t expected_publishes; + /* The returned QoS from mock server */ + struct aws_array_list qos_returned; /* list of uint_8 */ + size_t ops_completed; + size_t expected_ops_completed; + size_t connection_close_calls; /* All of the times on_connection_closed has been called */ + + size_t connection_termination_calls; /* How many times on_connection_termination has been called, should be 1 */ +}; + +AWS_EXTERN_C_BEGIN + +AWS_MQTT_API int aws_test311_setup_mqtt_server_fn(struct aws_allocator *allocator, void *ctx); + +AWS_MQTT_API int aws_test311_clean_up_mqtt_server_fn(struct aws_allocator *allocator, int setup_result, void *ctx); + +AWS_MQTT_API void aws_test311_wait_for_interrupt_to_complete(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_wait_for_reconnect_to_complete(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_wait_for_connection_to_succeed(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_wait_for_connection_to_fail(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_on_connection_complete_fn( + struct aws_mqtt_client_connection *connection, + int error_code, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata); + +AWS_MQTT_API void aws_test311_wait_for_connection_to_complete(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_on_disconnect_fn(struct aws_mqtt_client_connection *connection, void *userdata); + +AWS_MQTT_API void aws_test311_wait_for_disconnect_to_complete(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_wait_for_any_publish(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_on_publish_received( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain, + void *userdata); + +AWS_MQTT_API void aws_test311_wait_for_publish(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_on_suback( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + int error_code, + void *userdata); + +AWS_MQTT_API void aws_test311_wait_for_subscribe_to_complete(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_on_multi_suback( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + const struct aws_array_list *topic_subacks, + int error_code, + void *userdata); + +AWS_MQTT_API void aws_test311_on_op_complete( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + int error_code, + void *userdata); + +AWS_MQTT_API void aws_test311_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data); + +AWS_MQTT_API void aws_test311_on_connection_termination_fn(void *userdata); + +AWS_EXTERN_C_END + +#endif // AWS_C_MQTT_MQTT311_TESTING_UTILS_H From addabe14346c102f9bd5c69a4c7ce0964dc565fa Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 24 Jan 2024 09:55:27 -0800 Subject: [PATCH 017/124] Formatting --- include/aws/mqtt/private/mqtt311_listener.h | 7 +- source/client_channel_handler.c | 6 +- source/client_impl_shared.c | 3 +- source/mqtt311_listener.c | 44 +++-- tests/v3/connection_state_test.c | 186 ++++++++++---------- tests/v3/mqtt311_testing_utils.c | 8 +- tests/v3/mqtt311_testing_utils.h | 6 +- 7 files changed, 137 insertions(+), 123 deletions(-) diff --git a/include/aws/mqtt/private/mqtt311_listener.h b/include/aws/mqtt/private/mqtt311_listener.h index 5e000148..1d5febee 100644 --- a/include/aws/mqtt/private/mqtt311_listener.h +++ b/include/aws/mqtt/private/mqtt311_listener.h @@ -54,7 +54,6 @@ struct aws_mqtt311_callback_set_manager { uint64_t next_callback_set_entry_id; }; - /** * Configuration options for MQTT311 listener objects. */ @@ -109,7 +108,6 @@ AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_acquire(struct aw */ AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_release(struct aws_mqtt311_listener *listener); - /** * Initializes a callback set manager */ @@ -144,7 +142,9 @@ uint64_t aws_mqtt311_callback_set_manager_push_front( * May only be called on the client's event loop thread. */ AWS_MQTT_API -void aws_mqtt311_callback_set_manager_remove(struct aws_mqtt311_callback_set_manager *manager, uint64_t callback_set_id); +void aws_mqtt311_callback_set_manager_remove( + struct aws_mqtt311_callback_set_manager *manager, + uint64_t callback_set_id); /** * Walks the incoming publish handler chain for an MQTT311 connection. The chain's callbacks will be invoked @@ -161,7 +161,6 @@ void aws_mqtt311_callback_set_manager_on_publish_received( enum aws_mqtt_qos qos, bool retain); - AWS_MQTT_API void aws_mqtt311_callback_set_manager_on_connection_resumed( struct aws_mqtt311_callback_set_manager *manager, diff --git a/source/client_channel_handler.c b/source/client_channel_handler.c index f1d91835..198d3fe3 100644 --- a/source/client_channel_handler.c +++ b/source/client_channel_handler.c @@ -241,7 +241,8 @@ static int s_packet_handler_connack(struct aws_byte_cursor message_cursor, void MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_resumed, connack.connect_return_code, connack.session_present); - aws_mqtt311_callback_set_manager_on_connection_resumed(&connection->callback_manager, connack.connect_return_code, connack.session_present); + aws_mqtt311_callback_set_manager_on_connection_resumed( + &connection->callback_manager, connack.connect_return_code, connack.session_present); } else { aws_create_reconnect_task(connection); @@ -293,7 +294,8 @@ static int s_packet_handler_publish(struct aws_byte_cursor message_cursor, void MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_any_publish, &publish.topic_name, &publish.payload, dup, qos, retain); - aws_mqtt311_callback_set_manager_on_publish_received(&connection->callback_manager, &publish.topic_name, &publish.payload, dup, qos, retain); + aws_mqtt311_callback_set_manager_on_publish_received( + &connection->callback_manager, &publish.topic_name, &publish.payload, dup, qos, retain); AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, diff --git a/source/client_impl_shared.c b/source/client_impl_shared.c index 7c4fc52e..525fb7e4 100644 --- a/source/client_impl_shared.c +++ b/source/client_impl_shared.c @@ -204,7 +204,8 @@ int aws_mqtt_client_connection_get_stats( return (*connection->vtable->get_stats_fn)(connection->impl, stats); } -enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type(const struct aws_mqtt_client_connection *connection) { +enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type( + const struct aws_mqtt_client_connection *connection) { return (*connection->vtable->get_impl_type)(connection->impl); } diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c index f061a3a7..27eb0f4a 100644 --- a/source/mqtt311_listener.c +++ b/source/mqtt311_listener.c @@ -13,7 +13,8 @@ #include -static struct aws_event_loop *s_mqtt_client_connection_get_event_loop(const struct aws_mqtt_client_connection *connection) { +static struct aws_event_loop *s_mqtt_client_connection_get_event_loop( + const struct aws_mqtt_client_connection *connection) { AWS_FATAL_ASSERT(aws_mqtt_client_connection_get_impl_type(connection) == AWS_MQTT311_IT_311_CONNECTION_IMPL); struct aws_mqtt_client_connection_311_impl *connection_impl = connection->impl; @@ -90,7 +91,8 @@ static void s_mqtt311_listener_terminate_task_fn(struct aws_task *task, void *ar static void s_aws_mqtt311_listener_on_zero_ref_count(void *context) { struct aws_mqtt311_listener *listener = context; - aws_event_loop_schedule_task_now(s_mqtt_client_connection_get_event_loop(listener->config.connection), &listener->terminate_task); + aws_event_loop_schedule_task_now( + s_mqtt_client_connection_get_event_loop(listener->config.connection), &listener->terminate_task); } struct aws_mqtt311_listener *aws_mqtt311_listener_new( @@ -112,11 +114,14 @@ struct aws_mqtt311_listener *aws_mqtt311_listener_new( aws_mqtt_client_connection_acquire(config->connection); aws_ref_count_init(&listener->ref_count, listener, s_aws_mqtt311_listener_on_zero_ref_count); - aws_task_init(&listener->initialize_task, s_mqtt311_listener_initialize_task_fn, listener, "Mqtt311ListenerInitialize"); - aws_task_init(&listener->terminate_task, s_mqtt311_listener_terminate_task_fn, listener, "Mqtt311ListenerTerminate"); + aws_task_init( + &listener->initialize_task, s_mqtt311_listener_initialize_task_fn, listener, "Mqtt311ListenerInitialize"); + aws_task_init( + &listener->terminate_task, s_mqtt311_listener_terminate_task_fn, listener, "Mqtt311ListenerTerminate"); aws_mqtt311_listener_acquire(listener); - aws_event_loop_schedule_task_now(s_mqtt_client_connection_get_event_loop(config->connection), &listener->initialize_task); + aws_event_loop_schedule_task_now( + s_mqtt_client_connection_get_event_loop(config->connection), &listener->initialize_task); return listener; } @@ -137,7 +142,6 @@ struct aws_mqtt311_listener *aws_mqtt311_listener_release(struct aws_mqtt311_lis return NULL; } - struct aws_mqtt311_callback_set_entry { struct aws_allocator *allocator; @@ -163,7 +167,8 @@ void aws_mqtt311_callback_set_manager_init( void aws_mqtt311_callback_set_manager_clean_up(struct aws_mqtt311_callback_set_manager *manager) { struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); while (node != aws_linked_list_end(&manager->callback_set_entries)) { - struct aws_mqtt311_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); node = aws_linked_list_next(node); aws_linked_list_remove(&entry->node); @@ -194,7 +199,8 @@ uint64_t aws_mqtt311_callback_set_manager_push_front( struct aws_mqtt311_callback_set_manager *manager, struct aws_mqtt311_callback_set *callback_set) { - AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(s_mqtt_client_connection_get_event_loop(manager->connection))); + AWS_FATAL_ASSERT( + aws_event_loop_thread_is_callers_thread(s_mqtt_client_connection_get_event_loop(manager->connection))); struct aws_mqtt311_callback_set_entry *entry = s_new_311_callback_set_entry(manager, callback_set); @@ -203,13 +209,17 @@ uint64_t aws_mqtt311_callback_set_manager_push_front( return entry->id; } -void aws_mqtt311_callback_set_manager_remove(struct aws_mqtt311_callback_set_manager *manager, uint64_t callback_set_id) { +void aws_mqtt311_callback_set_manager_remove( + struct aws_mqtt311_callback_set_manager *manager, + uint64_t callback_set_id) { - AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(s_mqtt_client_connection_get_event_loop(manager->connection))); + AWS_FATAL_ASSERT( + aws_event_loop_thread_is_callers_thread(s_mqtt_client_connection_get_event_loop(manager->connection))); struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); while (node != aws_linked_list_end(&manager->callback_set_entries)) { - struct aws_mqtt311_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); node = aws_linked_list_next(node); if (entry->id == callback_set_id) { @@ -244,12 +254,14 @@ void aws_mqtt311_callback_set_manager_on_publish_received( struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); while (node != aws_linked_list_end(&manager->callback_set_entries)) { - struct aws_mqtt311_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); node = aws_linked_list_next(node); struct aws_mqtt311_callback_set *callback_set = &entry->callbacks; if (callback_set->publish_received_handler != NULL) { - (*callback_set->publish_received_handler)(manager->connection, topic, payload, dup, qos, retain, callback_set->user_data); + (*callback_set->publish_received_handler)( + manager->connection, topic, payload, dup, qos, retain, callback_set->user_data); } } } @@ -264,12 +276,14 @@ void aws_mqtt311_callback_set_manager_on_connection_resumed( struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); while (node != aws_linked_list_end(&manager->callback_set_entries)) { - struct aws_mqtt311_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); node = aws_linked_list_next(node); struct aws_mqtt311_callback_set *callback_set = &entry->callbacks; if (callback_set->connection_resumed_handler != NULL) { - (*callback_set->connection_resumed_handler)(manager->connection, return_code, rejoined_session, callback_set->user_data); + (*callback_set->connection_resumed_handler)( + manager->connection, return_code, rejoined_session, callback_set->user_data); } } } diff --git a/tests/v3/connection_state_test.c b/tests/v3/connection_state_test.c index 8c5cbc8a..65f8b9e7 100644 --- a/tests/v3/connection_state_test.c +++ b/tests/v3/connection_state_test.c @@ -5,8 +5,8 @@ #include -#include "mqtt_mock_server_handler.h" #include "mqtt311_testing_utils.h" +#include "mqtt_mock_server_handler.h" #include #include @@ -39,8 +39,8 @@ static int s_test_mqtt_connect_disconnect_fn(struct aws_allocator *allocator, vo ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); aws_test311_wait_for_connection_to_complete(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -96,8 +96,8 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); aws_test311_wait_for_connection_to_complete(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -145,8 +145,8 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator ASSERT_TRUE(aws_byte_cursor_eq(&received_packet->password, &password)); /* disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* set new will & loggin message, before next connect, the next CONNECT packet will contain the new information */ @@ -182,8 +182,8 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator ASSERT_TRUE(aws_byte_cursor_eq(&received_packet->password, &new_password)); /* disconnect. FINISHED */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -238,8 +238,8 @@ static int s_test_mqtt_connection_interrupted_fn(struct aws_allocator *allocator aws_timestamp_convert(elapsed_time, AWS_TIMESTAMP_NANOS, AWS_TIMESTAMP_SECS, NULL) >= MIN_RECONNECT_DELAY_SECONDS); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -295,8 +295,8 @@ static int s_test_mqtt_connection_timeout_fn(struct aws_allocator *allocator, vo /* this should take about 1.1 seconds for the timeout and reconnect.*/ aws_test311_wait_for_reconnect_to_complete(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_TIMEOUT, state_test_data->interruption_error); @@ -354,8 +354,8 @@ static int s_test_mqtt_connection_any_publish_fn(struct aws_allocator *allocator aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -490,8 +490,8 @@ static int s_test_mqtt_connection_success_callback_fn(struct aws_allocator *allo ASSERT_TRUE(aws_byte_cursor_eq(&received_packet->client_identifier, &connection_options.client_id)); // Disconnect and finish - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -560,8 +560,8 @@ static int s_test_mqtt_subscribe_fn(struct aws_allocator *allocator, void *ctx) aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -695,8 +695,8 @@ static int s_test_mqtt_subscribe_incoming_dup_fn(struct aws_allocator *allocator aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 8); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -811,8 +811,8 @@ static int s_test_mqtt_connect_subscribe_fail_from_broker_fn(struct aws_allocato ASSERT_SUCCESS(aws_array_list_get_at(&state_test_data->qos_returned, &qos, 0)); ASSERT_UINT_EQUALS(AWS_MQTT_QOS_FAILURE, qos); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -918,8 +918,8 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 3); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -1073,8 +1073,8 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); aws_test311_wait_for_ops_completed(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -1215,8 +1215,8 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx aws_test311_wait_for_ops_completed(state_test_data); /* client still subscribes to topic_2 & topic_3 */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* reconnection to the same server */ @@ -1231,13 +1231,13 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx ASSERT_UINT_EQUALS(AWS_MQTT_PACKET_CONNECT, t_received_packet->type); /* resubscribes to topic_2 & topic_3 */ - uint16_t resub_packet_id = - aws_mqtt_resubscribe_existing_topics(state_test_data->mqtt_connection, aws_test311_on_multi_suback, state_test_data); + uint16_t resub_packet_id = aws_mqtt_resubscribe_existing_topics( + state_test_data->mqtt_connection, aws_test311_on_multi_suback, state_test_data); ASSERT_TRUE(resub_packet_id > 0); aws_test311_wait_for_subscribe_to_complete(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1321,8 +1321,8 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { aws_test311_wait_for_ops_completed(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -1402,8 +1402,8 @@ static int s_test_mqtt_publish_payload_fn(struct aws_allocator *allocator, void aws_byte_buf_clean_up(&buf_payload); aws_test311_wait_for_ops_completed(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -1500,8 +1500,8 @@ static int s_test_mqtt_connection_offline_publish_fn(struct aws_allocator *alloc ASSERT_TRUE(state_test_data->connection_resumed); aws_mutex_unlock(&state_test_data->lock); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ @@ -1607,8 +1607,8 @@ static int s_test_mqtt_connection_disconnect_while_reconnecting(struct aws_alloc aws_test311_on_op_complete, state_test_data) > 0); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); aws_mqtt_client_connection_release(state_test_data->mqtt_connection); state_test_data->mqtt_connection = NULL; @@ -1678,8 +1678,8 @@ static int s_test_mqtt_connection_closes_while_making_requests_fn(struct aws_all aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_test311_wait_for_ops_completed(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -1802,8 +1802,8 @@ static int s_test_mqtt_connection_resend_packets_fn(struct aws_allocator *alloca ASSERT_SUCCESS(s_check_resend_packets( state_test_data->mock_server, packet_count, true, packet_ids, AWS_ARRAY_SIZE(packet_ids))); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -1868,8 +1868,8 @@ static int s_test_mqtt_connection_not_retry_publish_QoS_0_fn(struct aws_allocato ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); ASSERT_NULL( mqtt_mock_server_find_decoded_packet_by_type(state_test_data->mock_server, 0, AWS_MQTT_PACKET_PUBLISH, NULL)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -1967,8 +1967,8 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato mqtt_mock_server_find_decoded_packet_by_type(handler, packet_count, AWS_MQTT_PACKET_SUBSCRIBE, NULL)); ASSERT_NOT_NULL(mqtt_mock_server_find_decoded_packet_by_type(handler, packet_count, AWS_MQTT_PACKET_PUBLISH, NULL)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -2054,8 +2054,8 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( mqtt_mock_server_find_decoded_packet_by_type(handler, pre_index + 1, AWS_MQTT_PACKET_SUBSCRIBE, NULL)); } - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -2162,8 +2162,8 @@ static int s_test_mqtt_clean_session_not_retry_fn(struct aws_allocator *allocato ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2233,8 +2233,8 @@ static int s_test_mqtt_clean_session_discard_previous_fn(struct aws_allocator *a ASSERT_NULL(mqtt_mock_server_find_decoded_packet_by_type(handler, 0, AWS_MQTT_PACKET_SUBSCRIBE, NULL)); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2303,8 +2303,8 @@ static int s_test_mqtt_clean_session_keep_next_session_fn(struct aws_allocator * ASSERT_NOT_NULL(mqtt_mock_server_find_decoded_packet_by_type(handler, 0, AWS_MQTT_PACKET_SUBSCRIBE, NULL)); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2362,8 +2362,8 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_fn(struct aws_allocator * aws_test311_wait_for_ops_completed(state_test_data); /* Check the publish has been completed with timeout error */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_TIMEOUT); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -2416,8 +2416,8 @@ static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocat aws_test311_wait_for_ops_completed(state_test_data); /* Check the publish has been completed with timeout error */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_TIMEOUT); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -2489,8 +2489,8 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim aws_test311_wait_for_ops_completed(state_test_data); /* Check the publish has been completed successfully since the lost of the connection reset the timeout */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_SUCCESS); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -2543,8 +2543,8 @@ static int s_test_mqtt_connection_close_callback_simple_fn(struct aws_allocator aws_thread_current_sleep((uint64_t)ONE_SEC * 2); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Make sure the callback was called and the value is what we expect */ @@ -2589,8 +2589,8 @@ static int s_test_mqtt_connection_close_callback_interrupted_fn(struct aws_alloc aws_thread_current_sleep((uint64_t)ONE_SEC * 2); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Make sure the callback was called only ONCE and the value is what we expect */ @@ -2691,8 +2691,8 @@ static int s_test_mqtt_connection_reconnection_backoff_stable(struct aws_allocat } /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -2748,8 +2748,8 @@ static int s_test_mqtt_connection_reconnection_backoff_unstable(struct aws_alloc } /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -2821,8 +2821,8 @@ static int s_test_mqtt_connection_reconnection_backoff_reset(struct aws_allocato ASSERT_TRUE(remainder <= RECONNECT_BACKOFF_DELAY_ERROR_MARGIN_NANO_SECONDS); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -2875,8 +2875,8 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection expected_reconnect_backoff = aws_min_u64(expected_reconnect_backoff * 2, 10); } /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); /* connect again */ @@ -2897,8 +2897,8 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection ASSERT_TRUE(remainder <= RECONNECT_BACKOFF_DELAY_ERROR_MARGIN_NANO_SECONDS); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -2937,8 +2937,8 @@ static int s_test_mqtt_connection_ping_norm_fn(struct aws_allocator *allocator, /* Ensure the server got 4 PING packets */ ASSERT_INT_EQUALS(4, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -3006,8 +3006,8 @@ static int s_test_mqtt_connection_ping_no_fn(struct aws_allocator *allocator, vo /* Ensure the server got 0 PING packets */ ASSERT_INT_EQUALS(0, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -3074,8 +3074,8 @@ static int s_test_mqtt_connection_ping_noack_fn(struct aws_allocator *allocator, /* Ensure the server got 4 PING packets */ ASSERT_INT_EQUALS(4, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -3158,8 +3158,8 @@ static int s_test_mqtt_connection_ping_basic_scenario_fn(struct aws_allocator *a ASSERT_INT_EQUALS(2, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); /* Disconnect and finish! */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -3256,8 +3256,8 @@ static int s_test_mqtt_connection_ping_double_scenario_fn(struct aws_allocator * */ /* Disconnect and finish! */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -3324,8 +3324,8 @@ static int s_test_mqtt_validation_failure_publish_qos_fn(struct aws_allocator *a int error_code = aws_last_error(); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_INVALID_QOS, error_code); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -3369,8 +3369,8 @@ static int s_test_mqtt_validation_failure_subscribe_empty_fn(struct aws_allocato int error_code = aws_last_error(); ASSERT_INT_EQUALS(AWS_ERROR_INVALID_ARGUMENT, error_code); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; @@ -3409,8 +3409,8 @@ static int s_test_mqtt_validation_failure_unsubscribe_null_fn(struct aws_allocat int error_code = aws_last_error(); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_INVALID_TOPIC, error_code); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; diff --git a/tests/v3/mqtt311_testing_utils.c b/tests/v3/mqtt311_testing_utils.c index 06d58d6e..52a55e92 100644 --- a/tests/v3/mqtt311_testing_utils.c +++ b/tests/v3/mqtt311_testing_utils.c @@ -1,7 +1,7 @@ /** -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0. -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ #include "mqtt311_testing_utils.h" @@ -14,7 +14,6 @@ #include "mqtt_mock_server_handler.h" - static void s_on_incoming_channel_setup_fn( struct aws_server_bootstrap *bootstrap, int error_code, @@ -428,7 +427,6 @@ void aws_test311_wait_for_disconnect_to_complete(struct mqtt_connection_state_te aws_mutex_unlock(&state_test_data->lock); } - static bool s_is_any_publish_received(void *arg) { struct mqtt_connection_state_test *state_test_data = arg; return state_test_data->any_publishes_received == state_test_data->expected_any_publishes; diff --git a/tests/v3/mqtt311_testing_utils.h b/tests/v3/mqtt311_testing_utils.h index d3a1d8d7..8ea134a0 100644 --- a/tests/v3/mqtt311_testing_utils.h +++ b/tests/v3/mqtt311_testing_utils.h @@ -1,7 +1,7 @@ /** -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0. -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ #ifndef AWS_C_MQTT_MQTT311_TESTING_UTILS_H #define AWS_C_MQTT_MQTT311_TESTING_UTILS_H From 397b4634114e923379e5b08e16bef32d7f41d1fa Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 24 Jan 2024 10:29:14 -0800 Subject: [PATCH 018/124] Checkpoint --- tests/v3/mqtt311_listener_test.c | 13 +++++++++++ tests/v3/mqtt311_testing_utils.h | 38 ++++++++++++++++---------------- 2 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 tests/v3/mqtt311_listener_test.c diff --git a/tests/v3/mqtt311_listener_test.c b/tests/v3/mqtt311_listener_test.c new file mode 100644 index 00000000..2c1ab9d3 --- /dev/null +++ b/tests/v3/mqtt311_listener_test.c @@ -0,0 +1,13 @@ +/** +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0. +*/ + +#include + +#include "mqtt311_testing_utils.h" +#include "mqtt_mock_server_handler.h" + +#include + + diff --git a/tests/v3/mqtt311_testing_utils.h b/tests/v3/mqtt311_testing_utils.h index 8ea134a0..e9162c20 100644 --- a/tests/v3/mqtt311_testing_utils.h +++ b/tests/v3/mqtt311_testing_utils.h @@ -85,34 +85,34 @@ struct mqtt_connection_state_test { AWS_EXTERN_C_BEGIN -AWS_MQTT_API int aws_test311_setup_mqtt_server_fn(struct aws_allocator *allocator, void *ctx); +int aws_test311_setup_mqtt_server_fn(struct aws_allocator *allocator, void *ctx); -AWS_MQTT_API int aws_test311_clean_up_mqtt_server_fn(struct aws_allocator *allocator, int setup_result, void *ctx); +int aws_test311_clean_up_mqtt_server_fn(struct aws_allocator *allocator, int setup_result, void *ctx); -AWS_MQTT_API void aws_test311_wait_for_interrupt_to_complete(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_interrupt_to_complete(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_wait_for_reconnect_to_complete(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_reconnect_to_complete(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_wait_for_connection_to_succeed(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_connection_to_succeed(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_wait_for_connection_to_fail(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_connection_to_fail(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_on_connection_complete_fn( +void aws_test311_on_connection_complete_fn( struct aws_mqtt_client_connection *connection, int error_code, enum aws_mqtt_connect_return_code return_code, bool session_present, void *userdata); -AWS_MQTT_API void aws_test311_wait_for_connection_to_complete(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_connection_to_complete(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_on_disconnect_fn(struct aws_mqtt_client_connection *connection, void *userdata); +void aws_test311_on_disconnect_fn(struct aws_mqtt_client_connection *connection, void *userdata); -AWS_MQTT_API void aws_test311_wait_for_disconnect_to_complete(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_disconnect_to_complete(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_wait_for_any_publish(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_any_publish(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_on_publish_received( +void aws_test311_on_publish_received( struct aws_mqtt_client_connection *connection, const struct aws_byte_cursor *topic, const struct aws_byte_cursor *payload, @@ -121,9 +121,9 @@ AWS_MQTT_API void aws_test311_on_publish_received( bool retain, void *userdata); -AWS_MQTT_API void aws_test311_wait_for_publish(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_publish(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_on_suback( +void aws_test311_on_suback( struct aws_mqtt_client_connection *connection, uint16_t packet_id, const struct aws_byte_cursor *topic, @@ -131,24 +131,24 @@ AWS_MQTT_API void aws_test311_on_suback( int error_code, void *userdata); -AWS_MQTT_API void aws_test311_wait_for_subscribe_to_complete(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_subscribe_to_complete(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_on_multi_suback( +void aws_test311_on_multi_suback( struct aws_mqtt_client_connection *connection, uint16_t packet_id, const struct aws_array_list *topic_subacks, int error_code, void *userdata); -AWS_MQTT_API void aws_test311_on_op_complete( +void aws_test311_on_op_complete( struct aws_mqtt_client_connection *connection, uint16_t packet_id, int error_code, void *userdata); -AWS_MQTT_API void aws_test311_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data); +void aws_test311_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data); -AWS_MQTT_API void aws_test311_on_connection_termination_fn(void *userdata); +void aws_test311_on_connection_termination_fn(void *userdata); AWS_EXTERN_C_END From b1fe1f93c0e1fd46c00fbef8463c854fecd18faf Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 24 Jan 2024 11:02:22 -0800 Subject: [PATCH 019/124] Feedback --- include/aws/mqtt/private/client_impl_shared.h | 4 +-- .../request-response/protocol_adapter.h | 6 ++-- include/aws/mqtt/v5/mqtt5_client.h | 3 ++ source/client.c | 2 +- source/request-response/protocol_adapter.c | 30 +++++++++++-------- source/v5/mqtt5_to_mqtt3_adapter.c | 2 +- .../request_response_protocol_adapter_tests.c | 2 +- 7 files changed, 29 insertions(+), 20 deletions(-) diff --git a/include/aws/mqtt/private/client_impl_shared.h b/include/aws/mqtt/private/client_impl_shared.h index 9f28894f..49961308 100644 --- a/include/aws/mqtt/private/client_impl_shared.h +++ b/include/aws/mqtt/private/client_impl_shared.h @@ -18,10 +18,10 @@ struct aws_mqtt_client_connection; enum aws_mqtt311_impl_type { /* 311 connection impl can be cast to `struct aws_mqtt_client_connection_311_impl` */ - AWS_MQTT311_IT_311_CONNECTION_IMPL, + AWS_MQTT311_IT_311_CONNECTION, /* 311 connection impl can be cast to `struct aws_mqtt_client_connection_5_impl`*/ - AWS_MQTT311_IT_5_ADAPTER_IMPL, + AWS_MQTT311_IT_5_ADAPTER, }; struct aws_mqtt_client_connection_vtable { diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 4b439925..6f596601 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -21,7 +21,7 @@ struct aws_mqtt5_client; * Valid protocol clients include the CRT MQTT5 client, the CRT MQTT311 client, and an eventstream RPC connection * that belongs to a Greengrass IPC client. Each of these protocol clients has a different (or even implicit) * contract for carrying out pub-sub operations. The protocol adapter abstracts these details with a simple, - * minimal interface based on the requirements identigied in the request-response design documents. + * minimal interface based on the requirements identified in the request-response design documents. */ /* @@ -132,7 +132,7 @@ struct aws_mqtt_protocol_adapter_options { struct aws_mqtt_protocol_adapter_vtable { - void (*aws_mqtt_protocol_adapter_delete_fn)(void *); + void (*aws_mqtt_protocol_adapter_destroy_fn)(void *); int (*aws_mqtt_protocol_adapter_subscribe_fn)(void *, struct aws_protocol_adapter_subscribe_options *); @@ -168,7 +168,7 @@ AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_fro * Destroys a request-response protocol adapter. Destruction is an asynchronous process and the caller must * wait for the termination callback to be invoked before assuming that no further callbacks will be invoked. */ -AWS_MQTT_API void aws_mqtt_protocol_adapter_delete(struct aws_mqtt_protocol_adapter *adapter); +AWS_MQTT_API void aws_mqtt_protocol_adapter_destroy(struct aws_mqtt_protocol_adapter *adapter); /* * Asks the adapted protocol client to perform an MQTT subscribe operation diff --git a/include/aws/mqtt/v5/mqtt5_client.h b/include/aws/mqtt/v5/mqtt5_client.h index d04d2981..5cac371c 100644 --- a/include/aws/mqtt/v5/mqtt5_client.h +++ b/include/aws/mqtt/v5/mqtt5_client.h @@ -341,6 +341,7 @@ struct aws_mqtt5_publish_completion_options { aws_mqtt5_publish_completion_fn *completion_callback; void *completion_user_data; + /** Overrides the client's ack timeout with this value, for this operation only */ uint32_t ack_timeout_seconds_override; }; @@ -351,6 +352,7 @@ struct aws_mqtt5_subscribe_completion_options { aws_mqtt5_subscribe_completion_fn *completion_callback; void *completion_user_data; + /** Overrides the client's ack timeout with this value, for this operation only */ uint32_t ack_timeout_seconds_override; }; @@ -361,6 +363,7 @@ struct aws_mqtt5_unsubscribe_completion_options { aws_mqtt5_unsubscribe_completion_fn *completion_callback; void *completion_user_data; + /** Overrides the client's ack timeout with this value, for this operation only */ uint32_t ack_timeout_seconds_override; }; diff --git a/source/client.c b/source/client.c index 42ab634c..631a26c0 100644 --- a/source/client.c +++ b/source/client.c @@ -3223,7 +3223,7 @@ static void s_aws_mqtt_client_connection_311_release(void *impl) { enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_3_get_impl(void *impl) { (void)impl; - return AWS_MQTT311_IT_311_CONNECTION_IMPL; + return AWS_MQTT311_IT_311_CONNECTION; } static struct aws_mqtt_client_connection_vtable s_aws_mqtt_client_connection_311_vtable = { diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index b8758723..652ba5b2 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -58,7 +58,7 @@ struct aws_mqtt_protocol_adapter_5_impl { struct aws_mqtt5_listener *listener; }; -static void s_aws_mqtt_protocol_adapter_5_delete(void *impl) { +static void s_aws_mqtt_protocol_adapter_5_destroy(void *impl) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; // all the real cleanup is done in the listener termination callback @@ -87,7 +87,7 @@ static struct aws_mqtt_protocol_adapter_5_subscription_op_data *s_aws_mqtt_proto return subscribe_data; } -static void s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete( +static void s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy( struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data) { aws_weak_ref_release(subscribe_data->callback_ref); aws_byte_buf_clean_up(&subscribe_data->topic_filter); @@ -120,7 +120,7 @@ static void s_protocol_adapter_5_subscribe_completion( done: - s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(subscribe_data); + s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy(subscribe_data); } int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { @@ -154,7 +154,7 @@ int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adap error: - s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(subscribe_data); + s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy(subscribe_data); return AWS_OP_ERR; } @@ -184,7 +184,7 @@ static void s_protocol_adapter_5_unsubscribe_completion( done: - s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(unsubscribe_data); + s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy(unsubscribe_data); } int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { @@ -213,7 +213,7 @@ int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_ad error: - s_aws_mqtt_protocol_adapter_5_subscription_op_data_delete(unsubscribe_data); + s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy(unsubscribe_data); return AWS_OP_ERR; } @@ -243,7 +243,7 @@ static struct aws_mqtt_protocol_adapter_5_publish_op_data *s_aws_mqtt_protocol_a return publish_data; } -static void s_aws_mqtt_protocol_adapter_5_publish_op_data_delete( +static void s_aws_mqtt_protocol_adapter_5_publish_op_data_destroy( struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data) { aws_weak_ref_release(publish_data->callback_ref); @@ -274,7 +274,7 @@ static void s_protocol_adapter_5_publish_completion( done: - s_aws_mqtt_protocol_adapter_5_publish_op_data_delete(publish_data); + s_aws_mqtt_protocol_adapter_5_publish_op_data_destroy(publish_data); } int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { @@ -299,7 +299,7 @@ int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapte error: - s_aws_mqtt_protocol_adapter_5_publish_op_data_delete(publish_data); + s_aws_mqtt_protocol_adapter_5_publish_op_data_destroy(publish_data); return AWS_OP_ERR; } @@ -360,7 +360,7 @@ static void s_protocol_adapter_mqtt5_listener_termination_callback(void *user_da } static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt5_vtable = { - .aws_mqtt_protocol_adapter_delete_fn = s_aws_mqtt_protocol_adapter_5_delete, + .aws_mqtt_protocol_adapter_destroy_fn = s_aws_mqtt_protocol_adapter_5_destroy, .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_5_subscribe, .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_5_unsubscribe, .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_5_publish, @@ -370,6 +370,12 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter_options *options, struct aws_mqtt5_client *client) { + + if (options == NULL || client == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_impl)); @@ -399,8 +405,8 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( return &adapter->base; } -void aws_mqtt_protocol_adapter_delete(struct aws_mqtt_protocol_adapter *adapter) { - (*adapter->vtable->aws_mqtt_protocol_adapter_delete_fn)(adapter->impl); +void aws_mqtt_protocol_adapter_destroy(struct aws_mqtt_protocol_adapter *adapter) { + (*adapter->vtable->aws_mqtt_protocol_adapter_destroy_fn)(adapter->impl); } int aws_mqtt_protocol_adapter_subscribe( diff --git a/source/v5/mqtt5_to_mqtt3_adapter.c b/source/v5/mqtt5_to_mqtt3_adapter.c index fb39e444..2cb0451d 100644 --- a/source/v5/mqtt5_to_mqtt3_adapter.c +++ b/source/v5/mqtt5_to_mqtt3_adapter.c @@ -2857,7 +2857,7 @@ static uint16_t s_aws_mqtt_5_resubscribe_existing_topics( enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_5_get_impl(void *impl) { (void)impl; - return AWS_MQTT311_IT_5_ADAPTER_IMPL; + return AWS_MQTT311_IT_5_ADAPTER; } static struct aws_mqtt_client_connection_vtable s_aws_mqtt_client_connection_5_vtable = { diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c index c2fd65fa..6bca4aaa 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -190,7 +190,7 @@ static bool s_is_adapter_terminated(void *context) { static void s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters( struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { if (fixture->protocol_adapter != NULL) { - aws_mqtt_protocol_adapter_delete(fixture->protocol_adapter); + aws_mqtt_protocol_adapter_destroy(fixture->protocol_adapter); aws_mutex_lock(&fixture->lock); aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_is_adapter_terminated, fixture); From 0f1554bb1e12ada75bc4a26c8f9a9612c71a2cf7 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 24 Jan 2024 13:16:44 -0800 Subject: [PATCH 020/124] Check point --- source/mqtt311_listener.c | 4 +- tests/CMakeLists.txt | 3 + tests/v3/mqtt311_listener_test.c | 144 +++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 2 deletions(-) diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c index 27eb0f4a..93eb5d17 100644 --- a/source/mqtt311_listener.c +++ b/source/mqtt311_listener.c @@ -15,7 +15,7 @@ static struct aws_event_loop *s_mqtt_client_connection_get_event_loop( const struct aws_mqtt_client_connection *connection) { - AWS_FATAL_ASSERT(aws_mqtt_client_connection_get_impl_type(connection) == AWS_MQTT311_IT_311_CONNECTION_IMPL); + AWS_FATAL_ASSERT(aws_mqtt_client_connection_get_impl_type(connection) == AWS_MQTT311_IT_311_CONNECTION); struct aws_mqtt_client_connection_311_impl *connection_impl = connection->impl; @@ -102,7 +102,7 @@ struct aws_mqtt311_listener *aws_mqtt311_listener_new( return NULL; } - if (aws_mqtt_client_connection_get_impl_type(config->connection) != AWS_MQTT311_IT_311_CONNECTION_IMPL) { + if (aws_mqtt_client_connection_get_impl_type(config->connection) != AWS_MQTT311_IT_311_CONNECTION) { return NULL; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ab5057b7..a83459e7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -458,6 +458,9 @@ add_test_case(request_response_mqtt5_protocol_adapter_connection_event_sequence) add_test_case(request_response_mqtt5_protocol_adapter_incoming_publish) add_test_case(request_response_mqtt5_protocol_adapter_shutdown_while_pending) +add_test_case(mqtt311_listener_session_events) +#add_test_case(mqtt311_listener_publish_events) + generate_test_driver(${PROJECT_NAME}-tests) set(TEST_PAHO_CLIENT_BINARY_NAME ${PROJECT_NAME}-paho-client) diff --git a/tests/v3/mqtt311_listener_test.c b/tests/v3/mqtt311_listener_test.c index 2c1ab9d3..3491de5a 100644 --- a/tests/v3/mqtt311_listener_test.c +++ b/tests/v3/mqtt311_listener_test.c @@ -5,9 +5,153 @@ #include +#include + #include "mqtt311_testing_utils.h" #include "mqtt_mock_server_handler.h" #include +struct mqtt311_listener_resumption_record { + bool rejoined_session; +}; + +struct mqtt311_listener_publish_record { + struct aws_byte_buf topic; + struct aws_byte_buf payload; +}; + +struct mqtt311_listener_test_context { + struct aws_allocator *allocator; + + struct aws_mqtt311_listener *listener; + + struct mqtt_connection_state_test *mqtt311_test_context; + int mqtt311_test_context_setup_result; + + struct aws_array_list resumption_events; + struct aws_array_list publish_events; + bool terminated; + + struct aws_mutex lock; + struct aws_condition_variable signal; +}; + +static void s_311_listener_test_on_publish_received( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain, + void *userdata) { + +} + +static void s_311_listener_test_on_connection_resumed( + struct aws_mqtt_client_connection *connection, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata) { + +} + +static void s_311_listener_test_on_termination(void *complete_ctx) { + +} + +static int mqtt311_listener_test_context_init(struct mqtt311_listener_test_context *context, struct aws_allocator *allocator, struct mqtt_connection_state_test *mqtt311_test_context) { + AWS_ZERO_STRUCT(*context); + + context->allocator = allocator; + context->mqtt311_test_context = mqtt311_test_context; + + aws_array_list_init_dynamic(&context->resumption_events, allocator, 10, sizeof(struct mqtt311_listener_resumption_record)); + aws_array_list_init_dynamic(&context->publish_events, allocator, 10, sizeof(struct mqtt311_listener_publish_record)); + + aws_mutex_init(&context->lock); + aws_condition_variable_init(&context->signal); + + context->mqtt311_test_context_setup_result = aws_test311_setup_mqtt_server_fn(allocator, &mqtt311_test_context); + ASSERT_SUCCESS(context->mqtt311_test_context_setup_result); + + struct aws_mqtt311_listener_config listener_config = { + .connection = mqtt311_test_context->mqtt_connection, + .listener_callbacks = { + .publish_received_handler = s_311_listener_test_on_publish_received, + .connection_resumed_handler = s_311_listener_test_on_connection_resumed, + .user_data = context + }, + .termination_callback = s_311_listener_test_on_termination, + .termination_callback_user_data = context, + }; + + context->listener = aws_mqtt311_listener_new(allocator, &listener_config); + + return AWS_OP_SUCCESS; +} + +static void s_wait_for_listener_termination_callback(struct mqtt311_listener_test_context *context) { + +} + +static void mqtt311_listener_test_context_clean_up(struct mqtt311_listener_test_context *context) { + + aws_mqtt311_listener_release(context->listener); + s_wait_for_listener_termination_callback(context); + + aws_test311_clean_up_mqtt_server_fn(context->allocator, context->mqtt311_test_context_setup_result, context->mqtt311_test_context); + + aws_mutex_clean_up(&context->lock); + aws_condition_variable_clean_up(&context->signal); + + aws_array_list_clean_up(&context->resumption_events); + + for (size_t i = 0; i < aws_array_list_length(&context->publish_events); ++i) { + struct mqtt311_listener_publish_record publish_record; + AWS_ZERO_STRUCT(publish_record); + + aws_array_list_get_at(&context->publish_events, &publish_record, i); + + aws_byte_buf_clean_up(&publish_record.topic); + aws_byte_buf_clean_up(&publish_record.payload); + } + + aws_array_list_clean_up(&context->publish_events); +} + +static int s_mqtt311_listener_session_events_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt_connection_state_test mqtt311_context; + AWS_ZERO_STRUCT(mqtt311_context); + + struct mqtt311_listener_test_context test_context; + ASSERT_SUCCESS(mqtt311_listener_test_context_init(&test_context, allocator, &mqtt311_context)); + + struct aws_mqtt_connection_options connection_options = { + .user_data = &mqtt311_context, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(mqtt311_context.endpoint.address), + .socket_options = &mqtt311_context.socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(&mqtt311_context); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); + aws_test311_wait_for_disconnect_to_complete(&mqtt311_context); + + mqtt311_listener_test_context_clean_up(&test_context); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(mqtt311_listener_session_events, s_mqtt311_listener_session_events_fn) \ No newline at end of file From e5f93e1e3cfa81e67c8bc85fbec86d0145bb6bc4 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 24 Jan 2024 15:24:15 -0800 Subject: [PATCH 021/124] Convert to session event --- .../request-response/protocol_adapter.h | 20 +--- source/request-response/protocol_adapter.c | 14 +-- tests/CMakeLists.txt | 3 +- .../request_response_protocol_adapter_tests.c | 106 ++++++++---------- 4 files changed, 59 insertions(+), 84 deletions(-) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 6f596601..c70a2b99 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -90,19 +90,10 @@ struct aws_protocol_adapter_incoming_publish_event { }; /* - * Describes the type of connection event emitted by the protocol adapter + * An event emitted by the protocol adapter whenever the protocol client successfully reconnects to the broker. */ -enum aws_protocol_adapter_connection_event_type { - AWS_PACET_OFFLINE, - AWS_PACET_ONLINE, -}; - -/* - * An event emitted by the protocol adapter whenever the protocol client encounters a change in connectivity state. - */ -struct aws_protocol_adapter_connection_event { - enum aws_protocol_adapter_connection_event_type event_type; - bool rejoined_session; +struct aws_protocol_adapter_session_event { + bool joined_session; }; typedef void( @@ -111,8 +102,7 @@ typedef void(aws_protocol_adapter_incoming_publish_fn)( struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data); typedef void(aws_protocol_adapter_terminate_callback_fn)(void *user_data); -typedef void( - aws_protocol_adapter_connection_event_fn)(struct aws_protocol_adapter_connection_event *event, void *user_data); +typedef void(aws_protocol_adapter_session_event_fn)(struct aws_protocol_adapter_session_event *event, void *user_data); /* * Set of callbacks invoked by the protocol adapter. These must all be set. @@ -121,7 +111,7 @@ struct aws_mqtt_protocol_adapter_options { aws_protocol_adapter_subscription_event_fn *subscription_event_callback; aws_protocol_adapter_incoming_publish_fn *incoming_publish_callback; aws_protocol_adapter_terminate_callback_fn *terminate_callback; - aws_protocol_adapter_connection_event_fn *connection_event_callback; + aws_protocol_adapter_session_event_fn *session_event_callback; /* * User data to pass into all singleton protocol adapter callbacks. Likely either the request-response client diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 652ba5b2..a277f9ce 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -322,21 +322,15 @@ static bool s_protocol_adapter_mqtt5_listener_publish_received( static void s_protocol_adapter_mqtt5_lifecycle_event_callback(const struct aws_mqtt5_client_lifecycle_event *event) { struct aws_mqtt_protocol_adapter_5_impl *adapter = event->user_data; - if (event->event_type != AWS_MQTT5_CLET_CONNECTION_SUCCESS && event->event_type != AWS_MQTT5_CLET_DISCONNECTION) { + if (event->event_type != AWS_MQTT5_CLET_CONNECTION_SUCCESS) { return; } - bool is_connection_success = event->event_type == AWS_MQTT5_CLET_CONNECTION_SUCCESS; - - struct aws_protocol_adapter_connection_event connection_event = { - .event_type = is_connection_success ? AWS_PACET_ONLINE : AWS_PACET_OFFLINE, + struct aws_protocol_adapter_session_event session_event = { + .joined_session = event->settings->rejoined_session, }; - if (is_connection_success) { - connection_event.rejoined_session = event->settings->rejoined_session; - } - - (*adapter->config.connection_event_callback)(&connection_event, adapter->config.user_data); + (*adapter->config.session_event_callback)(&session_event, adapter->config.user_data); } static void s_protocol_adapter_mqtt5_listener_termination_callback(void *user_data) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ab5057b7..d93b7427 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -454,7 +454,8 @@ add_test_case(request_response_mqtt5_protocol_adapter_publish_success) add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_error_code) add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_reason_code) add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_timeout) -add_test_case(request_response_mqtt5_protocol_adapter_connection_event_sequence) +add_test_case(request_response_mqtt5_protocol_adapter_session_event_no_rejoin) +add_test_case(request_response_mqtt5_protocol_adapter_session_event_rejoin) add_test_case(request_response_mqtt5_protocol_adapter_incoming_publish) add_test_case(request_response_mqtt5_protocol_adapter_shutdown_while_pending) diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c index 6bca4aaa..586c9c00 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -31,11 +31,6 @@ static void s_request_response_protocol_adapter_incoming_publish_event_record_cl aws_byte_buf_clean_up(&record->payload); } -struct request_response_protocol_adapter_connection_event_record { - enum aws_protocol_adapter_connection_event_type event_type; - bool rejoined_session; -}; - struct request_response_protocol_adapter_subscription_event_record { enum aws_protocol_adapter_subscription_event_type event_type; struct aws_byte_buf topic_filter; @@ -61,7 +56,7 @@ struct aws_request_response_mqtt5_adapter_test_fixture { struct aws_mqtt_protocol_adapter *protocol_adapter; struct aws_array_list incoming_publish_events; - struct aws_array_list connection_events; + struct aws_array_list session_events; struct aws_array_list subscription_events; struct aws_array_list publish_results; @@ -111,16 +106,13 @@ static void s_rr_mqtt5_protocol_adapter_test_on_terminate_callback(void *user_da aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_connection_event( - struct aws_protocol_adapter_connection_event *event, +static void s_rr_mqtt5_protocol_adapter_test_on_session_event( + struct aws_protocol_adapter_session_event *event, void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; - struct request_response_protocol_adapter_connection_event_record record = { - .event_type = event->event_type, .rejoined_session = event->rejoined_session}; - aws_mutex_lock(&fixture->lock); - aws_array_list_push_back(&fixture->connection_events, &record); + aws_array_list_push_back(&fixture->session_events, event); aws_mutex_unlock(&fixture->lock); aws_condition_variable_notify_all(&fixture->signal); } @@ -151,7 +143,7 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( .subscription_event_callback = s_rr_mqtt5_protocol_adapter_test_on_subscription_event, .incoming_publish_callback = s_rr_mqtt5_protocol_adapter_test_on_incoming_publish, .terminate_callback = s_rr_mqtt5_protocol_adapter_test_on_terminate_callback, - .connection_event_callback = s_rr_mqtt5_protocol_adapter_test_on_connection_event, + .session_event_callback = s_rr_mqtt5_protocol_adapter_test_on_session_event, .user_data = fixture}; fixture->protocol_adapter = @@ -164,10 +156,7 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( 10, sizeof(struct request_response_protocol_adapter_incoming_publish_event_record)); aws_array_list_init_dynamic( - &fixture->connection_events, - allocator, - 10, - sizeof(struct request_response_protocol_adapter_connection_event_record)); + &fixture->session_events, allocator, 10, sizeof(struct aws_protocol_adapter_session_event)); aws_array_list_init_dynamic( &fixture->subscription_events, allocator, @@ -220,7 +209,7 @@ static void s_aws_request_response_mqtt5_adapter_test_fixture_clean_up( } aws_array_list_clean_up(&fixture->incoming_publish_events); - aws_array_list_clean_up(&fixture->connection_events); + aws_array_list_clean_up(&fixture->session_events); aws_array_list_clean_up(&fixture->publish_results); aws_mutex_clean_up(&fixture->lock); @@ -272,24 +261,23 @@ static void s_wait_for_subscription_events_contains( aws_mutex_unlock(&fixture->lock); } -struct test_connection_event_wait_context { - struct request_response_protocol_adapter_connection_event_record *expected_event; +struct test_session_event_wait_context { + struct aws_protocol_adapter_session_event *expected_event; size_t expected_count; struct aws_request_response_mqtt5_adapter_test_fixture *fixture; }; -static bool s_do_connection_events_contain(void *context) { - struct test_connection_event_wait_context *wait_context = context; +static bool s_do_session_events_contain(void *context) { + struct test_session_event_wait_context *wait_context = context; size_t found = 0; - size_t num_events = aws_array_list_length(&wait_context->fixture->connection_events); + size_t num_events = aws_array_list_length(&wait_context->fixture->session_events); for (size_t i = 0; i < num_events; ++i) { - struct request_response_protocol_adapter_connection_event_record record; - aws_array_list_get_at(&wait_context->fixture->connection_events, &record, i); + struct aws_protocol_adapter_session_event record; + aws_array_list_get_at(&wait_context->fixture->session_events, &record, i); - if (record.event_type == wait_context->expected_event->event_type && - record.rejoined_session == wait_context->expected_event->rejoined_session) { + if (record.joined_session == wait_context->expected_event->joined_session) { ++found; } } @@ -297,19 +285,19 @@ static bool s_do_connection_events_contain(void *context) { return found >= wait_context->expected_count; } -static void s_wait_for_connection_events_contains( +static void s_wait_for_session_events_contains( struct aws_request_response_mqtt5_adapter_test_fixture *fixture, - struct request_response_protocol_adapter_connection_event_record *expected_event, + struct aws_protocol_adapter_session_event *expected_event, size_t expected_count) { - struct test_connection_event_wait_context context = { + struct test_session_event_wait_context context = { .expected_event = expected_event, .expected_count = expected_count, .fixture = fixture, }; aws_mutex_lock(&fixture->lock); - aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_do_connection_events_contain, &context); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_do_session_events_contain, &context); aws_mutex_unlock(&fixture->lock); } @@ -818,11 +806,9 @@ AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_publish_failure_error_code, s_request_response_mqtt5_protocol_adapter_publish_failure_error_code_fn) -static int s_request_response_mqtt5_protocol_adapter_connection_event_sequence_fn( +static int s_do_request_response_mqtt5_protocol_adapter_session_event_test( struct aws_allocator *allocator, - void *ctx) { - (void)ctx; - + bool rejoin_session) { aws_mqtt_library_init(allocator); struct mqtt5_client_test_options test_options; @@ -830,7 +816,7 @@ static int s_request_response_mqtt5_protocol_adapter_connection_event_sequence_f test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_CONNECT] = aws_mqtt5_mock_server_handle_connect_honor_session_unconditional; - test_options.client_options.session_behavior = AWS_MQTT5_CSBT_REJOIN_POST_SUCCESS; + test_options.client_options.session_behavior = rejoin_session ? AWS_MQTT5_CSBT_REJOIN_ALWAYS : AWS_MQTT5_CSBT_CLEAN; struct aws_mqtt5_client_mqtt5_mock_test_fixture_options mqtt5_test_fixture_options = { .client_options = &test_options.client_options, @@ -843,31 +829,15 @@ static int s_request_response_mqtt5_protocol_adapter_connection_event_sequence_f struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; - struct request_response_protocol_adapter_connection_event_record online_record1 = { - .event_type = AWS_PACET_ONLINE, - .rejoined_session = false, - }; - - ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - s_wait_for_connection_events_contains(&fixture, &online_record1, 1); - - struct request_response_protocol_adapter_connection_event_record offline_record = { - .event_type = AWS_PACET_OFFLINE, - }; - - ASSERT_SUCCESS(aws_mqtt5_client_stop(client, NULL, NULL)); - s_wait_for_connection_events_contains(&fixture, &offline_record, 1); - - struct request_response_protocol_adapter_connection_event_record online_record2 = { - .event_type = AWS_PACET_ONLINE, - .rejoined_session = true, + struct aws_protocol_adapter_session_event expected_session_record = { + .joined_session = rejoin_session, }; ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - s_wait_for_connection_events_contains(&fixture, &online_record2, 1); + s_wait_for_session_events_contains(&fixture, &expected_session_record, 1); ASSERT_SUCCESS(aws_mqtt5_client_stop(client, NULL, NULL)); - s_wait_for_connection_events_contains(&fixture, &offline_record, 2); + aws_wait_for_stopped_lifecycle_event(&fixture.mqtt5_fixture); s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); @@ -876,9 +846,29 @@ static int s_request_response_mqtt5_protocol_adapter_connection_event_sequence_f return AWS_OP_SUCCESS; } +static int s_request_response_mqtt5_protocol_adapter_session_event_no_rejoin_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_do_request_response_mqtt5_protocol_adapter_session_event_test(allocator, false); +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_session_event_no_rejoin, + s_request_response_mqtt5_protocol_adapter_session_event_no_rejoin_fn) + +static int s_request_response_mqtt5_protocol_adapter_session_event_rejoin_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_do_request_response_mqtt5_protocol_adapter_session_event_test(allocator, true); +} + AWS_TEST_CASE( - request_response_mqtt5_protocol_adapter_connection_event_sequence, - s_request_response_mqtt5_protocol_adapter_connection_event_sequence_fn) + request_response_mqtt5_protocol_adapter_session_event_rejoin, + s_request_response_mqtt5_protocol_adapter_session_event_rejoin_fn) static int s_request_response_mqtt5_protocol_adapter_incoming_publish_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; From a54824a1813ab678af6c5eb6fef77991063ef436 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 25 Jan 2024 09:07:26 -0800 Subject: [PATCH 022/124] Stop talking to myself --- source/request-response/protocol_adapter.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index a277f9ce..da604023 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -13,24 +13,22 @@ #include /* - * New API contract + * Basic API contract * * Invariant 1: Subscribe is only called from the RR subscription manager when going from 0 to 1 pending operations * Invariant 2: Unsubscribe is only called from the RR subscription manager when there are 0 pending operations, not * necessarily on the exact transition to zero though. * + * Additional Notes + * * Entries are not tracked with the exception of eventstream impl which needs the stream handles to close. - * A subscribe failure should not trigger an unsubscribe, only notify the status callback. - * Subscription event callback should be {subscribe_success, subscribe_failure, unsubscribe_success, - * unsubscribe_failure}. The sub manager is responsible for calling Unsubscribe on all its entries when shutting down - * (before releasing hold of the adapter). * - * How do we know not to retry unsubscribe failures because a subscribe came in? Well, we don't retry failures; let - * the manager make that decision. No retry when the weak ref is zeroed either. The potential for things to go wrong - * is worse than the potential of a subscription "leaking." + * A subscribe failure does not trigger an unsubscribe, a status event. + * + * The sub manager is responsible for calling Unsubscribe on all its entries when shutting down + * (before releasing hold of the adapter). * - * On subscribe failures with zeroed weak ref, trust that an Unsubscribe was sent that will resolve later and let it - * decide what to do. + * Retries, when appropriate, are the responsibility of the caller. */ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( From 46b0d66376e40e89ab42711154a8dee5a8703c79 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 25 Jan 2024 14:39:22 -0800 Subject: [PATCH 023/124] Testing --- include/aws/mqtt/private/mqtt311_listener.h | 4 +- source/client_channel_handler.c | 6 +- source/mqtt311_listener.c | 6 +- tests/CMakeLists.txt | 5 +- tests/v3/mqtt311_listener_test.c | 260 ++++++++++++++++++-- tests/v3/mqtt_mock_server_handler.c | 40 ++- tests/v3/mqtt_mock_server_handler.h | 11 + 7 files changed, 298 insertions(+), 34 deletions(-) diff --git a/include/aws/mqtt/private/mqtt311_listener.h b/include/aws/mqtt/private/mqtt311_listener.h index 1d5febee..e1e5162e 100644 --- a/include/aws/mqtt/private/mqtt311_listener.h +++ b/include/aws/mqtt/private/mqtt311_listener.h @@ -33,7 +33,7 @@ struct aws_mqtt311_callback_set { aws_mqtt_client_publish_received_fn *publish_received_handler; /* Called from s_packet_handler_connack which is event-loop invoked */ - aws_mqtt_client_on_connection_resumed_fn *connection_resumed_handler; + aws_mqtt_client_on_connection_success_fn *connection_success_handler; void *user_data; }; @@ -162,7 +162,7 @@ void aws_mqtt311_callback_set_manager_on_publish_received( bool retain); AWS_MQTT_API -void aws_mqtt311_callback_set_manager_on_connection_resumed( +void aws_mqtt311_callback_set_manager_on_connection_success( struct aws_mqtt311_callback_set_manager *manager, enum aws_mqtt_connect_return_code return_code, bool rejoined_session); diff --git a/source/client_channel_handler.c b/source/client_channel_handler.c index 198d3fe3..b95a43fc 100644 --- a/source/client_channel_handler.c +++ b/source/client_channel_handler.c @@ -240,9 +240,6 @@ static int s_packet_handler_connack(struct aws_byte_cursor message_cursor, void (void *)connection); MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_resumed, connack.connect_return_code, connack.session_present); - - aws_mqtt311_callback_set_manager_on_connection_resumed( - &connection->callback_manager, connack.connect_return_code, connack.session_present); } else { aws_create_reconnect_task(connection); @@ -266,6 +263,9 @@ static int s_packet_handler_connack(struct aws_byte_cursor message_cursor, void MQTT_CLIENT_CALL_CALLBACK_ARGS( connection, on_connection_success, connack.connect_return_code, connack.session_present); + aws_mqtt311_callback_set_manager_on_connection_success( + &connection->callback_manager, 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); diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c index 93eb5d17..4e827152 100644 --- a/source/mqtt311_listener.c +++ b/source/mqtt311_listener.c @@ -266,7 +266,7 @@ void aws_mqtt311_callback_set_manager_on_publish_received( } } -void aws_mqtt311_callback_set_manager_on_connection_resumed( +void aws_mqtt311_callback_set_manager_on_connection_success( struct aws_mqtt311_callback_set_manager *manager, enum aws_mqtt_connect_return_code return_code, bool rejoined_session) { @@ -281,8 +281,8 @@ void aws_mqtt311_callback_set_manager_on_connection_resumed( node = aws_linked_list_next(node); struct aws_mqtt311_callback_set *callback_set = &entry->callbacks; - if (callback_set->connection_resumed_handler != NULL) { - (*callback_set->connection_resumed_handler)( + if (callback_set->connection_success_handler != NULL) { + (*callback_set->connection_success_handler)( manager->connection, return_code, rejoined_session, callback_set->user_data); } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e656d92..e52138a6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -459,8 +459,9 @@ add_test_case(request_response_mqtt5_protocol_adapter_session_event_rejoin) add_test_case(request_response_mqtt5_protocol_adapter_incoming_publish) add_test_case(request_response_mqtt5_protocol_adapter_shutdown_while_pending) -add_test_case(mqtt311_listener_session_events) -#add_test_case(mqtt311_listener_publish_events) +add_test_case(mqtt311_listener_connection_success_event_no_session) +add_test_case(mqtt311_listener_connection_success_event_with_session) +add_test_case(mqtt311_listener_publish_event) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/v3/mqtt311_listener_test.c b/tests/v3/mqtt311_listener_test.c index 3491de5a..57b54c25 100644 --- a/tests/v3/mqtt311_listener_test.c +++ b/tests/v3/mqtt311_listener_test.c @@ -1,7 +1,7 @@ /** -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0. -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ #include @@ -12,8 +12,8 @@ #include -struct mqtt311_listener_resumption_record { - bool rejoined_session; +struct mqtt311_listener_connection_success_record { + bool joined_session; }; struct mqtt311_listener_publish_record { @@ -29,7 +29,7 @@ struct mqtt311_listener_test_context { struct mqtt_connection_state_test *mqtt311_test_context; int mqtt311_test_context_setup_result; - struct aws_array_list resumption_events; + struct aws_array_list connection_success_events; struct aws_array_list publish_events; bool terminated; @@ -45,43 +45,78 @@ static void s_311_listener_test_on_publish_received( enum aws_mqtt_qos qos, bool retain, void *userdata) { + (void)dup; + (void)qos; + (void)retain; + struct mqtt311_listener_test_context *context = userdata; + + struct mqtt311_listener_publish_record publish_record; + AWS_ZERO_STRUCT(publish_record); + + aws_byte_buf_init_copy_from_cursor(&publish_record.topic, context->allocator, *topic); + aws_byte_buf_init_copy_from_cursor(&publish_record.payload, context->allocator, *payload); + + aws_mutex_lock(&context->lock); + aws_array_list_push_back(&context->publish_events, &publish_record); + aws_mutex_unlock(&context->lock); + aws_condition_variable_notify_all(&context->signal); } -static void s_311_listener_test_on_connection_resumed( +static void s_311_listener_test_on_connection_success( struct aws_mqtt_client_connection *connection, enum aws_mqtt_connect_return_code return_code, bool session_present, void *userdata) { + (void)return_code; + + struct mqtt311_listener_test_context *context = userdata; + + struct mqtt311_listener_connection_success_record connection_success_record = { + .joined_session = session_present, + }; + aws_mutex_lock(&context->lock); + aws_array_list_push_back(&context->connection_success_events, &connection_success_record); + aws_mutex_unlock(&context->lock); + aws_condition_variable_notify_all(&context->signal); } static void s_311_listener_test_on_termination(void *complete_ctx) { + struct mqtt311_listener_test_context *context = complete_ctx; + aws_mutex_lock(&context->lock); + context->terminated = true; + aws_mutex_unlock(&context->lock); + aws_condition_variable_notify_all(&context->signal); } -static int mqtt311_listener_test_context_init(struct mqtt311_listener_test_context *context, struct aws_allocator *allocator, struct mqtt_connection_state_test *mqtt311_test_context) { +static int mqtt311_listener_test_context_init( + struct mqtt311_listener_test_context *context, + struct aws_allocator *allocator, + struct mqtt_connection_state_test *mqtt311_test_context) { AWS_ZERO_STRUCT(*context); context->allocator = allocator; context->mqtt311_test_context = mqtt311_test_context; - aws_array_list_init_dynamic(&context->resumption_events, allocator, 10, sizeof(struct mqtt311_listener_resumption_record)); - aws_array_list_init_dynamic(&context->publish_events, allocator, 10, sizeof(struct mqtt311_listener_publish_record)); + aws_array_list_init_dynamic( + &context->connection_success_events, allocator, 10, sizeof(struct mqtt311_listener_connection_success_record)); + aws_array_list_init_dynamic( + &context->publish_events, allocator, 10, sizeof(struct mqtt311_listener_publish_record)); aws_mutex_init(&context->lock); aws_condition_variable_init(&context->signal); - context->mqtt311_test_context_setup_result = aws_test311_setup_mqtt_server_fn(allocator, &mqtt311_test_context); + context->mqtt311_test_context_setup_result = aws_test311_setup_mqtt_server_fn(allocator, mqtt311_test_context); ASSERT_SUCCESS(context->mqtt311_test_context_setup_result); struct aws_mqtt311_listener_config listener_config = { .connection = mqtt311_test_context->mqtt_connection, - .listener_callbacks = { - .publish_received_handler = s_311_listener_test_on_publish_received, - .connection_resumed_handler = s_311_listener_test_on_connection_resumed, - .user_data = context - }, + .listener_callbacks = + {.publish_received_handler = s_311_listener_test_on_publish_received, + .connection_success_handler = s_311_listener_test_on_connection_success, + .user_data = context}, .termination_callback = s_311_listener_test_on_termination, .termination_callback_user_data = context, }; @@ -91,21 +126,29 @@ static int mqtt311_listener_test_context_init(struct mqtt311_listener_test_conte return AWS_OP_SUCCESS; } -static void s_wait_for_listener_termination_callback(struct mqtt311_listener_test_context *context) { +static bool s_is_listener_terminated(void *userdata) { + struct mqtt311_listener_test_context *context = userdata; + return context->terminated; } -static void mqtt311_listener_test_context_clean_up(struct mqtt311_listener_test_context *context) { +static void s_wait_for_listener_termination_callback(struct mqtt311_listener_test_context *context) { + aws_mutex_lock(&context->lock); + aws_condition_variable_wait_pred(&context->signal, &context->lock, s_is_listener_terminated, context); + aws_mutex_unlock(&context->lock); +} +static void mqtt311_listener_test_context_clean_up(struct mqtt311_listener_test_context *context) { aws_mqtt311_listener_release(context->listener); s_wait_for_listener_termination_callback(context); - aws_test311_clean_up_mqtt_server_fn(context->allocator, context->mqtt311_test_context_setup_result, context->mqtt311_test_context); + aws_test311_clean_up_mqtt_server_fn( + context->allocator, context->mqtt311_test_context_setup_result, context->mqtt311_test_context); aws_mutex_clean_up(&context->lock); aws_condition_variable_clean_up(&context->signal); - aws_array_list_clean_up(&context->resumption_events); + aws_array_list_clean_up(&context->connection_success_events); for (size_t i = 0; i < aws_array_list_length(&context->publish_events); ++i) { struct mqtt311_listener_publish_record publish_record; @@ -120,7 +163,160 @@ static void mqtt311_listener_test_context_clean_up(struct mqtt311_listener_test_ aws_array_list_clean_up(&context->publish_events); } -static int s_mqtt311_listener_session_events_fn(struct aws_allocator *allocator, void *ctx) { +struct connection_success_event_test_context { + struct mqtt311_listener_test_context *context; + bool joined_session; + size_t expected_count; +}; + +static bool s_contains_connection_success_events(void *userdata) { + struct connection_success_event_test_context *wait_context = userdata; + struct mqtt311_listener_test_context *context = wait_context->context; + + size_t found = 0; + for (size_t i = 0; i < aws_array_list_length(&context->connection_success_events); ++i) { + struct mqtt311_listener_connection_success_record record; + aws_array_list_get_at(&context->connection_success_events, &record, i); + + if (record.joined_session == wait_context->joined_session) { + ++found; + } + } + + return found >= wait_context->expected_count; +} + +static void s_wait_for_connection_success_events( + struct mqtt311_listener_test_context *context, + bool joined_session, + size_t expected_count) { + struct connection_success_event_test_context wait_context = { + .context = context, + .joined_session = joined_session, + .expected_count = expected_count, + }; + + aws_mutex_lock(&context->lock); + aws_condition_variable_wait_pred( + &context->signal, &context->lock, s_contains_connection_success_events, &wait_context); + aws_mutex_unlock(&context->lock); +} + +static int s_do_mqtt311_listener_connection_success_event_test(struct aws_allocator *allocator, bool session_present) { + aws_mqtt_library_init(allocator); + + struct mqtt_connection_state_test mqtt311_context; + AWS_ZERO_STRUCT(mqtt311_context); + + struct mqtt311_listener_test_context test_context; + ASSERT_SUCCESS(mqtt311_listener_test_context_init(&test_context, allocator, &mqtt311_context)); + + mqtt_mock_server_set_session_present(mqtt311_context.mock_server, false); + + struct aws_mqtt_connection_options connection_options = { + .user_data = &mqtt311_context, + .clean_session = true, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(mqtt311_context.endpoint.address), + .socket_options = &mqtt311_context.socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(&mqtt311_context); + + s_wait_for_connection_success_events(&test_context, false, 1); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); + aws_test311_wait_for_disconnect_to_complete(&mqtt311_context); + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(&mqtt311_context); + + s_wait_for_connection_success_events(&test_context, false, 2); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); + aws_test311_wait_for_disconnect_to_complete(&mqtt311_context); + + mqtt311_listener_test_context_clean_up(&test_context); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_mqtt311_listener_connection_success_event_no_session_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_mqtt311_listener_connection_success_event_test(allocator, false); +} + +AWS_TEST_CASE( + mqtt311_listener_connection_success_event_no_session, + s_mqtt311_listener_connection_success_event_no_session_fn) + +static int s_mqtt311_listener_connection_success_event_with_session_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_mqtt311_listener_connection_success_event_test(allocator, true); +} + +AWS_TEST_CASE( + mqtt311_listener_connection_success_event_with_session, + s_mqtt311_listener_connection_success_event_with_session_fn) + +struct publish_event_test_context { + struct mqtt311_listener_test_context *context; + struct aws_byte_cursor expected_topic; + struct aws_byte_cursor expected_payload; + size_t expected_count; +}; + +static bool s_contains_publish_events(void *userdata) { + struct publish_event_test_context *wait_context = userdata; + struct mqtt311_listener_test_context *context = wait_context->context; + + size_t found = 0; + for (size_t i = 0; i < aws_array_list_length(&context->publish_events); ++i) { + struct mqtt311_listener_publish_record record; + aws_array_list_get_at(&context->publish_events, &record, i); + + struct aws_byte_cursor actual_topic = aws_byte_cursor_from_buf(&record.topic); + if (!aws_byte_cursor_eq(&wait_context->expected_topic, &actual_topic)) { + continue; + } + + struct aws_byte_cursor actual_payload = aws_byte_cursor_from_buf(&record.payload); + if (!aws_byte_cursor_eq(&wait_context->expected_payload, &actual_payload)) { + continue; + } + + ++found; + } + + return found >= wait_context->expected_count; +} + +static void s_wait_for_publish_events( + struct mqtt311_listener_test_context *context, + struct aws_byte_cursor topic, + struct aws_byte_cursor payload, + size_t expected_count) { + struct publish_event_test_context wait_context = { + .context = context, + .expected_topic = topic, + .expected_payload = payload, + .expected_count = expected_count, + }; + + aws_mutex_lock(&context->lock); + aws_condition_variable_wait_pred(&context->signal, &context->lock, s_contains_publish_events, &wait_context); + aws_mutex_unlock(&context->lock); +} + +static int s_mqtt311_listener_publish_event_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_mqtt_library_init(allocator); @@ -131,9 +327,11 @@ static int s_mqtt311_listener_session_events_fn(struct aws_allocator *allocator, struct mqtt311_listener_test_context test_context; ASSERT_SUCCESS(mqtt311_listener_test_context_init(&test_context, allocator, &mqtt311_context)); + mqtt_mock_server_set_publish_reflection(mqtt311_context.mock_server, true); + struct aws_mqtt_connection_options connection_options = { .user_data = &mqtt311_context, - .clean_session = false, + .clean_session = true, .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(mqtt311_context.endpoint.address), .socket_options = &mqtt311_context.socket_options, @@ -143,6 +341,22 @@ static int s_mqtt311_listener_session_events_fn(struct aws_allocator *allocator, ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); aws_test311_wait_for_connection_to_complete(&mqtt311_context); + struct aws_byte_cursor topic1 = aws_byte_cursor_from_c_str("hello/world/1"); + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("payload1"); + aws_mqtt_client_connection_publish( + mqtt311_context.mqtt_connection, &topic1, AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload1, NULL, NULL); + + s_wait_for_publish_events(&test_context, topic1, payload1, 1); + + struct aws_byte_cursor topic2 = aws_byte_cursor_from_c_str("nothing/important"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("somethingneeddoing?"); + aws_mqtt_client_connection_publish( + mqtt311_context.mqtt_connection, &topic2, AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload2, NULL, NULL); + aws_mqtt_client_connection_publish( + mqtt311_context.mqtt_connection, &topic2, AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload2, NULL, NULL); + + s_wait_for_publish_events(&test_context, topic2, payload2, 2); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); aws_test311_wait_for_disconnect_to_complete(&mqtt311_context); @@ -154,4 +368,4 @@ static int s_mqtt311_listener_session_events_fn(struct aws_allocator *allocator, return AWS_OP_SUCCESS; } -AWS_TEST_CASE(mqtt311_listener_session_events, s_mqtt311_listener_session_events_fn) \ No newline at end of file +AWS_TEST_CASE(mqtt311_listener_publish_event, s_mqtt311_listener_publish_event_fn) \ No newline at end of file diff --git a/tests/v3/mqtt_mock_server_handler.c b/tests/v3/mqtt_mock_server_handler.c index 73576a7d..0e770690 100644 --- a/tests/v3/mqtt_mock_server_handler.c +++ b/tests/v3/mqtt_mock_server_handler.c @@ -30,6 +30,8 @@ struct mqtt_mock_server_handler { size_t pubacks_received; size_t ping_received; size_t connacks_avail; + bool session_present; + bool reflect_publishes; bool auto_ack; /* last ID used when sending PUBLISH (QoS1+) to client */ @@ -77,12 +79,14 @@ static int s_mqtt_mock_server_handler_process_packet( switch (packet_type) { case AWS_MQTT_PACKET_CONNECT: { size_t connacks_available = 0; + bool session_present = false; aws_mutex_lock(&server->synced.lock); AWS_LOGF_DEBUG( MOCK_LOG_SUBJECT, "server, CONNECT received, %llu available connacks.", (long long unsigned)server->synced.connacks_avail); connacks_available = server->synced.connacks_avail > 0 ? server->synced.connacks_avail-- : 0; + session_present = server->synced.session_present; aws_mutex_unlock(&server->synced.lock); if (connacks_available) { @@ -90,7 +94,7 @@ static int s_mqtt_mock_server_handler_process_packet( aws_channel_acquire_message_from_pool(server->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, 256); struct aws_mqtt_packet_connack conn_ack; - err |= aws_mqtt_packet_connack_init(&conn_ack, false, AWS_MQTT_CONNECT_ACCEPTED); + err |= aws_mqtt_packet_connack_init(&conn_ack, session_present, AWS_MQTT_CONNECT_ACCEPTED); err |= aws_mqtt_packet_connack_encode(&connack_msg->message_data, &conn_ack); if (aws_channel_slot_send_message(server->slot, connack_msg, AWS_CHANNEL_DIR_WRITE)) { err |= 1; @@ -185,6 +189,7 @@ static int s_mqtt_mock_server_handler_process_packet( aws_mutex_lock(&server->synced.lock); bool auto_ack = server->synced.auto_ack; + bool reflect_publishes = server->synced.reflect_publishes; aws_mutex_unlock(&server->synced.lock); uint8_t qos = (publish_packet.fixed_header.flags >> 1) & 0x3; @@ -197,6 +202,23 @@ static int s_mqtt_mock_server_handler_process_packet( err |= aws_mqtt_packet_ack_encode(&puback_msg->message_data, &puback); err |= aws_channel_slot_send_message(server->slot, puback_msg, AWS_CHANNEL_DIR_WRITE); } + + if (reflect_publishes) { + struct aws_io_message *publish_msg = + aws_channel_acquire_message_from_pool(server->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, 1024); + struct aws_mqtt_packet_publish publish; + // reusing the packet identifier here is weird but reasonably safe, they're separate id spaces + err |= aws_mqtt_packet_publish_init( + &publish, + false, + qos, + false, + publish_packet.topic_name, + publish_packet.packet_identifier, + publish_packet.payload); + err |= aws_mqtt_packet_publish_encode(&publish_msg->message_data, &publish); + err |= aws_channel_slot_send_message(server->slot, publish_msg, AWS_CHANNEL_DIR_WRITE); + } break; } @@ -466,6 +488,22 @@ void destroy_mqtt_mock_server(struct aws_channel_handler *handler) { aws_mem_release(handler->alloc, server); } +void mqtt_mock_server_set_session_present(struct aws_channel_handler *handler, bool session_present) { + struct mqtt_mock_server_handler *server = handler->impl; + + aws_mutex_lock(&server->synced.lock); + server->synced.session_present = session_present; + aws_mutex_unlock(&server->synced.lock); +} + +void mqtt_mock_server_set_publish_reflection(struct aws_channel_handler *handler, bool reflect_publishes) { + struct mqtt_mock_server_handler *server = handler->impl; + + aws_mutex_lock(&server->synced.lock); + server->synced.reflect_publishes = reflect_publishes; + aws_mutex_unlock(&server->synced.lock); +} + void mqtt_mock_server_set_max_ping_resp(struct aws_channel_handler *handler, size_t max_ping) { struct mqtt_mock_server_handler *server = handler->impl; diff --git a/tests/v3/mqtt_mock_server_handler.h b/tests/v3/mqtt_mock_server_handler.h index 170201e6..8187ae11 100644 --- a/tests/v3/mqtt_mock_server_handler.h +++ b/tests/v3/mqtt_mock_server_handler.h @@ -68,10 +68,21 @@ int mqtt_mock_server_send_publish_by_id( enum aws_mqtt_qos qos, bool retain); +/** + * Sets whether or not connacks return session present + */ +void mqtt_mock_server_set_session_present(struct aws_channel_handler *handler, bool session_present); + +/** + * Sets whether or not connacks return session present + */ +void mqtt_mock_server_set_publish_reflection(struct aws_channel_handler *handler, bool reflect_publishes); + /** * Set max number of PINGRESP that mock server will send back to client */ void mqtt_mock_server_set_max_ping_resp(struct aws_channel_handler *handler, size_t max_ping); + /** * Set max number of CONACK that mock server will send back to client */ From abbcdc1688f4f3af72003218433959b63178887a Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 07:26:48 -0800 Subject: [PATCH 024/124] Sync point --- include/aws/mqtt/private/request-response/protocol_adapter.h | 1 + source/request-response/protocol_adapter.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index c70a2b99..82f10c0b 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -77,6 +77,7 @@ enum aws_protocol_adapter_subscription_event_type { struct aws_protocol_adapter_subscription_event { struct aws_byte_cursor topic_filter; enum aws_protocol_adapter_subscription_event_type event_type; + int error_code; }; /* diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index da604023..5b3b41f9 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -112,6 +112,7 @@ static void s_protocol_adapter_5_subscribe_completion( struct aws_protocol_adapter_subscription_event subscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), .event_type = success ? AWS_PASET_SUBSCRIBE_SUCCESS : AWS_PASET_SUBSCRIBE_FAILURE, + .error_code = error_code, }; (*adapter->config.subscription_event_callback)(&subscribe_event, adapter->config.user_data); @@ -176,6 +177,7 @@ static void s_protocol_adapter_5_unsubscribe_completion( struct aws_protocol_adapter_subscription_event unsubscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), .event_type = success ? AWS_PASET_UNSUBSCRIBE_SUCCESS : AWS_PASET_UNSUBSCRIBE_FAILURE, + .error_code = error_code, }; (*adapter->config.subscription_event_callback)(&unsubscribe_event, adapter->config.user_data); From 426878f11abc8df56ee1b0e0a35fbea8a16e95f1 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 10:33:02 -0800 Subject: [PATCH 025/124] success/failure -> error code --- include/aws/mqtt/mqtt.h | 1 + .../request-response/protocol_adapter.h | 8 +- source/mqtt.c | 3 + source/request-response/protocol_adapter.c | 27 +++-- .../request_response_protocol_adapter_tests.c | 101 ++++++++++++------ 5 files changed, 92 insertions(+), 48 deletions(-) diff --git a/include/aws/mqtt/mqtt.h b/include/aws/mqtt/mqtt.h index 974c4576..8e7cd0ef 100644 --- a/include/aws/mqtt/mqtt.h +++ b/include/aws/mqtt/mqtt.h @@ -81,6 +81,7 @@ enum aws_mqtt_error { AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT, AWS_ERROR_MQTT_CONNECTION_RESUBSCRIBE_NO_TOPICS, AWS_ERROR_MQTT_CONNECTION_SUBSCRIBE_FAILURE, + AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, AWS_ERROR_END_MQTT_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_MQTT_PACKAGE_ID), }; diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 82f10c0b..f09d6135 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -52,7 +52,7 @@ struct aws_protocol_adapter_publish_options { * Invoked on success/failure of the publish itself. Our implementations use QoS1 which means that success * will be on puback receipt. */ - void (*completion_callback_fn)(bool, void *); + void (*completion_callback_fn)(int, void *); /* * User data to pass in when invoking the completion callback @@ -64,10 +64,8 @@ struct aws_protocol_adapter_publish_options { * Describes the type of subscription event (relative to a topic filter) */ enum aws_protocol_adapter_subscription_event_type { - AWS_PASET_SUBSCRIBE_SUCCESS, - AWS_PASET_SUBSCRIBE_FAILURE, - AWS_PASET_UNSUBSCRIBE_SUCCESS, - AWS_PASET_UNSUBSCRIBE_FAILURE, + AWS_PASET_SUBSCRIBE, + AWS_PASET_UNSUBSCRIBE, }; /* diff --git a/source/mqtt.c b/source/mqtt.c index c87ec0cc..fd16340a 100644 --- a/source/mqtt.c +++ b/source/mqtt.c @@ -233,6 +233,9 @@ bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter) AWS_DEFINE_ERROR_INFO_MQTT( AWS_ERROR_MQTT_CONNECTION_SUBSCRIBE_FAILURE, "MQTT subscribe operation failed"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, + "MQTT operation returned a failing reason code"), }; /* clang-format on */ #undef AWS_DEFINE_ERROR_INFO_MQTT diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 5b3b41f9..68cc8667 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -106,12 +106,15 @@ static void s_protocol_adapter_5_subscribe_completion( goto done; } - bool success = error_code == AWS_ERROR_SUCCESS && suback != NULL && suback->reason_code_count == 1 && - suback->reason_codes[0] <= AWS_MQTT5_SARC_GRANTED_QOS_2; + if (error_code == AWS_ERROR_SUCCESS) { + if (suback == NULL || suback->reason_code_count != 1 || suback->reason_codes[0] >= 128) { + error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; + } + } struct aws_protocol_adapter_subscription_event subscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), - .event_type = success ? AWS_PASET_SUBSCRIBE_SUCCESS : AWS_PASET_SUBSCRIBE_FAILURE, + .event_type = AWS_PASET_SUBSCRIBE, .error_code = error_code, }; @@ -171,12 +174,15 @@ static void s_protocol_adapter_5_unsubscribe_completion( goto done; } - bool success = error_code == AWS_ERROR_SUCCESS && unsuback != NULL && unsuback->reason_code_count == 1 && - unsuback->reason_codes[0] < 128; + if (error_code == AWS_ERROR_SUCCESS) { + if (unsuback == NULL || unsuback->reason_code_count != 1 || unsuback->reason_codes[0] >= 128) { + error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; + } + } struct aws_protocol_adapter_subscription_event unsubscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), - .event_type = success ? AWS_PASET_UNSUBSCRIBE_SUCCESS : AWS_PASET_UNSUBSCRIBE_FAILURE, + .event_type = AWS_PASET_UNSUBSCRIBE, .error_code = error_code, }; @@ -224,7 +230,7 @@ struct aws_mqtt_protocol_adapter_5_publish_op_data { struct aws_allocator *allocator; struct aws_weak_ref *callback_ref; - void (*completion_callback_fn)(bool, void *); + void (*completion_callback_fn)(int, void *); void *user_data; }; @@ -262,15 +268,14 @@ static void s_protocol_adapter_5_publish_completion( goto done; } - bool success = false; if (error_code == AWS_ERROR_SUCCESS && packet_type == AWS_MQTT5_PT_PUBACK) { const struct aws_mqtt5_packet_puback_view *puback = packet; - if (puback->reason_code < 128) { - success = true; + if (puback->reason_code >= 128) { + error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; } } - (*publish_data->completion_callback_fn)(success, publish_data->user_data); + (*publish_data->completion_callback_fn)(error_code, publish_data->user_data); done: diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c index 586c9c00..2034f460 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -34,13 +34,20 @@ static void s_request_response_protocol_adapter_incoming_publish_event_record_cl struct request_response_protocol_adapter_subscription_event_record { enum aws_protocol_adapter_subscription_event_type event_type; struct aws_byte_buf topic_filter; + int error_code; }; static void s_request_response_protocol_adapter_subscription_event_record_init( struct request_response_protocol_adapter_subscription_event_record *record, struct aws_allocator *allocator, - struct aws_byte_cursor topic_filter) { + enum aws_protocol_adapter_subscription_event_type event_type, + struct aws_byte_cursor topic_filter, + int error_code) { + AWS_ZERO_STRUCT(*record); + + record->event_type = event_type; + record->error_code = error_code; aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); } @@ -71,9 +78,9 @@ static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event( void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; - struct request_response_protocol_adapter_subscription_event_record record = {.event_type = event->event_type}; + struct request_response_protocol_adapter_subscription_event_record record; s_request_response_protocol_adapter_subscription_event_record_init( - &record, fixture->allocator, event->topic_filter); + &record, fixture->allocator, event->event_type, event->topic_filter, event->error_code); aws_mutex_lock(&fixture->lock); aws_array_list_push_back(&fixture->subscription_events, &record); @@ -117,11 +124,11 @@ static void s_rr_mqtt5_protocol_adapter_test_on_session_event( aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_publish_result(bool success, void *user_data) { +static void s_rr_mqtt5_protocol_adapter_test_on_publish_result(int error_code, void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; aws_mutex_lock(&fixture->lock); - aws_array_list_push_back(&fixture->publish_results, &success); + aws_array_list_push_back(&fixture->publish_results, &error_code); aws_mutex_unlock(&fixture->lock); aws_condition_variable_notify_all(&fixture->signal); } @@ -162,7 +169,7 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( allocator, 10, sizeof(struct request_response_protocol_adapter_subscription_event_record)); - aws_array_list_init_dynamic(&fixture->publish_results, allocator, 10, sizeof(bool)); + aws_array_list_init_dynamic(&fixture->publish_results, allocator, 10, sizeof(int)); aws_mutex_init(&fixture->lock); aws_condition_variable_init(&fixture->signal); @@ -232,14 +239,22 @@ static bool s_do_subscription_events_contain(void *context) { struct request_response_protocol_adapter_subscription_event_record record; aws_array_list_get_at(&wait_context->fixture->subscription_events, &record, i); - if (record.event_type == wait_context->expected_event->event_type) { - struct aws_byte_cursor record_topic_filter = aws_byte_cursor_from_buf(&record.topic_filter); - struct aws_byte_cursor expected_topic_filter = - aws_byte_cursor_from_buf(&wait_context->expected_event->topic_filter); - if (aws_byte_cursor_eq(&record_topic_filter, &expected_topic_filter)) { - ++found; - } + if (record.event_type != wait_context->expected_event->event_type) { + continue; } + + if (record.error_code != wait_context->expected_event->error_code) { + continue; + } + + struct aws_byte_cursor record_topic_filter = aws_byte_cursor_from_buf(&record.topic_filter); + struct aws_byte_cursor expected_topic_filter = + aws_byte_cursor_from_buf(&wait_context->expected_event->topic_filter); + if (!aws_byte_cursor_eq(&record_topic_filter, &expected_topic_filter)) { + continue; + } + + ++found; } return found >= wait_context->expected_count; @@ -277,9 +292,11 @@ static bool s_do_session_events_contain(void *context) { struct aws_protocol_adapter_session_event record; aws_array_list_get_at(&wait_context->fixture->session_events, &record, i); - if (record.joined_session == wait_context->expected_event->joined_session) { - ++found; + if (record.joined_session != wait_context->expected_event->joined_session) { + continue; } + + ++found; } return found >= wait_context->expected_count; @@ -352,7 +369,7 @@ static void s_wait_for_incoming_publish_events_contains( } struct test_publish_result_wait_context { - bool expected_success; + int expected_error_code; size_t expected_count; struct aws_request_response_mqtt5_adapter_test_fixture *fixture; }; @@ -364,10 +381,10 @@ static bool s_do_publish_results_contain(void *context) { size_t num_events = aws_array_list_length(&wait_context->fixture->publish_results); for (size_t i = 0; i < num_events; ++i) { - bool success = false; - aws_array_list_get_at(&wait_context->fixture->publish_results, &success, i); + int error_code = AWS_ERROR_SUCCESS; + aws_array_list_get_at(&wait_context->fixture->publish_results, &error_code, i); - if (success == wait_context->expected_success) { + if (error_code == wait_context->expected_error_code) { ++found; } } @@ -377,11 +394,11 @@ static bool s_do_publish_results_contain(void *context) { static void s_wait_for_publish_results_contains( struct aws_request_response_mqtt5_adapter_test_fixture *fixture, - bool success, + int expected_error_code, size_t expected_count) { struct test_publish_result_wait_context context = { - .expected_success = success, + .expected_error_code = expected_error_code, .expected_count = expected_count, .fixture = fixture, }; @@ -420,6 +437,19 @@ static int s_aws_mqtt5_server_send_failed_suback_on_subscribe( return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view); } +static int s_test_type_to_expected_error_code(enum protocol_adapter_operation_test_type test_type) { + switch (test_type) { + case PAOTT_FAILURE_TIMEOUT: + return AWS_ERROR_MQTT_TIMEOUT; + case PAOTT_FAILURE_REASON_CODE: + return AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; + case PAOTT_FAILURE_ERROR_CODE: + return AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY; + default: + return AWS_ERROR_SUCCESS; + } +} + static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( struct aws_allocator *allocator, enum protocol_adapter_operation_test_type test_type) { @@ -457,12 +487,15 @@ static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); } - struct request_response_protocol_adapter_subscription_event_record expected_outcome = { - .event_type = (test_type == PAOTT_SUCCESS) ? AWS_PASET_SUBSCRIBE_SUCCESS : AWS_PASET_SUBSCRIBE_FAILURE, - }; + int expected_error_code = s_test_type_to_expected_error_code(test_type); - aws_byte_buf_init_copy_from_cursor( - &expected_outcome.topic_filter, allocator, aws_byte_cursor_from_c_str("hello/world")); + struct request_response_protocol_adapter_subscription_event_record expected_outcome; + s_request_response_protocol_adapter_subscription_event_record_init( + &expected_outcome, + allocator, + AWS_PASET_SUBSCRIBE, + aws_byte_cursor_from_c_str("hello/world"), + expected_error_code); struct aws_protocol_adapter_subscribe_options subscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -595,12 +628,15 @@ static int s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test( aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); } - struct request_response_protocol_adapter_subscription_event_record expected_outcome = { - .event_type = (test_type == PAOTT_SUCCESS) ? AWS_PASET_UNSUBSCRIBE_SUCCESS : AWS_PASET_UNSUBSCRIBE_FAILURE, - }; + int expected_error_code = s_test_type_to_expected_error_code(test_type); - aws_byte_buf_init_copy_from_cursor( - &expected_outcome.topic_filter, allocator, aws_byte_cursor_from_c_str("hello/world")); + struct request_response_protocol_adapter_subscription_event_record expected_outcome; + s_request_response_protocol_adapter_subscription_event_record_init( + &expected_outcome, + allocator, + AWS_PASET_UNSUBSCRIBE, + aws_byte_cursor_from_c_str("hello/world"), + expected_error_code); struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -743,7 +779,8 @@ static int s_do_request_response_mqtt5_protocol_adapter_publish_test( aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); - s_wait_for_publish_results_contains(&fixture, test_type == PAOTT_SUCCESS, 1); + int expected_error_code = s_test_type_to_expected_error_code(test_type); + s_wait_for_publish_results_contains(&fixture, expected_error_code, 1); s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); From 75e1c3bb02012e70eda35a5148bef38be143c731 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 07:26:48 -0800 Subject: [PATCH 026/124] Sync point --- include/aws/mqtt/private/request-response/protocol_adapter.h | 1 + source/request-response/protocol_adapter.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index c70a2b99..82f10c0b 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -77,6 +77,7 @@ enum aws_protocol_adapter_subscription_event_type { struct aws_protocol_adapter_subscription_event { struct aws_byte_cursor topic_filter; enum aws_protocol_adapter_subscription_event_type event_type; + int error_code; }; /* diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index da604023..5b3b41f9 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -112,6 +112,7 @@ static void s_protocol_adapter_5_subscribe_completion( struct aws_protocol_adapter_subscription_event subscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), .event_type = success ? AWS_PASET_SUBSCRIBE_SUCCESS : AWS_PASET_SUBSCRIBE_FAILURE, + .error_code = error_code, }; (*adapter->config.subscription_event_callback)(&subscribe_event, adapter->config.user_data); @@ -176,6 +177,7 @@ static void s_protocol_adapter_5_unsubscribe_completion( struct aws_protocol_adapter_subscription_event unsubscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), .event_type = success ? AWS_PASET_UNSUBSCRIBE_SUCCESS : AWS_PASET_UNSUBSCRIBE_FAILURE, + .error_code = error_code, }; (*adapter->config.subscription_event_callback)(&unsubscribe_event, adapter->config.user_data); From 7efe25624ba240f17c0b4971bdbd80d04b47e929 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 10:33:02 -0800 Subject: [PATCH 027/124] success/failure -> error code --- include/aws/mqtt/mqtt.h | 1 + .../request-response/protocol_adapter.h | 8 +- source/mqtt.c | 3 + source/request-response/protocol_adapter.c | 27 +++-- .../request_response_protocol_adapter_tests.c | 101 ++++++++++++------ 5 files changed, 92 insertions(+), 48 deletions(-) diff --git a/include/aws/mqtt/mqtt.h b/include/aws/mqtt/mqtt.h index 974c4576..8e7cd0ef 100644 --- a/include/aws/mqtt/mqtt.h +++ b/include/aws/mqtt/mqtt.h @@ -81,6 +81,7 @@ enum aws_mqtt_error { AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT, AWS_ERROR_MQTT_CONNECTION_RESUBSCRIBE_NO_TOPICS, AWS_ERROR_MQTT_CONNECTION_SUBSCRIBE_FAILURE, + AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, AWS_ERROR_END_MQTT_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_MQTT_PACKAGE_ID), }; diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 82f10c0b..f09d6135 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -52,7 +52,7 @@ struct aws_protocol_adapter_publish_options { * Invoked on success/failure of the publish itself. Our implementations use QoS1 which means that success * will be on puback receipt. */ - void (*completion_callback_fn)(bool, void *); + void (*completion_callback_fn)(int, void *); /* * User data to pass in when invoking the completion callback @@ -64,10 +64,8 @@ struct aws_protocol_adapter_publish_options { * Describes the type of subscription event (relative to a topic filter) */ enum aws_protocol_adapter_subscription_event_type { - AWS_PASET_SUBSCRIBE_SUCCESS, - AWS_PASET_SUBSCRIBE_FAILURE, - AWS_PASET_UNSUBSCRIBE_SUCCESS, - AWS_PASET_UNSUBSCRIBE_FAILURE, + AWS_PASET_SUBSCRIBE, + AWS_PASET_UNSUBSCRIBE, }; /* diff --git a/source/mqtt.c b/source/mqtt.c index c87ec0cc..fd16340a 100644 --- a/source/mqtt.c +++ b/source/mqtt.c @@ -233,6 +233,9 @@ bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter) AWS_DEFINE_ERROR_INFO_MQTT( AWS_ERROR_MQTT_CONNECTION_SUBSCRIBE_FAILURE, "MQTT subscribe operation failed"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, + "MQTT operation returned a failing reason code"), }; /* clang-format on */ #undef AWS_DEFINE_ERROR_INFO_MQTT diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 5b3b41f9..68cc8667 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -106,12 +106,15 @@ static void s_protocol_adapter_5_subscribe_completion( goto done; } - bool success = error_code == AWS_ERROR_SUCCESS && suback != NULL && suback->reason_code_count == 1 && - suback->reason_codes[0] <= AWS_MQTT5_SARC_GRANTED_QOS_2; + if (error_code == AWS_ERROR_SUCCESS) { + if (suback == NULL || suback->reason_code_count != 1 || suback->reason_codes[0] >= 128) { + error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; + } + } struct aws_protocol_adapter_subscription_event subscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), - .event_type = success ? AWS_PASET_SUBSCRIBE_SUCCESS : AWS_PASET_SUBSCRIBE_FAILURE, + .event_type = AWS_PASET_SUBSCRIBE, .error_code = error_code, }; @@ -171,12 +174,15 @@ static void s_protocol_adapter_5_unsubscribe_completion( goto done; } - bool success = error_code == AWS_ERROR_SUCCESS && unsuback != NULL && unsuback->reason_code_count == 1 && - unsuback->reason_codes[0] < 128; + if (error_code == AWS_ERROR_SUCCESS) { + if (unsuback == NULL || unsuback->reason_code_count != 1 || unsuback->reason_codes[0] >= 128) { + error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; + } + } struct aws_protocol_adapter_subscription_event unsubscribe_event = { .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), - .event_type = success ? AWS_PASET_UNSUBSCRIBE_SUCCESS : AWS_PASET_UNSUBSCRIBE_FAILURE, + .event_type = AWS_PASET_UNSUBSCRIBE, .error_code = error_code, }; @@ -224,7 +230,7 @@ struct aws_mqtt_protocol_adapter_5_publish_op_data { struct aws_allocator *allocator; struct aws_weak_ref *callback_ref; - void (*completion_callback_fn)(bool, void *); + void (*completion_callback_fn)(int, void *); void *user_data; }; @@ -262,15 +268,14 @@ static void s_protocol_adapter_5_publish_completion( goto done; } - bool success = false; if (error_code == AWS_ERROR_SUCCESS && packet_type == AWS_MQTT5_PT_PUBACK) { const struct aws_mqtt5_packet_puback_view *puback = packet; - if (puback->reason_code < 128) { - success = true; + if (puback->reason_code >= 128) { + error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; } } - (*publish_data->completion_callback_fn)(success, publish_data->user_data); + (*publish_data->completion_callback_fn)(error_code, publish_data->user_data); done: diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c index 586c9c00..2034f460 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -34,13 +34,20 @@ static void s_request_response_protocol_adapter_incoming_publish_event_record_cl struct request_response_protocol_adapter_subscription_event_record { enum aws_protocol_adapter_subscription_event_type event_type; struct aws_byte_buf topic_filter; + int error_code; }; static void s_request_response_protocol_adapter_subscription_event_record_init( struct request_response_protocol_adapter_subscription_event_record *record, struct aws_allocator *allocator, - struct aws_byte_cursor topic_filter) { + enum aws_protocol_adapter_subscription_event_type event_type, + struct aws_byte_cursor topic_filter, + int error_code) { + AWS_ZERO_STRUCT(*record); + + record->event_type = event_type; + record->error_code = error_code; aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); } @@ -71,9 +78,9 @@ static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event( void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; - struct request_response_protocol_adapter_subscription_event_record record = {.event_type = event->event_type}; + struct request_response_protocol_adapter_subscription_event_record record; s_request_response_protocol_adapter_subscription_event_record_init( - &record, fixture->allocator, event->topic_filter); + &record, fixture->allocator, event->event_type, event->topic_filter, event->error_code); aws_mutex_lock(&fixture->lock); aws_array_list_push_back(&fixture->subscription_events, &record); @@ -117,11 +124,11 @@ static void s_rr_mqtt5_protocol_adapter_test_on_session_event( aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_publish_result(bool success, void *user_data) { +static void s_rr_mqtt5_protocol_adapter_test_on_publish_result(int error_code, void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; aws_mutex_lock(&fixture->lock); - aws_array_list_push_back(&fixture->publish_results, &success); + aws_array_list_push_back(&fixture->publish_results, &error_code); aws_mutex_unlock(&fixture->lock); aws_condition_variable_notify_all(&fixture->signal); } @@ -162,7 +169,7 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( allocator, 10, sizeof(struct request_response_protocol_adapter_subscription_event_record)); - aws_array_list_init_dynamic(&fixture->publish_results, allocator, 10, sizeof(bool)); + aws_array_list_init_dynamic(&fixture->publish_results, allocator, 10, sizeof(int)); aws_mutex_init(&fixture->lock); aws_condition_variable_init(&fixture->signal); @@ -232,14 +239,22 @@ static bool s_do_subscription_events_contain(void *context) { struct request_response_protocol_adapter_subscription_event_record record; aws_array_list_get_at(&wait_context->fixture->subscription_events, &record, i); - if (record.event_type == wait_context->expected_event->event_type) { - struct aws_byte_cursor record_topic_filter = aws_byte_cursor_from_buf(&record.topic_filter); - struct aws_byte_cursor expected_topic_filter = - aws_byte_cursor_from_buf(&wait_context->expected_event->topic_filter); - if (aws_byte_cursor_eq(&record_topic_filter, &expected_topic_filter)) { - ++found; - } + if (record.event_type != wait_context->expected_event->event_type) { + continue; } + + if (record.error_code != wait_context->expected_event->error_code) { + continue; + } + + struct aws_byte_cursor record_topic_filter = aws_byte_cursor_from_buf(&record.topic_filter); + struct aws_byte_cursor expected_topic_filter = + aws_byte_cursor_from_buf(&wait_context->expected_event->topic_filter); + if (!aws_byte_cursor_eq(&record_topic_filter, &expected_topic_filter)) { + continue; + } + + ++found; } return found >= wait_context->expected_count; @@ -277,9 +292,11 @@ static bool s_do_session_events_contain(void *context) { struct aws_protocol_adapter_session_event record; aws_array_list_get_at(&wait_context->fixture->session_events, &record, i); - if (record.joined_session == wait_context->expected_event->joined_session) { - ++found; + if (record.joined_session != wait_context->expected_event->joined_session) { + continue; } + + ++found; } return found >= wait_context->expected_count; @@ -352,7 +369,7 @@ static void s_wait_for_incoming_publish_events_contains( } struct test_publish_result_wait_context { - bool expected_success; + int expected_error_code; size_t expected_count; struct aws_request_response_mqtt5_adapter_test_fixture *fixture; }; @@ -364,10 +381,10 @@ static bool s_do_publish_results_contain(void *context) { size_t num_events = aws_array_list_length(&wait_context->fixture->publish_results); for (size_t i = 0; i < num_events; ++i) { - bool success = false; - aws_array_list_get_at(&wait_context->fixture->publish_results, &success, i); + int error_code = AWS_ERROR_SUCCESS; + aws_array_list_get_at(&wait_context->fixture->publish_results, &error_code, i); - if (success == wait_context->expected_success) { + if (error_code == wait_context->expected_error_code) { ++found; } } @@ -377,11 +394,11 @@ static bool s_do_publish_results_contain(void *context) { static void s_wait_for_publish_results_contains( struct aws_request_response_mqtt5_adapter_test_fixture *fixture, - bool success, + int expected_error_code, size_t expected_count) { struct test_publish_result_wait_context context = { - .expected_success = success, + .expected_error_code = expected_error_code, .expected_count = expected_count, .fixture = fixture, }; @@ -420,6 +437,19 @@ static int s_aws_mqtt5_server_send_failed_suback_on_subscribe( return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view); } +static int s_test_type_to_expected_error_code(enum protocol_adapter_operation_test_type test_type) { + switch (test_type) { + case PAOTT_FAILURE_TIMEOUT: + return AWS_ERROR_MQTT_TIMEOUT; + case PAOTT_FAILURE_REASON_CODE: + return AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; + case PAOTT_FAILURE_ERROR_CODE: + return AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY; + default: + return AWS_ERROR_SUCCESS; + } +} + static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( struct aws_allocator *allocator, enum protocol_adapter_operation_test_type test_type) { @@ -457,12 +487,15 @@ static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); } - struct request_response_protocol_adapter_subscription_event_record expected_outcome = { - .event_type = (test_type == PAOTT_SUCCESS) ? AWS_PASET_SUBSCRIBE_SUCCESS : AWS_PASET_SUBSCRIBE_FAILURE, - }; + int expected_error_code = s_test_type_to_expected_error_code(test_type); - aws_byte_buf_init_copy_from_cursor( - &expected_outcome.topic_filter, allocator, aws_byte_cursor_from_c_str("hello/world")); + struct request_response_protocol_adapter_subscription_event_record expected_outcome; + s_request_response_protocol_adapter_subscription_event_record_init( + &expected_outcome, + allocator, + AWS_PASET_SUBSCRIBE, + aws_byte_cursor_from_c_str("hello/world"), + expected_error_code); struct aws_protocol_adapter_subscribe_options subscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -595,12 +628,15 @@ static int s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test( aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); } - struct request_response_protocol_adapter_subscription_event_record expected_outcome = { - .event_type = (test_type == PAOTT_SUCCESS) ? AWS_PASET_UNSUBSCRIBE_SUCCESS : AWS_PASET_UNSUBSCRIBE_FAILURE, - }; + int expected_error_code = s_test_type_to_expected_error_code(test_type); - aws_byte_buf_init_copy_from_cursor( - &expected_outcome.topic_filter, allocator, aws_byte_cursor_from_c_str("hello/world")); + struct request_response_protocol_adapter_subscription_event_record expected_outcome; + s_request_response_protocol_adapter_subscription_event_record_init( + &expected_outcome, + allocator, + AWS_PASET_UNSUBSCRIBE, + aws_byte_cursor_from_c_str("hello/world"), + expected_error_code); struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -743,7 +779,8 @@ static int s_do_request_response_mqtt5_protocol_adapter_publish_test( aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); - s_wait_for_publish_results_contains(&fixture, test_type == PAOTT_SUCCESS, 1); + int expected_error_code = s_test_type_to_expected_error_code(test_type); + s_wait_for_publish_results_contains(&fixture, expected_error_code, 1); s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); From ac334f44092a9c80290dde911f8908b9d7034a59 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 10:43:14 -0800 Subject: [PATCH 028/124] Formatting --- tests/v3/mqtt311_listener_test.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/v3/mqtt311_listener_test.c b/tests/v3/mqtt311_listener_test.c index 57b54c25..df037d4b 100644 --- a/tests/v3/mqtt311_listener_test.c +++ b/tests/v3/mqtt311_listener_test.c @@ -114,9 +114,11 @@ static int mqtt311_listener_test_context_init( struct aws_mqtt311_listener_config listener_config = { .connection = mqtt311_test_context->mqtt_connection, .listener_callbacks = - {.publish_received_handler = s_311_listener_test_on_publish_received, - .connection_success_handler = s_311_listener_test_on_connection_success, - .user_data = context}, + { + .publish_received_handler = s_311_listener_test_on_publish_received, + .connection_success_handler = s_311_listener_test_on_connection_success, + .user_data = context, + }, .termination_callback = s_311_listener_test_on_termination, .termination_callback_user_data = context, }; From 6ac58f1cac342f7e0901ea0e552e82851bc4e559 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 10:56:56 -0800 Subject: [PATCH 029/124] Clean up --- include/aws/mqtt/private/mqtt311_listener.h | 12 +++++++++--- source/mqtt311_listener.c | 14 ++++++++------ source/v5/mqtt5_listener.c | 1 + 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/include/aws/mqtt/private/mqtt311_listener.h b/include/aws/mqtt/private/mqtt311_listener.h index e1e5162e..2a4f5b53 100644 --- a/include/aws/mqtt/private/mqtt311_listener.h +++ b/include/aws/mqtt/private/mqtt311_listener.h @@ -24,7 +24,9 @@ typedef void(aws_mqtt311_listener_termination_completion_fn)(void *complete_ctx) * All the callbacks that are supported here are invoked only on the 311 connection's event loop. With the * add/remove callback set also on the event loop, everything is correctly serialized without data races. * - * We only listen to connection-resumed because the only connection-level event we care about is a failure + * If binding additional callbacks, they must only be invoked from the connection's event loop. + * + * We only listen to connection-success because the only connection-level event we care about is a failure * to rejoin a session (which invalidates all subscriptions that were considered valid) */ struct aws_mqtt311_callback_set { @@ -147,8 +149,7 @@ void aws_mqtt311_callback_set_manager_remove( uint64_t callback_set_id); /** - * Walks the incoming publish handler chain for an MQTT311 connection. The chain's callbacks will be invoked - * until either the end is reached or one of the callbacks returns true. + * Walks the incoming publish handler chain for an MQTT311 connection, invoking each in sequence. * * May only be called on the client's event loop thread. */ @@ -161,6 +162,11 @@ void aws_mqtt311_callback_set_manager_on_publish_received( enum aws_mqtt_qos qos, bool retain); +/** + * Invokes a connection success event on each listener in the manager's collection of callback sets. + * + * May only be called on the client's event loop thread. + */ AWS_MQTT_API void aws_mqtt311_callback_set_manager_on_connection_success( struct aws_mqtt311_callback_set_manager *manager, diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c index 4e827152..caf9b650 100644 --- a/source/mqtt311_listener.c +++ b/source/mqtt311_listener.c @@ -99,10 +99,12 @@ struct aws_mqtt311_listener *aws_mqtt311_listener_new( struct aws_allocator *allocator, struct aws_mqtt311_listener_config *config) { if (config->connection == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); return NULL; } if (aws_mqtt_client_connection_get_impl_type(config->connection) != AWS_MQTT311_IT_311_CONNECTION) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); return NULL; } @@ -187,8 +189,8 @@ static struct aws_mqtt311_callback_set_entry *s_new_311_callback_set_entry( entry->callbacks = *callback_set; AWS_LOGF_INFO( - AWS_LS_MQTT5_GENERAL, - "id=%p: callback manager created new entry :%" PRIu64, + AWS_LS_MQTT_GENERAL, + "id=%p: MQTT311 callback manager created new entry :%" PRIu64, (void *)manager->connection, entry->id); @@ -226,8 +228,8 @@ void aws_mqtt311_callback_set_manager_remove( aws_linked_list_remove(&entry->node); AWS_LOGF_INFO( - AWS_LS_MQTT5_GENERAL, - "id=%p: callback manager removed entry id=%" PRIu64, + AWS_LS_MQTT_GENERAL, + "id=%p: MQTT311 callback manager removed entry id=%" PRIu64, (void *)manager->connection, entry->id); aws_mem_release(entry->allocator, entry); @@ -235,8 +237,8 @@ void aws_mqtt311_callback_set_manager_remove( } } AWS_LOGF_INFO( - AWS_LS_MQTT5_GENERAL, - "id=%p: callback manager failed to remove entry id=%" PRIu64 ", callback set id not found.", + AWS_LS_MQTT_GENERAL, + "id=%p: MQTT311 callback manager failed to remove entry id=%" PRIu64 ", callback set id not found.", (void *)manager->connection, callback_set_id); } diff --git a/source/v5/mqtt5_listener.c b/source/v5/mqtt5_listener.c index 04170394..ebb45b81 100644 --- a/source/v5/mqtt5_listener.c +++ b/source/v5/mqtt5_listener.c @@ -84,6 +84,7 @@ struct aws_mqtt5_listener *aws_mqtt5_listener_new( struct aws_allocator *allocator, struct aws_mqtt5_listener_config *config) { if (config->client == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); return NULL; } From 67db09c7a4883ea69ef20cca36f5d90f8f94241a Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 11:02:43 -0800 Subject: [PATCH 030/124] Test fixes --- tests/v3/mqtt311_listener_test.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/v3/mqtt311_listener_test.c b/tests/v3/mqtt311_listener_test.c index df037d4b..758ee89f 100644 --- a/tests/v3/mqtt311_listener_test.c +++ b/tests/v3/mqtt311_listener_test.c @@ -45,6 +45,7 @@ static void s_311_listener_test_on_publish_received( enum aws_mqtt_qos qos, bool retain, void *userdata) { + (void)connection; (void)dup; (void)qos; (void)retain; @@ -68,6 +69,7 @@ static void s_311_listener_test_on_connection_success( enum aws_mqtt_connect_return_code return_code, bool session_present, void *userdata) { + (void)connection; (void)return_code; struct mqtt311_listener_test_context *context = userdata; @@ -213,7 +215,7 @@ static int s_do_mqtt311_listener_connection_success_event_test(struct aws_alloca struct mqtt311_listener_test_context test_context; ASSERT_SUCCESS(mqtt311_listener_test_context_init(&test_context, allocator, &mqtt311_context)); - mqtt_mock_server_set_session_present(mqtt311_context.mock_server, false); + mqtt_mock_server_set_session_present(mqtt311_context.mock_server, session_present); struct aws_mqtt_connection_options connection_options = { .user_data = &mqtt311_context, @@ -227,7 +229,7 @@ static int s_do_mqtt311_listener_connection_success_event_test(struct aws_alloca ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); aws_test311_wait_for_connection_to_complete(&mqtt311_context); - s_wait_for_connection_success_events(&test_context, false, 1); + s_wait_for_connection_success_events(&test_context, session_present, 1); ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); @@ -236,7 +238,7 @@ static int s_do_mqtt311_listener_connection_success_event_test(struct aws_alloca ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); aws_test311_wait_for_connection_to_complete(&mqtt311_context); - s_wait_for_connection_success_events(&test_context, false, 2); + s_wait_for_connection_success_events(&test_context, session_present, 2); ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); From ca0c14fbaa6401624e81f8d6a7dcb882f07abfe3 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 11:17:11 -0800 Subject: [PATCH 031/124] Rebuild the branch --- include/aws/mqtt/private/client_impl.h | 4 + include/aws/mqtt/private/client_impl_shared.h | 4 +- include/aws/mqtt/private/mqtt311_listener.h | 180 +++ source/client.c | 7 +- source/client_channel_handler.c | 6 + source/client_impl_shared.c | 3 +- source/mqtt311_listener.c | 291 ++++ source/v5/mqtt5_listener.c | 1 + source/v5/mqtt5_to_mqtt3_adapter.c | 2 +- tests/CMakeLists.txt | 4 + tests/v3/connection_state_test.c | 1333 +++++------------ tests/v3/mqtt311_listener_test.c | 375 +++++ tests/v3/mqtt311_testing_utils.c | 580 +++++++ tests/v3/mqtt311_testing_utils.h | 155 ++ tests/v3/mqtt_mock_server_handler.c | 40 +- tests/v3/mqtt_mock_server_handler.h | 11 + tests/v5/mqtt5_testing_utils.c | 2 +- 17 files changed, 2005 insertions(+), 993 deletions(-) create mode 100644 include/aws/mqtt/private/mqtt311_listener.h create mode 100644 source/mqtt311_listener.c create mode 100644 tests/v3/mqtt311_listener_test.c create mode 100644 tests/v3/mqtt311_testing_utils.c create mode 100644 tests/v3/mqtt311_testing_utils.h diff --git a/include/aws/mqtt/private/client_impl.h b/include/aws/mqtt/private/client_impl.h index 1d0dd67a..ce041b2d 100644 --- a/include/aws/mqtt/private/client_impl.h +++ b/include/aws/mqtt/private/client_impl.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -255,6 +256,9 @@ struct aws_mqtt_client_connection_311_impl { aws_mqtt_on_operation_statistics_fn *on_any_operation_statistics; void *on_any_operation_statistics_ud; + /* listener callbacks */ + struct aws_mqtt311_callback_set_manager callback_manager; + /* Connection tasks. */ struct aws_mqtt_reconnect_task *reconnect_task; struct aws_channel_task ping_task; diff --git a/include/aws/mqtt/private/client_impl_shared.h b/include/aws/mqtt/private/client_impl_shared.h index 49961308..fa5cbeb3 100644 --- a/include/aws/mqtt/private/client_impl_shared.h +++ b/include/aws/mqtt/private/client_impl_shared.h @@ -122,7 +122,7 @@ struct aws_mqtt_client_connection_vtable { int (*get_stats_fn)(void *impl, struct aws_mqtt_connection_operation_statistics *stats); - enum aws_mqtt311_impl_type (*get_impl_type)(void *impl); + enum aws_mqtt311_impl_type (*get_impl_type)(const void *impl); }; struct aws_mqtt_client_connection { @@ -131,7 +131,7 @@ struct aws_mqtt_client_connection { }; AWS_MQTT_API enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type( - struct aws_mqtt_client_connection *connection); + const struct aws_mqtt_client_connection *connection); AWS_MQTT_API uint64_t aws_mqtt_hash_uint16_t(const void *item); diff --git a/include/aws/mqtt/private/mqtt311_listener.h b/include/aws/mqtt/private/mqtt311_listener.h new file mode 100644 index 00000000..2a4f5b53 --- /dev/null +++ b/include/aws/mqtt/private/mqtt311_listener.h @@ -0,0 +1,180 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#ifndef AWS_MQTT_MQTT311_LISTENER_H +#define AWS_MQTT_MQTT311_LISTENER_H + +#include + +#include +#include + +AWS_PUSH_SANE_WARNING_LEVEL + +/** + * Callback signature for when an mqtt311 listener has completely destroyed itself. + */ +typedef void(aws_mqtt311_listener_termination_completion_fn)(void *complete_ctx); + +/** + * A record that tracks MQTT311 client connection callbacks which can be dynamically injected via a listener. + * + * All the callbacks that are supported here are invoked only on the 311 connection's event loop. With the + * add/remove callback set also on the event loop, everything is correctly serialized without data races. + * + * If binding additional callbacks, they must only be invoked from the connection's event loop. + * + * We only listen to connection-success because the only connection-level event we care about is a failure + * to rejoin a session (which invalidates all subscriptions that were considered valid) + */ +struct aws_mqtt311_callback_set { + + /* Called from s_packet_handler_publish which is event-loop invoked */ + aws_mqtt_client_publish_received_fn *publish_received_handler; + + /* Called from s_packet_handler_connack which is event-loop invoked */ + aws_mqtt_client_on_connection_success_fn *connection_success_handler; + + void *user_data; +}; + +/** + * An internal type for managing chains of callbacks attached to an mqtt311 client connection. Supports chains for + * lifecycle events and incoming publish packet handling. + * + * Assumed to be owned and used only by an MQTT311 client connection. + */ +struct aws_mqtt311_callback_set_manager { + struct aws_allocator *allocator; + + struct aws_mqtt_client_connection *connection; + + struct aws_linked_list callback_set_entries; + + uint64_t next_callback_set_entry_id; +}; + +/** + * Configuration options for MQTT311 listener objects. + */ +struct aws_mqtt311_listener_config { + + /** + * MQTT311 client connection to listen to events on + */ + struct aws_mqtt_client_connection *connection; + + /** + * Callbacks to invoke when events occur on the MQTT311 client connection + */ + struct aws_mqtt311_callback_set listener_callbacks; + + /** + * Listener destruction is asynchronous and thus requires a termination callback and associated user data + * to notify the user that the listener has been fully destroyed and no further events will be received. + */ + aws_mqtt311_listener_termination_completion_fn *termination_callback; + void *termination_callback_user_data; +}; + +AWS_EXTERN_C_BEGIN + +/** + * Creates a new MQTT311 listener object. For as long as the listener lives, incoming publishes and lifecycle events + * will be forwarded to the callbacks configured on the listener. + * + * @param allocator allocator to use + * @param config listener configuration + * @return a new aws_mqtt311_listener object + */ +AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_new( + struct aws_allocator *allocator, + struct aws_mqtt311_listener_config *config); + +/** + * Adds a reference to an mqtt311 listener. + * + * @param listener listener to add a reference to + * @return the listener object + */ +AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_acquire(struct aws_mqtt311_listener *listener); + +/** + * Removes a reference to an mqtt311 listener. When the reference count drops to zero, the listener's asynchronous + * destruction will be started. + * + * @param listener listener to remove a reference from + * @return NULL + */ +AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_release(struct aws_mqtt311_listener *listener); + +/** + * Initializes a callback set manager + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_init( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_allocator *allocator, + struct aws_mqtt_client_connection *connection); + +/** + * Cleans up a callback set manager. + * + * aws_mqtt311_callback_set_manager_init must have been previously called or this will crash. + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_clean_up(struct aws_mqtt311_callback_set_manager *manager); + +/** + * Adds a callback set to the front of the handler chain. Returns an integer id that can be used to selectively + * remove the callback set from the manager. + * + * May only be called on the client's event loop thread. + */ +AWS_MQTT_API +uint64_t aws_mqtt311_callback_set_manager_push_front( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_mqtt311_callback_set *callback_set); + +/** + * Removes a callback set from the handler chain. + * + * May only be called on the client's event loop thread. + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_remove( + struct aws_mqtt311_callback_set_manager *manager, + uint64_t callback_set_id); + +/** + * Walks the incoming publish handler chain for an MQTT311 connection, invoking each in sequence. + * + * May only be called on the client's event loop thread. + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_on_publish_received( + struct aws_mqtt311_callback_set_manager *manager, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain); + +/** + * Invokes a connection success event on each listener in the manager's collection of callback sets. + * + * May only be called on the client's event loop thread. + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_on_connection_success( + struct aws_mqtt311_callback_set_manager *manager, + enum aws_mqtt_connect_return_code return_code, + bool rejoined_session); + +AWS_EXTERN_C_END + +AWS_POP_SANE_WARNING_LEVEL + +#endif /* AWS_MQTT_MQTT311_LISTENER_H */ diff --git a/source/client.c b/source/client.c index 631a26c0..8a8bc386 100644 --- a/source/client.c +++ b/source/client.c @@ -259,6 +259,7 @@ static void s_mqtt_client_shutdown( (void)channel; struct aws_mqtt_client_connection_311_impl *connection = user_data; + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection->loop)); AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, "id=%p: Channel has been shutdown with error code %d", (void *)connection, error_code); @@ -801,6 +802,8 @@ static void s_mqtt_client_connection_destroy_final(struct aws_mqtt_client_connec termination_handler_user_data = connection->on_termination_ud; } + aws_mqtt311_callback_set_manager_clean_up(&connection->callback_manager); + /* If the reconnect_task isn't freed, free it */ if (connection->reconnect_task) { aws_mem_release(connection->reconnect_task->allocator, connection->reconnect_task); @@ -3220,7 +3223,7 @@ static void s_aws_mqtt_client_connection_311_release(void *impl) { aws_ref_count_release(&connection->ref_count); } -enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_3_get_impl(void *impl) { +enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_3_get_impl(const void *impl) { (void)impl; return AWS_MQTT311_IT_311_CONNECTION; @@ -3351,6 +3354,8 @@ struct aws_mqtt_client_connection *aws_mqtt_client_connection_new(struct aws_mqt connection->handler.vtable = aws_mqtt_get_client_channel_vtable(); connection->handler.impl = connection; + aws_mqtt311_callback_set_manager_init(&connection->callback_manager, connection->allocator, &connection->base); + return &connection->base; failed_init_outstanding_requests_table: diff --git a/source/client_channel_handler.c b/source/client_channel_handler.c index 543f0950..b95a43fc 100644 --- a/source/client_channel_handler.c +++ b/source/client_channel_handler.c @@ -263,6 +263,9 @@ static int s_packet_handler_connack(struct aws_byte_cursor message_cursor, void MQTT_CLIENT_CALL_CALLBACK_ARGS( connection, on_connection_success, connack.connect_return_code, connack.session_present); + aws_mqtt311_callback_set_manager_on_connection_success( + &connection->callback_manager, 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); @@ -291,6 +294,9 @@ static int s_packet_handler_publish(struct aws_byte_cursor message_cursor, void MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_any_publish, &publish.topic_name, &publish.payload, dup, qos, retain); + aws_mqtt311_callback_set_manager_on_publish_received( + &connection->callback_manager, &publish.topic_name, &publish.payload, dup, qos, retain); + AWS_LOGF_TRACE( AWS_LS_MQTT_CLIENT, "id=%p: publish received with msg id=%" PRIu16 " dup=%d qos=%d retain=%d payload-size=%zu topic=" PRInSTR, diff --git a/source/client_impl_shared.c b/source/client_impl_shared.c index 0f0e70a2..525fb7e4 100644 --- a/source/client_impl_shared.c +++ b/source/client_impl_shared.c @@ -204,7 +204,8 @@ int aws_mqtt_client_connection_get_stats( return (*connection->vtable->get_stats_fn)(connection->impl, stats); } -enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type(struct aws_mqtt_client_connection *connection) { +enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type( + const struct aws_mqtt_client_connection *connection) { return (*connection->vtable->get_impl_type)(connection->impl); } diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c new file mode 100644 index 00000000..caf9b650 --- /dev/null +++ b/source/mqtt311_listener.c @@ -0,0 +1,291 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include +#include +#include +#include +#include + +#include + +static struct aws_event_loop *s_mqtt_client_connection_get_event_loop( + const struct aws_mqtt_client_connection *connection) { + AWS_FATAL_ASSERT(aws_mqtt_client_connection_get_impl_type(connection) == AWS_MQTT311_IT_311_CONNECTION); + + struct aws_mqtt_client_connection_311_impl *connection_impl = connection->impl; + + return connection_impl->loop; +} + +struct aws_mqtt311_listener { + struct aws_allocator *allocator; + + struct aws_ref_count ref_count; + + struct aws_mqtt311_listener_config config; + + uint64_t callback_set_id; + + struct aws_task initialize_task; + struct aws_task terminate_task; +}; + +static void s_mqtt311_listener_destroy(struct aws_mqtt311_listener *listener) { + + aws_mqtt_client_connection_release(listener->config.connection); + + aws_mqtt311_listener_termination_completion_fn *termination_callback = listener->config.termination_callback; + void *termination_callback_user_data = listener->config.termination_callback_user_data; + + aws_mem_release(listener->allocator, listener); + + if (termination_callback != NULL) { + (*termination_callback)(termination_callback_user_data); + } +} + +static void s_mqtt311_listener_initialize_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { + (void)task; + + struct aws_mqtt311_listener *listener = arg; + + if (task_status == AWS_TASK_STATUS_RUN_READY) { + struct aws_mqtt_client_connection_311_impl *connection_impl = listener->config.connection->impl; + listener->callback_set_id = aws_mqtt311_callback_set_manager_push_front( + &connection_impl->callback_manager, &listener->config.listener_callbacks); + AWS_LOGF_INFO( + AWS_LS_MQTT_GENERAL, + "id=%p: Mqtt311 Listener initialized, listener id=%p", + (void *)listener->config.connection, + (void *)listener); + aws_mqtt311_listener_release(listener); + } else { + s_mqtt311_listener_destroy(listener); + } +} + +static void s_mqtt311_listener_terminate_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { + (void)task; + + struct aws_mqtt311_listener *listener = arg; + + if (task_status == AWS_TASK_STATUS_RUN_READY) { + struct aws_mqtt_client_connection_311_impl *connection_impl = listener->config.connection->impl; + aws_mqtt311_callback_set_manager_remove(&connection_impl->callback_manager, listener->callback_set_id); + } + + AWS_LOGF_INFO( + AWS_LS_MQTT_GENERAL, + "id=%p: Mqtt311 Listener terminated, listener id=%p", + (void *)listener->config.connection, + (void *)listener); + + s_mqtt311_listener_destroy(listener); +} + +static void s_aws_mqtt311_listener_on_zero_ref_count(void *context) { + struct aws_mqtt311_listener *listener = context; + + aws_event_loop_schedule_task_now( + s_mqtt_client_connection_get_event_loop(listener->config.connection), &listener->terminate_task); +} + +struct aws_mqtt311_listener *aws_mqtt311_listener_new( + struct aws_allocator *allocator, + struct aws_mqtt311_listener_config *config) { + if (config->connection == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + if (aws_mqtt_client_connection_get_impl_type(config->connection) != AWS_MQTT311_IT_311_CONNECTION) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_mqtt311_listener *listener = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt311_listener)); + + listener->allocator = allocator; + listener->config = *config; + + aws_mqtt_client_connection_acquire(config->connection); + aws_ref_count_init(&listener->ref_count, listener, s_aws_mqtt311_listener_on_zero_ref_count); + + aws_task_init( + &listener->initialize_task, s_mqtt311_listener_initialize_task_fn, listener, "Mqtt311ListenerInitialize"); + aws_task_init( + &listener->terminate_task, s_mqtt311_listener_terminate_task_fn, listener, "Mqtt311ListenerTerminate"); + + aws_mqtt311_listener_acquire(listener); + aws_event_loop_schedule_task_now( + s_mqtt_client_connection_get_event_loop(config->connection), &listener->initialize_task); + + return listener; +} + +struct aws_mqtt311_listener *aws_mqtt311_listener_acquire(struct aws_mqtt311_listener *listener) { + if (listener != NULL) { + aws_ref_count_acquire(&listener->ref_count); + } + + return listener; +} + +struct aws_mqtt311_listener *aws_mqtt311_listener_release(struct aws_mqtt311_listener *listener) { + if (listener != NULL) { + aws_ref_count_release(&listener->ref_count); + } + + return NULL; +} + +struct aws_mqtt311_callback_set_entry { + struct aws_allocator *allocator; + + struct aws_linked_list_node node; + + uint64_t id; + + struct aws_mqtt311_callback_set callbacks; +}; + +void aws_mqtt311_callback_set_manager_init( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_allocator *allocator, + struct aws_mqtt_client_connection *connection) { + + manager->allocator = allocator; + manager->connection = connection; /* no need to ref count, this is assumed to be owned by the client connection */ + manager->next_callback_set_entry_id = 1; + + aws_linked_list_init(&manager->callback_set_entries); +} + +void aws_mqtt311_callback_set_manager_clean_up(struct aws_mqtt311_callback_set_manager *manager) { + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); + + aws_linked_list_remove(&entry->node); + aws_mem_release(entry->allocator, entry); + } +} + +static struct aws_mqtt311_callback_set_entry *s_new_311_callback_set_entry( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_mqtt311_callback_set *callback_set) { + struct aws_mqtt311_callback_set_entry *entry = + aws_mem_calloc(manager->allocator, 1, sizeof(struct aws_mqtt311_callback_set_entry)); + + entry->allocator = manager->allocator; + entry->id = manager->next_callback_set_entry_id++; + entry->callbacks = *callback_set; + + AWS_LOGF_INFO( + AWS_LS_MQTT_GENERAL, + "id=%p: MQTT311 callback manager created new entry :%" PRIu64, + (void *)manager->connection, + entry->id); + + return entry; +} + +uint64_t aws_mqtt311_callback_set_manager_push_front( + struct aws_mqtt311_callback_set_manager *manager, + struct aws_mqtt311_callback_set *callback_set) { + + AWS_FATAL_ASSERT( + aws_event_loop_thread_is_callers_thread(s_mqtt_client_connection_get_event_loop(manager->connection))); + + struct aws_mqtt311_callback_set_entry *entry = s_new_311_callback_set_entry(manager, callback_set); + + aws_linked_list_push_front(&manager->callback_set_entries, &entry->node); + + return entry->id; +} + +void aws_mqtt311_callback_set_manager_remove( + struct aws_mqtt311_callback_set_manager *manager, + uint64_t callback_set_id) { + + AWS_FATAL_ASSERT( + aws_event_loop_thread_is_callers_thread(s_mqtt_client_connection_get_event_loop(manager->connection))); + + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); + + if (entry->id == callback_set_id) { + aws_linked_list_remove(&entry->node); + + AWS_LOGF_INFO( + AWS_LS_MQTT_GENERAL, + "id=%p: MQTT311 callback manager removed entry id=%" PRIu64, + (void *)manager->connection, + entry->id); + aws_mem_release(entry->allocator, entry); + return; + } + } + AWS_LOGF_INFO( + AWS_LS_MQTT_GENERAL, + "id=%p: MQTT311 callback manager failed to remove entry id=%" PRIu64 ", callback set id not found.", + (void *)manager->connection, + callback_set_id); +} + +void aws_mqtt311_callback_set_manager_on_publish_received( + struct aws_mqtt311_callback_set_manager *manager, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain) { + + struct aws_mqtt_client_connection_311_impl *connection_impl = manager->connection->impl; + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection_impl->loop)); + + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); + + struct aws_mqtt311_callback_set *callback_set = &entry->callbacks; + if (callback_set->publish_received_handler != NULL) { + (*callback_set->publish_received_handler)( + manager->connection, topic, payload, dup, qos, retain, callback_set->user_data); + } + } +} + +void aws_mqtt311_callback_set_manager_on_connection_success( + struct aws_mqtt311_callback_set_manager *manager, + enum aws_mqtt_connect_return_code return_code, + bool rejoined_session) { + + struct aws_mqtt_client_connection_311_impl *connection_impl = manager->connection->impl; + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection_impl->loop)); + + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); + + struct aws_mqtt311_callback_set *callback_set = &entry->callbacks; + if (callback_set->connection_success_handler != NULL) { + (*callback_set->connection_success_handler)( + manager->connection, return_code, rejoined_session, callback_set->user_data); + } + } +} diff --git a/source/v5/mqtt5_listener.c b/source/v5/mqtt5_listener.c index 04170394..ebb45b81 100644 --- a/source/v5/mqtt5_listener.c +++ b/source/v5/mqtt5_listener.c @@ -84,6 +84,7 @@ struct aws_mqtt5_listener *aws_mqtt5_listener_new( struct aws_allocator *allocator, struct aws_mqtt5_listener_config *config) { if (config->client == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); return NULL; } diff --git a/source/v5/mqtt5_to_mqtt3_adapter.c b/source/v5/mqtt5_to_mqtt3_adapter.c index 2cb0451d..72128a97 100644 --- a/source/v5/mqtt5_to_mqtt3_adapter.c +++ b/source/v5/mqtt5_to_mqtt3_adapter.c @@ -2854,7 +2854,7 @@ static uint16_t s_aws_mqtt_5_resubscribe_existing_topics( return 0; } -enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_5_get_impl(void *impl) { +enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_5_get_impl(const void *impl) { (void)impl; return AWS_MQTT311_IT_5_ADAPTER; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d93b7427..e52138a6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -459,6 +459,10 @@ add_test_case(request_response_mqtt5_protocol_adapter_session_event_rejoin) add_test_case(request_response_mqtt5_protocol_adapter_incoming_publish) add_test_case(request_response_mqtt5_protocol_adapter_shutdown_while_pending) +add_test_case(mqtt311_listener_connection_success_event_no_session) +add_test_case(mqtt311_listener_connection_success_event_with_session) +add_test_case(mqtt311_listener_publish_event) + generate_test_driver(${PROJECT_NAME}-tests) set(TEST_PAHO_CLIENT_BINARY_NAME ${PROJECT_NAME}-paho-client) diff --git a/tests/v3/connection_state_test.c b/tests/v3/connection_state_test.c index 6a3e086f..65f8b9e7 100644 --- a/tests/v3/connection_state_test.c +++ b/tests/v3/connection_state_test.c @@ -3,661 +3,22 @@ * SPDX-License-Identifier: Apache-2.0. */ -#include "mqtt_mock_server_handler.h" - -#include +#include -#include -#include -#include +#include "mqtt311_testing_utils.h" +#include "mqtt_mock_server_handler.h" #include -#include - #include -#include - -static const int TEST_LOG_SUBJECT = 60000; -static const int ONE_SEC = 1000000000; -// The value is extract from aws-c-mqtt/source/client.c -static const int AWS_RESET_RECONNECT_BACKOFF_DELAY_SECONDS = 10; -static const uint64_t RECONNECT_BACKOFF_DELAY_ERROR_MARGIN_NANO_SECONDS = 500000000; -#define DEFAULT_MIN_RECONNECT_DELAY_SECONDS 1 - -#define DEFAULT_TEST_PING_TIMEOUT_MS 1000 -#define DEFAULT_TEST_KEEP_ALIVE_S 2 - -struct received_publish_packet { - struct aws_byte_buf topic; - struct aws_byte_buf payload; - bool dup; - enum aws_mqtt_qos qos; - bool retain; -}; - -struct mqtt_connection_state_test { - struct aws_allocator *allocator; - struct aws_channel *server_channel; - struct aws_channel_handler *mock_server; - struct aws_client_bootstrap *client_bootstrap; - struct aws_server_bootstrap *server_bootstrap; - struct aws_event_loop_group *el_group; - struct aws_host_resolver *host_resolver; - struct aws_socket_endpoint endpoint; - struct aws_socket *listener; - struct aws_mqtt_client *mqtt_client; - struct aws_mqtt_client_connection *mqtt_connection; - struct aws_socket_options socket_options; - - bool session_present; - bool connection_completed; - bool connection_success; - bool connection_failure; - bool client_disconnect_completed; - bool server_disconnect_completed; - bool connection_interrupted; - bool connection_resumed; - bool subscribe_completed; - bool listener_destroyed; - bool connection_terminated; - int interruption_error; - int subscribe_complete_error; - int op_complete_error; - enum aws_mqtt_connect_return_code mqtt_return_code; - int error; - struct aws_condition_variable cvar; - struct aws_mutex lock; - /* any published messages from mock server, that you may not subscribe to. (Which should not happen in real life) */ - struct aws_array_list any_published_messages; /* list of struct received_publish_packet */ - size_t any_publishes_received; - size_t expected_any_publishes; - /* the published messages from mock server, that you did subscribe to. */ - struct aws_array_list published_messages; /* list of struct received_publish_packet */ - size_t publishes_received; - size_t expected_publishes; - /* The returned QoS from mock server */ - struct aws_array_list qos_returned; /* list of uint_8 */ - size_t ops_completed; - size_t expected_ops_completed; - size_t connection_close_calls; /* All of the times on_connection_closed has been called */ - - size_t connection_termination_calls; /* How many times on_connection_termination has been called, should be 1 */ -}; - static struct mqtt_connection_state_test test_data = {0}; -static void s_on_any_publish_received( - struct aws_mqtt_client_connection *connection, - const struct aws_byte_cursor *topic, - const struct aws_byte_cursor *payload, - bool dup, - enum aws_mqtt_qos qos, - bool retain, - void *userdata); - -static void s_on_incoming_channel_setup_fn( - struct aws_server_bootstrap *bootstrap, - int error_code, - struct aws_channel *channel, - void *user_data) { - (void)bootstrap; - struct mqtt_connection_state_test *state_test_data = user_data; - - state_test_data->error = error_code; - - if (!error_code) { - aws_mutex_lock(&state_test_data->lock); - state_test_data->server_disconnect_completed = false; - aws_mutex_unlock(&state_test_data->lock); - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "server channel setup completed"); - - state_test_data->server_channel = channel; - struct aws_channel_slot *test_handler_slot = aws_channel_slot_new(channel); - aws_channel_slot_insert_end(channel, test_handler_slot); - mqtt_mock_server_handler_update_slot(state_test_data->mock_server, test_handler_slot); - aws_channel_slot_set_handler(test_handler_slot, state_test_data->mock_server); - } -} - -static void s_on_incoming_channel_shutdown_fn( - struct aws_server_bootstrap *bootstrap, - int error_code, - struct aws_channel *channel, - void *user_data) { - (void)bootstrap; - (void)error_code; - (void)channel; - struct mqtt_connection_state_test *state_test_data = user_data; - aws_mutex_lock(&state_test_data->lock); - state_test_data->server_disconnect_completed = true; - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "server channel shutdown completed"); - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static void s_on_listener_destroy(struct aws_server_bootstrap *bootstrap, void *user_data) { - (void)bootstrap; - struct mqtt_connection_state_test *state_test_data = user_data; - aws_mutex_lock(&state_test_data->lock); - state_test_data->listener_destroyed = true; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_listener_destroyed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->listener_destroyed; -} - -static void s_wait_on_listener_cleanup(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_listener_destroyed, state_test_data); - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_connection_interrupted(struct aws_mqtt_client_connection *connection, int error_code, void *userdata) { - (void)connection; - (void)error_code; - struct mqtt_connection_state_test *state_test_data = userdata; - - aws_mutex_lock(&state_test_data->lock); - state_test_data->connection_interrupted = true; - state_test_data->interruption_error = error_code; - aws_mutex_unlock(&state_test_data->lock); - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "connection interrupted"); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_connection_interrupted(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_interrupted; -} - -static void s_wait_for_interrupt_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_interrupted, state_test_data); - state_test_data->connection_interrupted = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_connection_resumed( - struct aws_mqtt_client_connection *connection, - enum aws_mqtt_connect_return_code return_code, - bool session_present, - void *userdata) { - (void)connection; - (void)return_code; - (void)session_present; - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "reconnect completed"); - - struct mqtt_connection_state_test *state_test_data = userdata; - - aws_mutex_lock(&state_test_data->lock); - state_test_data->connection_resumed = true; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_connection_resumed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_resumed; -} - -static void s_wait_for_reconnect_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_resumed, state_test_data); - state_test_data->connection_resumed = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_connection_success( - struct aws_mqtt_client_connection *connection, - enum aws_mqtt_connect_return_code return_code, - bool session_present, - void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - aws_mutex_lock(&state_test_data->lock); - - state_test_data->session_present = session_present; - state_test_data->mqtt_return_code = return_code; - state_test_data->connection_success = true; - aws_mutex_unlock(&state_test_data->lock); - - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static void s_on_connection_failure(struct aws_mqtt_client_connection *connection, int error_code, void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - aws_mutex_lock(&state_test_data->lock); - - state_test_data->error = error_code; - state_test_data->connection_failure = true; - aws_mutex_unlock(&state_test_data->lock); - - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_connection_succeed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_success; -} - -static bool s_is_connection_failed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_failure; -} - -static void s_wait_for_connection_to_succeed(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_succeed, state_test_data); - state_test_data->connection_success = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_wait_for_connection_to_fail(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_failed, state_test_data); - state_test_data->connection_failure = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static bool s_is_termination_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_terminated; -} - -static void s_wait_for_termination_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_termination_completed, state_test_data); - state_test_data->connection_terminated = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_connection_termination_fn(void *userdata) { - struct mqtt_connection_state_test *state_test_data = (struct mqtt_connection_state_test *)userdata; - - aws_mutex_lock(&state_test_data->lock); - state_test_data->connection_termination_calls += 1; - state_test_data->connection_terminated = true; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -/** sets up a unix domain socket server and socket options. Creates an mqtt connection configured to use - * the domain socket. - */ static int s_setup_mqtt_server_fn(struct aws_allocator *allocator, void *ctx) { - aws_mqtt_library_init(allocator); - - struct mqtt_connection_state_test *state_test_data = ctx; - - AWS_ZERO_STRUCT(*state_test_data); - - state_test_data->allocator = allocator; - state_test_data->el_group = aws_event_loop_group_new_default(allocator, 1, NULL); - - state_test_data->mock_server = new_mqtt_mock_server(allocator); - ASSERT_NOT_NULL(state_test_data->mock_server); - - state_test_data->server_bootstrap = aws_server_bootstrap_new(allocator, state_test_data->el_group); - ASSERT_NOT_NULL(state_test_data->server_bootstrap); - - struct aws_socket_options socket_options = { - .connect_timeout_ms = 100, - .domain = AWS_SOCKET_LOCAL, - }; - - state_test_data->socket_options = socket_options; - ASSERT_SUCCESS(aws_condition_variable_init(&state_test_data->cvar)); - ASSERT_SUCCESS(aws_mutex_init(&state_test_data->lock)); - - aws_socket_endpoint_init_local_address_for_test(&state_test_data->endpoint); - - struct aws_server_socket_channel_bootstrap_options server_bootstrap_options = { - .bootstrap = state_test_data->server_bootstrap, - .host_name = state_test_data->endpoint.address, - .port = state_test_data->endpoint.port, - .socket_options = &state_test_data->socket_options, - .incoming_callback = s_on_incoming_channel_setup_fn, - .shutdown_callback = s_on_incoming_channel_shutdown_fn, - .destroy_callback = s_on_listener_destroy, - .user_data = state_test_data, - }; - state_test_data->listener = aws_server_bootstrap_new_socket_listener(&server_bootstrap_options); - - ASSERT_NOT_NULL(state_test_data->listener); - - struct aws_host_resolver_default_options resolver_options = { - .el_group = state_test_data->el_group, - .max_entries = 1, - }; - state_test_data->host_resolver = aws_host_resolver_new_default(allocator, &resolver_options); - - struct aws_client_bootstrap_options bootstrap_options = { - .event_loop_group = state_test_data->el_group, - .user_data = state_test_data, - .host_resolver = state_test_data->host_resolver, - }; - - state_test_data->client_bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); - - state_test_data->mqtt_client = aws_mqtt_client_new(allocator, state_test_data->client_bootstrap); - state_test_data->mqtt_connection = aws_mqtt_client_connection_new(state_test_data->mqtt_client); - ASSERT_NOT_NULL(state_test_data->mqtt_connection); - - ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_interruption_handlers( - state_test_data->mqtt_connection, - s_on_connection_interrupted, - state_test_data, - s_on_connection_resumed, - state_test_data)); - - ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_result_handlers( - state_test_data->mqtt_connection, - s_on_connection_success, - state_test_data, - s_on_connection_failure, - state_test_data)); - - ASSERT_SUCCESS(aws_mqtt_client_connection_set_on_any_publish_handler( - state_test_data->mqtt_connection, s_on_any_publish_received, state_test_data)); - - ASSERT_SUCCESS(aws_array_list_init_dynamic( - &state_test_data->published_messages, allocator, 4, sizeof(struct received_publish_packet))); - ASSERT_SUCCESS(aws_array_list_init_dynamic( - &state_test_data->any_published_messages, allocator, 4, sizeof(struct received_publish_packet))); - ASSERT_SUCCESS(aws_array_list_init_dynamic(&state_test_data->qos_returned, allocator, 2, sizeof(uint8_t))); - - ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_termination_handler( - state_test_data->mqtt_connection, s_on_connection_termination_fn, state_test_data)); - - return AWS_OP_SUCCESS; -} - -static void s_received_publish_packet_list_clean_up(struct aws_array_list *list) { - for (size_t i = 0; i < aws_array_list_length(list); ++i) { - struct received_publish_packet *val_ptr = NULL; - aws_array_list_get_at_ptr(list, (void **)&val_ptr, i); - aws_byte_buf_clean_up(&val_ptr->payload); - aws_byte_buf_clean_up(&val_ptr->topic); - } - aws_array_list_clean_up(list); + return aws_test311_setup_mqtt_server_fn(allocator, ctx); } static int s_clean_up_mqtt_server_fn(struct aws_allocator *allocator, int setup_result, void *ctx) { - (void)allocator; - - if (!setup_result) { - struct mqtt_connection_state_test *state_test_data = ctx; - - s_received_publish_packet_list_clean_up(&state_test_data->published_messages); - s_received_publish_packet_list_clean_up(&state_test_data->any_published_messages); - aws_array_list_clean_up(&state_test_data->qos_returned); - aws_mqtt_client_connection_release(state_test_data->mqtt_connection); - - s_wait_for_termination_to_complete(state_test_data); - ASSERT_UINT_EQUALS(1, state_test_data->connection_termination_calls); - - aws_mqtt_client_release(state_test_data->mqtt_client); - aws_client_bootstrap_release(state_test_data->client_bootstrap); - aws_host_resolver_release(state_test_data->host_resolver); - aws_server_bootstrap_destroy_socket_listener(state_test_data->server_bootstrap, state_test_data->listener); - s_wait_on_listener_cleanup(state_test_data); - aws_server_bootstrap_release(state_test_data->server_bootstrap); - aws_event_loop_group_release(state_test_data->el_group); - destroy_mqtt_mock_server(state_test_data->mock_server); - } - - aws_mqtt_library_clean_up(); - return AWS_OP_SUCCESS; -} - -static void s_on_connection_complete_fn( - struct aws_mqtt_client_connection *connection, - int error_code, - enum aws_mqtt_connect_return_code return_code, - bool session_present, - void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - aws_mutex_lock(&state_test_data->lock); - - state_test_data->session_present = session_present; - state_test_data->mqtt_return_code = return_code; - state_test_data->error = error_code; - state_test_data->connection_completed = true; - aws_mutex_unlock(&state_test_data->lock); - - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_connection_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->connection_completed; -} - -static void s_wait_for_connection_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_connection_completed, state_test_data); - state_test_data->connection_completed = false; - aws_mutex_unlock(&state_test_data->lock); -} - -void s_on_disconnect_fn(struct aws_mqtt_client_connection *connection, void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "disconnect completed"); - aws_mutex_lock(&state_test_data->lock); - state_test_data->client_disconnect_completed = true; - aws_mutex_unlock(&state_test_data->lock); - - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_disconnect_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->client_disconnect_completed && state_test_data->server_disconnect_completed; -} - -static void s_wait_for_disconnect_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_disconnect_completed, state_test_data); - state_test_data->client_disconnect_completed = false; - state_test_data->server_disconnect_completed = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_any_publish_received( - struct aws_mqtt_client_connection *connection, - const struct aws_byte_cursor *topic, - const struct aws_byte_cursor *payload, - bool dup, - enum aws_mqtt_qos qos, - bool retain, - void *userdata) { - (void)connection; - struct mqtt_connection_state_test *state_test_data = userdata; - - struct aws_byte_buf payload_cp; - aws_byte_buf_init_copy_from_cursor(&payload_cp, state_test_data->allocator, *payload); - struct aws_byte_buf topic_cp; - aws_byte_buf_init_copy_from_cursor(&topic_cp, state_test_data->allocator, *topic); - struct received_publish_packet received_packet = { - .payload = payload_cp, - .topic = topic_cp, - .dup = dup, - .qos = qos, - .retain = retain, - }; - - aws_mutex_lock(&state_test_data->lock); - aws_array_list_push_back(&state_test_data->any_published_messages, &received_packet); - state_test_data->any_publishes_received++; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_any_publish_received(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->any_publishes_received == state_test_data->expected_any_publishes; -} - -static void s_wait_for_any_publish(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_any_publish_received, state_test_data); - state_test_data->any_publishes_received = 0; - state_test_data->expected_any_publishes = 0; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_publish_received( - struct aws_mqtt_client_connection *connection, - const struct aws_byte_cursor *topic, - const struct aws_byte_cursor *payload, - bool dup, - enum aws_mqtt_qos qos, - bool retain, - void *userdata) { - - (void)connection; - (void)topic; - struct mqtt_connection_state_test *state_test_data = userdata; - - struct aws_byte_buf payload_cp; - aws_byte_buf_init_copy_from_cursor(&payload_cp, state_test_data->allocator, *payload); - struct aws_byte_buf topic_cp; - aws_byte_buf_init_copy_from_cursor(&topic_cp, state_test_data->allocator, *topic); - struct received_publish_packet received_packet = { - .payload = payload_cp, - .topic = topic_cp, - .dup = dup, - .qos = qos, - .retain = retain, - }; - - aws_mutex_lock(&state_test_data->lock); - aws_array_list_push_back(&state_test_data->published_messages, &received_packet); - state_test_data->publishes_received++; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_publish_received(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->publishes_received == state_test_data->expected_publishes; -} - -static void s_wait_for_publish(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_publish_received, state_test_data); - state_test_data->publishes_received = 0; - state_test_data->expected_publishes = 0; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_suback( - struct aws_mqtt_client_connection *connection, - uint16_t packet_id, - const struct aws_byte_cursor *topic, - enum aws_mqtt_qos qos, - int error_code, - void *userdata) { - (void)connection; - (void)packet_id; - (void)topic; - - struct mqtt_connection_state_test *state_test_data = userdata; - - aws_mutex_lock(&state_test_data->lock); - if (!error_code) { - aws_array_list_push_back(&state_test_data->qos_returned, &qos); - } - state_test_data->subscribe_completed = true; - state_test_data->subscribe_complete_error = error_code; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_subscribe_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->subscribe_completed; -} - -static void s_wait_for_subscribe_to_complete(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_pred( - &state_test_data->cvar, &state_test_data->lock, s_is_subscribe_completed, state_test_data); - state_test_data->subscribe_completed = false; - aws_mutex_unlock(&state_test_data->lock); -} - -static void s_on_multi_suback( - struct aws_mqtt_client_connection *connection, - uint16_t packet_id, - const struct aws_array_list *topic_subacks, /* contains aws_mqtt_topic_subscription pointers */ - int error_code, - void *userdata) { - (void)connection; - (void)packet_id; - (void)topic_subacks; - (void)error_code; - - struct mqtt_connection_state_test *state_test_data = userdata; - - aws_mutex_lock(&state_test_data->lock); - state_test_data->subscribe_completed = true; - if (!error_code) { - size_t length = aws_array_list_length(topic_subacks); - for (size_t i = 0; i < length; ++i) { - struct aws_mqtt_topic_subscription *subscription = NULL; - aws_array_list_get_at(topic_subacks, &subscription, i); - aws_array_list_push_back(&state_test_data->qos_returned, &subscription->qos); - } - } - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static void s_on_op_complete( - struct aws_mqtt_client_connection *connection, - uint16_t packet_id, - int error_code, - void *userdata) { - (void)connection; - (void)packet_id; - - struct mqtt_connection_state_test *state_test_data = userdata; - AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "pub op completed"); - aws_mutex_lock(&state_test_data->lock); - state_test_data->ops_completed++; - state_test_data->op_complete_error = error_code; - aws_mutex_unlock(&state_test_data->lock); - aws_condition_variable_notify_one(&state_test_data->cvar); -} - -static bool s_is_ops_completed(void *arg) { - struct mqtt_connection_state_test *state_test_data = arg; - return state_test_data->ops_completed == state_test_data->expected_ops_completed; -} - -static void s_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data) { - aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_for_pred( - &state_test_data->cvar, &state_test_data->lock, 10000000000, s_is_ops_completed, state_test_data); - aws_mutex_unlock(&state_test_data->lock); + return aws_test311_clean_up_mqtt_server_fn(allocator, setup_result, ctx); } /* @@ -673,14 +34,14 @@ static int s_test_mqtt_connect_disconnect_fn(struct aws_allocator *allocator, vo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -730,14 +91,14 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -766,7 +127,7 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator /* Connect to the mock server again. If set will&loggin message is not called before the next connect, the * will&loggin message will still be there and be sent to the server again */ ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); /* The second CONNECT packet */ @@ -784,9 +145,9 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator ASSERT_TRUE(aws_byte_cursor_eq(&received_packet->password, &password)); /* disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* set new will & loggin message, before next connect, the next CONNECT packet will contain the new information */ struct aws_byte_cursor new_will_payload = aws_byte_cursor_from_c_str("this is a new will."); @@ -803,7 +164,7 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator /* connect again */ ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); /* The third CONNECT packet */ @@ -821,9 +182,9 @@ static int s_test_mqtt_connect_set_will_login_fn(struct aws_allocator *allocator ASSERT_TRUE(aws_byte_cursor_eq(&received_packet->password, &new_password)); /* disconnect. FINISHED */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -852,14 +213,14 @@ static int s_test_mqtt_connection_interrupted_fn(struct aws_allocator *allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; aws_mqtt_client_connection_set_reconnect_timeout( state_test_data->mqtt_connection, MIN_RECONNECT_DELAY_SECONDS, MAX_RECONNECT_DELAY_SECONDS); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* shut it down and make sure the client automatically reconnects.*/ uint64_t now = 0; @@ -867,7 +228,7 @@ static int s_test_mqtt_connection_interrupted_fn(struct aws_allocator *allocator uint64_t start_shutdown = now; aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&now); uint64_t reconnect_complete = now; @@ -877,9 +238,9 @@ static int s_test_mqtt_connection_interrupted_fn(struct aws_allocator *allocator aws_timestamp_convert(elapsed_time, AWS_TIMESTAMP_NANOS, AWS_TIMESTAMP_SECS, NULL) >= MIN_RECONNECT_DELAY_SECONDS); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -922,21 +283,21 @@ static int s_test_mqtt_connection_timeout_fn(struct aws_allocator *allocator, vo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, }; mqtt_mock_server_set_max_ping_resp(state_test_data->mock_server, 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* this should take about 1.1 seconds for the timeout and reconnect.*/ - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_TIMEOUT, state_test_data->interruption_error); @@ -962,14 +323,14 @@ static int s_test_mqtt_connection_any_publish_fn(struct aws_allocator *allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor topic_1 = aws_byte_cursor_from_c_str("/test/topic1"); struct aws_byte_cursor topic_2 = aws_byte_cursor_from_c_str("/test/topic2"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* NOTE: mock server sends to client with no subscription at all, which should not happen in the real world! */ state_test_data->expected_any_publishes = 2; @@ -990,12 +351,12 @@ static int s_test_mqtt_connection_any_publish_fn(struct aws_allocator *allocator AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1048,14 +409,14 @@ static int s_test_mqtt_connection_connack_timeout_fn(struct aws_allocator *alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, }; mqtt_mock_server_set_max_connack(state_test_data->mock_server, 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_TIMEOUT, state_test_data->error); @@ -1080,14 +441,14 @@ static int s_test_mqtt_connection_failure_callback_fn(struct aws_allocator *allo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, }; mqtt_mock_server_set_max_connack(state_test_data->mock_server, 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_fail(state_test_data); + aws_test311_wait_for_connection_to_fail(state_test_data); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_TIMEOUT, state_test_data->error); @@ -1112,11 +473,11 @@ static int s_test_mqtt_connection_success_callback_fn(struct aws_allocator *allo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_succeed(state_test_data); + aws_test311_wait_for_connection_to_succeed(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1129,9 +490,9 @@ static int s_test_mqtt_connection_success_callback_fn(struct aws_allocator *allo ASSERT_TRUE(aws_byte_cursor_eq(&received_packet->client_identifier, &connection_options.client_id)); // Disconnect and finish - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -1155,7 +516,7 @@ static int s_test_mqtt_subscribe_fn(struct aws_allocator *allocator, void *ctx) .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor sub_topic = aws_byte_cursor_from_c_str("/test/topic"); @@ -1164,17 +525,17 @@ static int s_test_mqtt_subscribe_fn(struct aws_allocator *allocator, void *ctx) state_test_data->mqtt_connection, &sub_topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); state_test_data->expected_publishes = 2; state_test_data->expected_any_publishes = 2; @@ -1195,13 +556,13 @@ static int s_test_mqtt_subscribe_fn(struct aws_allocator *allocator, void *ctx) AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_publish(state_test_data); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1282,7 +643,7 @@ static int s_test_mqtt_subscribe_incoming_dup_fn(struct aws_allocator *allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor subscribed_topic = aws_byte_cursor_from_c_str("/test/topic"); @@ -1292,16 +653,16 @@ static int s_test_mqtt_subscribe_incoming_dup_fn(struct aws_allocator *allocator state_test_data->mqtt_connection, &subscribed_topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); state_test_data->expected_publishes = 4; state_test_data->expected_any_publishes = 8; @@ -1330,13 +691,13 @@ static int s_test_mqtt_subscribe_incoming_dup_fn(struct aws_allocator *allocator false /*retain*/)); } - s_wait_for_publish(state_test_data); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 8); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1419,11 +780,11 @@ static int s_test_mqtt_connect_subscribe_fail_from_broker_fn(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, we will send failure SUBACK */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -1434,15 +795,15 @@ static int s_test_mqtt_connect_subscribe_fail_from_broker_fn(struct aws_allocato state_test_data->mqtt_connection, &sub_topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id > 0); ASSERT_SUCCESS(mqtt_mock_server_send_single_suback(state_test_data->mock_server, packet_id, AWS_MQTT_QOS_FAILURE)); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); /* Check the subscribe returned QoS is failure */ size_t length = aws_array_list_length(&state_test_data->qos_returned); ASSERT_UINT_EQUALS(1, length); @@ -1450,9 +811,9 @@ static int s_test_mqtt_connect_subscribe_fail_from_broker_fn(struct aws_allocato ASSERT_SUCCESS(aws_array_list_get_at(&state_test_data->qos_returned, &qos, 0)); ASSERT_UINT_EQUALS(AWS_MQTT_QOS_FAILURE, qos); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -1476,7 +837,7 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor sub_topic_1 = aws_byte_cursor_from_c_str("/test/topic1"); @@ -1485,14 +846,14 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void struct aws_mqtt_topic_subscription sub1 = { .topic = sub_topic_1, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; struct aws_mqtt_topic_subscription sub2 = { .topic = sub_topic_2, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; @@ -1506,13 +867,13 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void aws_array_list_push_back(&topic_filters, &sub2); uint16_t packet_id = aws_mqtt_client_connection_subscribe_multiple( - state_test_data->mqtt_connection, &topic_filters, s_on_multi_suback, state_test_data); + state_test_data->mqtt_connection, &topic_filters, aws_test311_on_multi_suback, state_test_data); ASSERT_TRUE(packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); /* Check the subscribe returned QoS is expected */ size_t length = aws_array_list_length(&state_test_data->qos_returned); ASSERT_UINT_EQUALS(2, length); @@ -1539,7 +900,7 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void false /*dup*/, AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_publish(state_test_data); + aws_test311_wait_for_publish(state_test_data); /* Let's do another publish on a topic that is not subscribed by client. * This can happen if the Server automatically assigned a subscription to the Client */ @@ -1553,13 +914,13 @@ static int s_test_mqtt_subscribe_multi_fn(struct aws_allocator *allocator, void false /*dup*/, AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 3); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1627,7 +988,7 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor sub_topic_1 = aws_byte_cursor_from_c_str("/test/topic1"); @@ -1636,14 +997,14 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx struct aws_mqtt_topic_subscription sub1 = { .topic = sub_topic_1, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; struct aws_mqtt_topic_subscription sub2 = { .topic = sub_topic_2, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; @@ -1657,13 +1018,13 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx aws_array_list_push_back(&topic_filters, &sub2); uint16_t sub_packet_id = aws_mqtt_client_connection_subscribe_multiple( - state_test_data->mqtt_connection, &topic_filters, s_on_multi_suback, state_test_data); + state_test_data->mqtt_connection, &topic_filters, aws_test311_on_multi_suback, state_test_data); ASSERT_TRUE(sub_packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); state_test_data->expected_any_publishes = 2; struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); @@ -1682,7 +1043,7 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx false /*dup*/, AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); aws_mutex_lock(&state_test_data->lock); @@ -1690,7 +1051,7 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx aws_mutex_unlock(&state_test_data->lock); /* unsubscribe to the first topic */ uint16_t unsub_packet_id = aws_mqtt_client_connection_unsubscribe( - state_test_data->mqtt_connection, &sub_topic_1, s_on_op_complete, state_test_data); + state_test_data->mqtt_connection, &sub_topic_1, aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(unsub_packet_id > 0); /* Even when the UNSUBACK has not received, the client will not invoke the on_pub callback for that topic */ ASSERT_SUCCESS(mqtt_mock_server_send_publish( @@ -1708,13 +1069,13 @@ static int s_test_mqtt_unsubscribe_fn(struct aws_allocator *allocator, void *ctx AWS_MQTT_QOS_AT_LEAST_ONCE, false /*retain*/)); state_test_data->expected_any_publishes = 2; - s_wait_for_any_publish(state_test_data); + aws_test311_wait_for_any_publish(state_test_data); mqtt_mock_server_wait_for_pubacks(state_test_data->mock_server, 2); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1797,7 +1158,7 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor sub_topic_1 = aws_byte_cursor_from_c_str("/test/topic1"); @@ -1807,21 +1168,21 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx struct aws_mqtt_topic_subscription sub1 = { .topic = sub_topic_1, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; struct aws_mqtt_topic_subscription sub2 = { .topic = sub_topic_2, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; struct aws_mqtt_topic_subscription sub3 = { .topic = sub_topic_3, .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, - .on_publish = s_on_publish_received, + .on_publish = aws_test311_on_publish_received, .on_cleanup = NULL, .on_publish_ud = state_test_data, }; @@ -1836,31 +1197,31 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx aws_array_list_push_back(&topic_filters, &sub3); uint16_t sub_packet_id = aws_mqtt_client_connection_subscribe_multiple( - state_test_data->mqtt_connection, &topic_filters, s_on_multi_suback, state_test_data); + state_test_data->mqtt_connection, &topic_filters, aws_test311_on_multi_suback, state_test_data); ASSERT_TRUE(sub_packet_id > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 1; aws_mutex_unlock(&state_test_data->lock); /* unsubscribe to the first topic */ uint16_t unsub_packet_id = aws_mqtt_client_connection_unsubscribe( - state_test_data->mqtt_connection, &sub_topic_1, s_on_op_complete, state_test_data); + state_test_data->mqtt_connection, &sub_topic_1, aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(unsub_packet_id > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* client still subscribes to topic_2 & topic_3 */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* reconnection to the same server */ ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Get all the packets out of the way */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -1870,14 +1231,14 @@ static int s_test_mqtt_resubscribe_fn(struct aws_allocator *allocator, void *ctx ASSERT_UINT_EQUALS(AWS_MQTT_PACKET_CONNECT, t_received_packet->type); /* resubscribes to topic_2 & topic_3 */ - uint16_t resub_packet_id = - aws_mqtt_resubscribe_existing_topics(state_test_data->mqtt_connection, s_on_multi_suback, state_test_data); + uint16_t resub_packet_id = aws_mqtt_resubscribe_existing_topics( + state_test_data->mqtt_connection, aws_test311_on_multi_suback, state_test_data); ASSERT_TRUE(resub_packet_id > 0); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); struct mqtt_decoded_packet *received_packet = @@ -1915,7 +1276,7 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); @@ -1923,7 +1284,7 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { struct aws_byte_cursor payload_2 = aws_byte_cursor_from_c_str("Test Message 2"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 3; @@ -1934,7 +1295,7 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); uint16_t packet_id_2 = aws_mqtt_client_connection_publish( @@ -1943,7 +1304,7 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_2, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_2 > 0); @@ -1954,15 +1315,15 @@ static int s_test_mqtt_publish_fn(struct aws_allocator *allocator, void *ctx) { AWS_MQTT_QOS_AT_LEAST_ONCE, false, NULL, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_3 > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -2012,7 +1373,7 @@ static int s_test_mqtt_publish_payload_fn(struct aws_allocator *allocator, void .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); @@ -2022,7 +1383,7 @@ static int s_test_mqtt_publish_payload_fn(struct aws_allocator *allocator, void struct aws_byte_cursor payload_curser = aws_byte_cursor_from_buf(&buf_payload); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 1; @@ -2033,17 +1394,17 @@ static int s_test_mqtt_publish_payload_fn(struct aws_allocator *allocator, void AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_curser, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id > 0); /* clean up the payload buf, as user don't need to manage the buf and keep it valid until publish completes */ aws_byte_buf_clean_up(&buf_payload); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -2087,19 +1448,19 @@ static int s_test_mqtt_connection_offline_publish_fn(struct aws_allocator *alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); mqtt_mock_server_set_max_connack(state_test_data->mock_server, 0); /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_interrupt_to_complete(state_test_data); + aws_test311_wait_for_interrupt_to_complete(state_test_data); state_test_data->server_disconnect_completed = false; @@ -2118,7 +1479,7 @@ static int s_test_mqtt_connection_offline_publish_fn(struct aws_allocator *alloc AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_publish( @@ -2127,21 +1488,21 @@ static int s_test_mqtt_connection_offline_publish_fn(struct aws_allocator *alloc AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_2, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); aws_mutex_lock(&state_test_data->lock); ASSERT_FALSE(state_test_data->connection_resumed); aws_mutex_unlock(&state_test_data->lock); mqtt_mock_server_set_max_connack(state_test_data->mock_server, SIZE_MAX); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); aws_mutex_lock(&state_test_data->lock); ASSERT_TRUE(state_test_data->connection_resumed); aws_mutex_unlock(&state_test_data->lock); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Decode all received packets by mock server */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); @@ -2205,19 +1566,19 @@ static int s_test_mqtt_connection_disconnect_while_reconnecting(struct aws_alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); mqtt_mock_server_set_max_connack(state_test_data->mock_server, 0); /* shut it down and the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_interrupt_to_complete(state_test_data); + aws_test311_wait_for_interrupt_to_complete(state_test_data); struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); @@ -2234,7 +1595,7 @@ static int s_test_mqtt_connection_disconnect_while_reconnecting(struct aws_alloc AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_publish( @@ -2243,15 +1604,15 @@ static int s_test_mqtt_connection_disconnect_while_reconnecting(struct aws_alloc AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_2, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); aws_mqtt_client_connection_release(state_test_data->mqtt_connection); state_test_data->mqtt_connection = NULL; - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); return AWS_OP_SUCCESS; } @@ -2281,7 +1642,7 @@ static int s_test_mqtt_connection_closes_while_making_requests_fn(struct aws_all .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; @@ -2290,7 +1651,7 @@ static int s_test_mqtt_connection_closes_while_making_requests_fn(struct aws_all struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 1; @@ -2310,16 +1671,16 @@ static int s_test_mqtt_connection_closes_while_making_requests_fn(struct aws_all AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); - s_wait_for_reconnect_to_complete(state_test_data); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2381,7 +1742,7 @@ static int s_test_mqtt_connection_resend_packets_fn(struct aws_allocator *alloca .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; @@ -2393,7 +1754,7 @@ static int s_test_mqtt_connection_resend_packets_fn(struct aws_allocator *alloca struct aws_byte_cursor payload_3 = aws_byte_cursor_from_c_str("Test Message 3"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -2408,10 +1769,10 @@ static int s_test_mqtt_connection_resend_packets_fn(struct aws_allocator *alloca state_test_data->mqtt_connection, &sub_topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_ids[1] > 0); @@ -2435,15 +1796,15 @@ static int s_test_mqtt_connection_resend_packets_fn(struct aws_allocator *alloca /* shutdown the channel for some error */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* Wait again, and ensure the publishes have been resent */ aws_thread_current_sleep(ONE_SEC); ASSERT_SUCCESS(s_check_resend_packets( state_test_data->mock_server, packet_count, true, packet_ids, AWS_ARRAY_SIZE(packet_ids))); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2468,7 +1829,7 @@ static int s_test_mqtt_connection_not_retry_publish_QoS_0_fn(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ }; @@ -2477,7 +1838,7 @@ static int s_test_mqtt_connection_not_retry_publish_QoS_0_fn(struct aws_allocato struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* kill the connection */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); @@ -2493,23 +1854,23 @@ static int s_test_mqtt_connection_not_retry_publish_QoS_0_fn(struct aws_allocato AWS_MQTT_QOS_AT_MOST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); /* publish should complete after the shutdown */ - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* wait for reconnect */ - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* Check all received packets, no publish packets ever received by the server. Because the connection lost before it * ever get sent. */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(state_test_data->mock_server)); ASSERT_NULL( mqtt_mock_server_find_decoded_packet_by_type(state_test_data->mock_server, 0, AWS_MQTT_PACKET_PUBLISH, NULL)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2535,7 +1896,7 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .protocol_operation_timeout_ms = 3000, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ @@ -2546,7 +1907,7 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato struct aws_byte_cursor sub_topic = aws_byte_cursor_from_c_str("/test/topic"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ @@ -2563,7 +1924,7 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); /* make another subscribe */ @@ -2574,12 +1935,12 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato NULL, NULL, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id_2 > 0); /* wait for reconnect */ - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* Wait for 1 sec. ensure all the requests have been received by the server */ aws_thread_current_sleep(ONE_SEC); @@ -2594,9 +1955,9 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato mqtt_mock_server_enable_auto_ack(handler); /* Kill the connection again, the requests will be retried since the response has not been received yet. */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* subscribe should be able to completed now */ - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); /* Check all received packets, subscribe and publish has been resent */ ASSERT_SUCCESS(mqtt_mock_server_decode_packets(handler)); @@ -2606,9 +1967,9 @@ static int s_test_mqtt_connection_consistent_retry_policy_fn(struct aws_allocato mqtt_mock_server_find_decoded_packet_by_type(handler, packet_count, AWS_MQTT_PACKET_SUBSCRIBE, NULL)); ASSERT_NOT_NULL(mqtt_mock_server_find_decoded_packet_by_type(handler, packet_count, AWS_MQTT_PACKET_PUBLISH, NULL)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2635,7 +1996,7 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; @@ -2645,7 +2006,7 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( struct aws_byte_cursor sub_topic = aws_byte_cursor_from_c_str("/test/topic"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ @@ -2661,7 +2022,7 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); /* make another subscribe */ @@ -2672,7 +2033,7 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( NULL, NULL, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data); ASSERT_TRUE(packet_id_2 > 0); @@ -2693,9 +2054,9 @@ static int s_test_mqtt_connection_not_resend_packets_on_healthy_connection_fn( mqtt_mock_server_find_decoded_packet_by_type(handler, pre_index + 1, AWS_MQTT_PACKET_SUBSCRIBE, NULL)); } - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2723,17 +2084,17 @@ static int s_test_mqtt_connection_destory_pending_requests_fn(struct aws_allocat AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_subscribe( state_test_data->mqtt_connection, &topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data) > 0); return AWS_OP_SUCCESS; @@ -2757,13 +2118,13 @@ static int s_test_mqtt_clean_session_not_retry_fn(struct aws_allocator *allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; mqtt_mock_server_disable_auto_ack(handler); @@ -2777,33 +2138,33 @@ static int s_test_mqtt_clean_session_not_retry_fn(struct aws_allocator *allocato AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_subscribe( state_test_data->mqtt_connection, &topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data) > 0); aws_mutex_lock(&state_test_data->lock); state_test_data->expected_ops_completed = 1; aws_mutex_unlock(&state_test_data->lock); /* Shutdown the connection */ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_interrupt_to_complete(state_test_data); + aws_test311_wait_for_interrupt_to_complete(state_test_data); /* Once the connection lost, the requests will fail */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION); ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2826,7 +2187,7 @@ static int s_test_mqtt_clean_session_discard_previous_fn(struct aws_allocator *a .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ }; @@ -2844,25 +2205,25 @@ static int s_test_mqtt_clean_session_discard_previous_fn(struct aws_allocator *a AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_subscribe( state_test_data->mqtt_connection, &topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data) > 0); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; - s_wait_for_ops_completed(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION); ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION); @@ -2872,9 +2233,9 @@ static int s_test_mqtt_clean_session_discard_previous_fn(struct aws_allocator *a ASSERT_NULL(mqtt_mock_server_find_decoded_packet_by_type(handler, 0, AWS_MQTT_PACKET_SUBSCRIBE, NULL)); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2896,7 +2257,7 @@ static int s_test_mqtt_clean_session_keep_next_session_fn(struct aws_allocator * .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ }; @@ -2916,23 +2277,23 @@ static int s_test_mqtt_clean_session_keep_next_session_fn(struct aws_allocator * AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data) > 0); ASSERT_TRUE( aws_mqtt_client_connection_subscribe( state_test_data->mqtt_connection, &topic, AWS_MQTT_QOS_AT_LEAST_ONCE, - s_on_publish_received, + aws_test311_on_publish_received, state_test_data, NULL, - s_on_suback, + aws_test311_on_suback, state_test_data) > 0); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_channel_handler *handler = state_test_data->mock_server; - s_wait_for_ops_completed(state_test_data); - s_wait_for_subscribe_to_complete(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_subscribe_to_complete(state_test_data); ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_SUCCESS); ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_SUCCESS); @@ -2942,9 +2303,9 @@ static int s_test_mqtt_clean_session_keep_next_session_fn(struct aws_allocator * ASSERT_NOT_NULL(mqtt_mock_server_find_decoded_packet_by_type(handler, 0, AWS_MQTT_PACKET_SUBSCRIBE, NULL)); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -2968,7 +2329,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_fn(struct aws_allocator * .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .protocol_operation_timeout_ms = 3000, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ @@ -2978,7 +2339,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_fn(struct aws_allocator * struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -2993,17 +2354,17 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_fn(struct aws_allocator * AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); /* publish should complete after the shutdown */ - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Check the publish has been completed with timeout error */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_TIMEOUT); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3028,7 +2389,7 @@ static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocat .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .protocol_operation_timeout_ms = 3000, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ @@ -3037,7 +2398,7 @@ static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocat struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -3048,16 +2409,16 @@ static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocat /* unsubscribe to the first topic */ uint16_t unsub_packet_id = aws_mqtt_client_connection_unsubscribe( - state_test_data->mqtt_connection, &pub_topic, s_on_op_complete, state_test_data); + state_test_data->mqtt_connection, &pub_topic, aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(unsub_packet_id > 0); /* publish should complete after the shutdown */ - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Check the publish has been completed with timeout error */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_TIMEOUT); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3084,7 +2445,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, .protocol_operation_timeout_ms = 3000, .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ @@ -3094,7 +2455,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); @@ -3109,7 +2470,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); @@ -3117,7 +2478,7 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim aws_thread_current_sleep((uint64_t)ONE_SEC * 2); /* Kill the connection, the requests will be retried and the timeout will be reset. */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* sleep for 2 sec again, in total the response has not received for more than 4 sec, timeout should happen if the * lost of connection not reset the timeout */ aws_thread_current_sleep((uint64_t)ONE_SEC * 2); @@ -3125,12 +2486,12 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_connection_lost_reset_tim ASSERT_SUCCESS(mqtt_mock_server_send_puback(state_test_data->mock_server, packet_id_1)); /* publish should complete after the shutdown */ - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Check the publish has been completed successfully since the lost of the connection reset the timeout */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_SUCCESS); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3170,21 +2531,21 @@ static int s_test_mqtt_connection_close_callback_simple_fn(struct aws_allocator .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; aws_mqtt_client_connection_set_connection_closed_handler( state_test_data->mqtt_connection, s_on_connection_closed_fn, state_test_data); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* sleep for 2 sec, just to make sure the connection is stable */ aws_thread_current_sleep((uint64_t)ONE_SEC * 2); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Make sure the callback was called and the value is what we expect */ ASSERT_UINT_EQUALS(1, state_test_data->connection_close_calls); @@ -3212,25 +2573,25 @@ static int s_test_mqtt_connection_close_callback_interrupted_fn(struct aws_alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; aws_mqtt_client_connection_set_connection_closed_handler( state_test_data->mqtt_connection, s_on_connection_closed_fn, state_test_data); ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Kill the connection */ aws_channel_shutdown(state_test_data->server_channel, AWS_ERROR_INVALID_STATE); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); /* sleep for 2 sec, just to make sure the connection is stable */ aws_thread_current_sleep((uint64_t)ONE_SEC * 2); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* Make sure the callback was called only ONCE and the value is what we expect */ ASSERT_UINT_EQUALS(1, state_test_data->connection_close_calls); @@ -3258,7 +2619,7 @@ static int s_test_mqtt_connection_close_callback_multi_fn(struct aws_allocator * .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; aws_mqtt_client_connection_set_connection_closed_handler( state_test_data->mqtt_connection, s_on_connection_closed_fn, state_test_data); @@ -3266,12 +2627,12 @@ static int s_test_mqtt_connection_close_callback_multi_fn(struct aws_allocator * int disconnect_amount = 10; for (int i = 0; i < disconnect_amount; i++) { ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* Disconnect */ ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( - state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); } /* Make sure the callback was called disconnect_amount times */ @@ -3299,11 +2660,11 @@ static int s_test_mqtt_connection_reconnection_backoff_stable(struct aws_allocat .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); uint64_t time_before = 0; uint64_t time_after = 0; @@ -3317,7 +2678,7 @@ static int s_test_mqtt_connection_reconnection_backoff_stable(struct aws_allocat /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); @@ -3330,9 +2691,9 @@ static int s_test_mqtt_connection_reconnection_backoff_stable(struct aws_allocat } /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3356,11 +2717,11 @@ static int s_test_mqtt_connection_reconnection_backoff_unstable(struct aws_alloc .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); uint64_t time_before = 0; uint64_t time_after = 0; @@ -3371,7 +2732,7 @@ static int s_test_mqtt_connection_reconnection_backoff_unstable(struct aws_alloc /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); @@ -3387,9 +2748,9 @@ static int s_test_mqtt_connection_reconnection_backoff_unstable(struct aws_alloc } /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3413,11 +2774,11 @@ static int s_test_mqtt_connection_reconnection_backoff_reset(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); uint64_t time_before = 0; uint64_t time_after = 0; @@ -3429,7 +2790,7 @@ static int s_test_mqtt_connection_reconnection_backoff_reset(struct aws_allocato /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); reconnection_backoff = time_after - time_before; @@ -3449,7 +2810,7 @@ static int s_test_mqtt_connection_reconnection_backoff_reset(struct aws_allocato /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); reconnection_backoff = time_after - time_before; @@ -3460,9 +2821,9 @@ static int s_test_mqtt_connection_reconnection_backoff_reset(struct aws_allocato ASSERT_TRUE(remainder <= RECONNECT_BACKOFF_DELAY_ERROR_MARGIN_NANO_SECONDS); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3488,11 +2849,11 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); uint64_t time_before = 0; uint64_t time_after = 0; @@ -3503,7 +2864,7 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection /* shut it down and make sure the client automatically reconnects.*/ aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); reconnection_backoff = time_after - time_before; @@ -3514,18 +2875,18 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection expected_reconnect_backoff = aws_min_u64(expected_reconnect_backoff * 2, 10); } /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); /* connect again */ ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_before); aws_channel_shutdown(state_test_data->server_channel, AWS_OP_SUCCESS); - s_wait_for_reconnect_to_complete(state_test_data); + aws_test311_wait_for_reconnect_to_complete(state_test_data); aws_high_res_clock_get_ticks(&time_after); reconnection_backoff = time_after - time_before; @@ -3536,9 +2897,9 @@ static int s_test_mqtt_connection_reconnection_backoff_reset_after_disconnection ASSERT_TRUE(remainder <= RECONNECT_BACKOFF_DELAY_ERROR_MARGIN_NANO_SECONDS); /* Disconnect */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3563,7 +2924,7 @@ static int s_test_mqtt_connection_ping_norm_fn(struct aws_allocator *allocator, .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 1, .ping_timeout_ms = 100, }; @@ -3576,9 +2937,9 @@ static int s_test_mqtt_connection_ping_norm_fn(struct aws_allocator *allocator, /* Ensure the server got 4 PING packets */ ASSERT_INT_EQUALS(4, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3604,13 +2965,13 @@ static int s_test_mqtt_connection_ping_no_fn(struct aws_allocator *allocator, vo .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 1, .ping_timeout_ms = 100, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); @@ -3630,7 +2991,7 @@ static int s_test_mqtt_connection_ping_no_fn(struct aws_allocator *allocator, vo AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id > 0); @@ -3645,9 +3006,9 @@ static int s_test_mqtt_connection_ping_no_fn(struct aws_allocator *allocator, vo /* Ensure the server got 0 PING packets */ ASSERT_INT_EQUALS(0, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3673,13 +3034,13 @@ static int s_test_mqtt_connection_ping_noack_fn(struct aws_allocator *allocator, .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 1, .ping_timeout_ms = 100, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); @@ -3699,7 +3060,7 @@ static int s_test_mqtt_connection_ping_noack_fn(struct aws_allocator *allocator, AWS_MQTT_QOS_AT_MOST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id > 0); @@ -3713,9 +3074,9 @@ static int s_test_mqtt_connection_ping_noack_fn(struct aws_allocator *allocator, /* Ensure the server got 4 PING packets */ ASSERT_INT_EQUALS(4, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3747,13 +3108,13 @@ static int s_test_mqtt_connection_ping_basic_scenario_fn(struct aws_allocator *a .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 4, .ping_timeout_ms = 100, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* PING will be in 4 seconds */ aws_thread_current_sleep(3000000000); /* Wait 3 seconds */ @@ -3771,10 +3132,10 @@ static int s_test_mqtt_connection_ping_basic_scenario_fn(struct aws_allocator *a AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Publish packet written at 3 seconds */ aws_thread_current_sleep(1250000000); /* Wait 1.25 second (the extra 0.25 is to account for jitter) */ @@ -3797,9 +3158,9 @@ static int s_test_mqtt_connection_ping_basic_scenario_fn(struct aws_allocator *a ASSERT_INT_EQUALS(2, mqtt_mock_server_get_ping_count(state_test_data->mock_server)); /* Disconnect and finish! */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3825,13 +3186,13 @@ static int s_test_mqtt_connection_ping_double_scenario_fn(struct aws_allocator * .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, .keep_alive_time_secs = 4, .ping_timeout_ms = 100, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); /* PING will be in 4 seconds */ aws_thread_current_sleep(3000000000); /* Wait 3 seconds */ @@ -3849,10 +3210,10 @@ static int s_test_mqtt_connection_ping_double_scenario_fn(struct aws_allocator * AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_1 > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Publish packet written at 3 seconds */ aws_thread_current_sleep(1250000000); /* Wait 1.25 second (the extra 0.25 is to account for jitter) */ @@ -3874,10 +3235,10 @@ static int s_test_mqtt_connection_ping_double_scenario_fn(struct aws_allocator * AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload_1, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(packet_id_2 > 0); - s_wait_for_ops_completed(state_test_data); + aws_test311_wait_for_ops_completed(state_test_data); /* Publish packet written at 2 seconds (relative to PING that was scheduled above) */ aws_thread_current_sleep(4250000000); /* Wait 4.25 (the extra 0.25 is to account for jitter) seconds */ @@ -3895,9 +3256,9 @@ static int s_test_mqtt_connection_ping_double_scenario_fn(struct aws_allocator * */ /* Disconnect and finish! */ - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3918,7 +3279,7 @@ static int s_test_mqtt_connection_termination_callback_simple_fn(struct aws_allo struct mqtt_connection_state_test *state_test_data = ctx; ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_termination_handler( - state_test_data->mqtt_connection, s_on_connection_termination_fn, state_test_data)); + state_test_data->mqtt_connection, aws_test311_on_connection_termination_fn, state_test_data)); return AWS_OP_SUCCESS; } @@ -3943,11 +3304,11 @@ static int s_test_mqtt_validation_failure_publish_qos_fn(struct aws_allocator *a .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_byte_cursor topic = aws_byte_cursor_from_c_str("a/b"); ASSERT_INT_EQUALS( @@ -3958,14 +3319,14 @@ static int s_test_mqtt_validation_failure_publish_qos_fn(struct aws_allocator *a (enum aws_mqtt_qos)3, true, NULL, - s_on_op_complete, + aws_test311_on_op_complete, state_test_data)); int error_code = aws_last_error(); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_INVALID_QOS, error_code); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -3990,11 +3351,11 @@ static int s_test_mqtt_validation_failure_subscribe_empty_fn(struct aws_allocato .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); struct aws_array_list topic_filters; size_t list_len = 2; @@ -4004,13 +3365,13 @@ static int s_test_mqtt_validation_failure_subscribe_empty_fn(struct aws_allocato ASSERT_INT_EQUALS( 0, aws_mqtt_client_connection_subscribe_multiple( - state_test_data->mqtt_connection, &topic_filters, s_on_multi_suback, state_test_data)); + state_test_data->mqtt_connection, &topic_filters, aws_test311_on_multi_suback, state_test_data)); int error_code = aws_last_error(); ASSERT_INT_EQUALS(AWS_ERROR_INVALID_ARGUMENT, error_code); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -4035,22 +3396,22 @@ static int s_test_mqtt_validation_failure_unsubscribe_null_fn(struct aws_allocat .client_id = aws_byte_cursor_from_c_str("client1234"), .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); - s_wait_for_connection_to_complete(state_test_data); + aws_test311_wait_for_connection_to_complete(state_test_data); ASSERT_INT_EQUALS( 0, aws_mqtt_client_connection_unsubscribe( - state_test_data->mqtt_connection, NULL, s_on_op_complete, state_test_data)); + state_test_data->mqtt_connection, NULL, aws_test311_on_op_complete, state_test_data)); int error_code = aws_last_error(); ASSERT_INT_EQUALS(AWS_ERROR_MQTT_INVALID_TOPIC, error_code); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, s_on_disconnect_fn, state_test_data)); - s_wait_for_disconnect_to_complete(state_test_data); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; } @@ -4078,7 +3439,7 @@ static int s_test_mqtt_validation_failure_connect_invalid_client_id_utf8_fn( .client_id = s_bad_client_id_utf8, .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), .socket_options = &state_test_data->socket_options, - .on_connection_complete = s_on_connection_complete_fn, + .on_connection_complete = aws_test311_on_connection_complete_fn, }; ASSERT_FAILS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); diff --git a/tests/v3/mqtt311_listener_test.c b/tests/v3/mqtt311_listener_test.c new file mode 100644 index 00000000..758ee89f --- /dev/null +++ b/tests/v3/mqtt311_listener_test.c @@ -0,0 +1,375 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include + +#include "mqtt311_testing_utils.h" +#include "mqtt_mock_server_handler.h" + +#include + +struct mqtt311_listener_connection_success_record { + bool joined_session; +}; + +struct mqtt311_listener_publish_record { + struct aws_byte_buf topic; + struct aws_byte_buf payload; +}; + +struct mqtt311_listener_test_context { + struct aws_allocator *allocator; + + struct aws_mqtt311_listener *listener; + + struct mqtt_connection_state_test *mqtt311_test_context; + int mqtt311_test_context_setup_result; + + struct aws_array_list connection_success_events; + struct aws_array_list publish_events; + bool terminated; + + struct aws_mutex lock; + struct aws_condition_variable signal; +}; + +static void s_311_listener_test_on_publish_received( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain, + void *userdata) { + (void)connection; + (void)dup; + (void)qos; + (void)retain; + + struct mqtt311_listener_test_context *context = userdata; + + struct mqtt311_listener_publish_record publish_record; + AWS_ZERO_STRUCT(publish_record); + + aws_byte_buf_init_copy_from_cursor(&publish_record.topic, context->allocator, *topic); + aws_byte_buf_init_copy_from_cursor(&publish_record.payload, context->allocator, *payload); + + aws_mutex_lock(&context->lock); + aws_array_list_push_back(&context->publish_events, &publish_record); + aws_mutex_unlock(&context->lock); + aws_condition_variable_notify_all(&context->signal); +} + +static void s_311_listener_test_on_connection_success( + struct aws_mqtt_client_connection *connection, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata) { + (void)connection; + (void)return_code; + + struct mqtt311_listener_test_context *context = userdata; + + struct mqtt311_listener_connection_success_record connection_success_record = { + .joined_session = session_present, + }; + + aws_mutex_lock(&context->lock); + aws_array_list_push_back(&context->connection_success_events, &connection_success_record); + aws_mutex_unlock(&context->lock); + aws_condition_variable_notify_all(&context->signal); +} + +static void s_311_listener_test_on_termination(void *complete_ctx) { + struct mqtt311_listener_test_context *context = complete_ctx; + + aws_mutex_lock(&context->lock); + context->terminated = true; + aws_mutex_unlock(&context->lock); + aws_condition_variable_notify_all(&context->signal); +} + +static int mqtt311_listener_test_context_init( + struct mqtt311_listener_test_context *context, + struct aws_allocator *allocator, + struct mqtt_connection_state_test *mqtt311_test_context) { + AWS_ZERO_STRUCT(*context); + + context->allocator = allocator; + context->mqtt311_test_context = mqtt311_test_context; + + aws_array_list_init_dynamic( + &context->connection_success_events, allocator, 10, sizeof(struct mqtt311_listener_connection_success_record)); + aws_array_list_init_dynamic( + &context->publish_events, allocator, 10, sizeof(struct mqtt311_listener_publish_record)); + + aws_mutex_init(&context->lock); + aws_condition_variable_init(&context->signal); + + context->mqtt311_test_context_setup_result = aws_test311_setup_mqtt_server_fn(allocator, mqtt311_test_context); + ASSERT_SUCCESS(context->mqtt311_test_context_setup_result); + + struct aws_mqtt311_listener_config listener_config = { + .connection = mqtt311_test_context->mqtt_connection, + .listener_callbacks = + { + .publish_received_handler = s_311_listener_test_on_publish_received, + .connection_success_handler = s_311_listener_test_on_connection_success, + .user_data = context, + }, + .termination_callback = s_311_listener_test_on_termination, + .termination_callback_user_data = context, + }; + + context->listener = aws_mqtt311_listener_new(allocator, &listener_config); + + return AWS_OP_SUCCESS; +} + +static bool s_is_listener_terminated(void *userdata) { + struct mqtt311_listener_test_context *context = userdata; + + return context->terminated; +} + +static void s_wait_for_listener_termination_callback(struct mqtt311_listener_test_context *context) { + aws_mutex_lock(&context->lock); + aws_condition_variable_wait_pred(&context->signal, &context->lock, s_is_listener_terminated, context); + aws_mutex_unlock(&context->lock); +} + +static void mqtt311_listener_test_context_clean_up(struct mqtt311_listener_test_context *context) { + aws_mqtt311_listener_release(context->listener); + s_wait_for_listener_termination_callback(context); + + aws_test311_clean_up_mqtt_server_fn( + context->allocator, context->mqtt311_test_context_setup_result, context->mqtt311_test_context); + + aws_mutex_clean_up(&context->lock); + aws_condition_variable_clean_up(&context->signal); + + aws_array_list_clean_up(&context->connection_success_events); + + for (size_t i = 0; i < aws_array_list_length(&context->publish_events); ++i) { + struct mqtt311_listener_publish_record publish_record; + AWS_ZERO_STRUCT(publish_record); + + aws_array_list_get_at(&context->publish_events, &publish_record, i); + + aws_byte_buf_clean_up(&publish_record.topic); + aws_byte_buf_clean_up(&publish_record.payload); + } + + aws_array_list_clean_up(&context->publish_events); +} + +struct connection_success_event_test_context { + struct mqtt311_listener_test_context *context; + bool joined_session; + size_t expected_count; +}; + +static bool s_contains_connection_success_events(void *userdata) { + struct connection_success_event_test_context *wait_context = userdata; + struct mqtt311_listener_test_context *context = wait_context->context; + + size_t found = 0; + for (size_t i = 0; i < aws_array_list_length(&context->connection_success_events); ++i) { + struct mqtt311_listener_connection_success_record record; + aws_array_list_get_at(&context->connection_success_events, &record, i); + + if (record.joined_session == wait_context->joined_session) { + ++found; + } + } + + return found >= wait_context->expected_count; +} + +static void s_wait_for_connection_success_events( + struct mqtt311_listener_test_context *context, + bool joined_session, + size_t expected_count) { + struct connection_success_event_test_context wait_context = { + .context = context, + .joined_session = joined_session, + .expected_count = expected_count, + }; + + aws_mutex_lock(&context->lock); + aws_condition_variable_wait_pred( + &context->signal, &context->lock, s_contains_connection_success_events, &wait_context); + aws_mutex_unlock(&context->lock); +} + +static int s_do_mqtt311_listener_connection_success_event_test(struct aws_allocator *allocator, bool session_present) { + aws_mqtt_library_init(allocator); + + struct mqtt_connection_state_test mqtt311_context; + AWS_ZERO_STRUCT(mqtt311_context); + + struct mqtt311_listener_test_context test_context; + ASSERT_SUCCESS(mqtt311_listener_test_context_init(&test_context, allocator, &mqtt311_context)); + + mqtt_mock_server_set_session_present(mqtt311_context.mock_server, session_present); + + struct aws_mqtt_connection_options connection_options = { + .user_data = &mqtt311_context, + .clean_session = true, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(mqtt311_context.endpoint.address), + .socket_options = &mqtt311_context.socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(&mqtt311_context); + + s_wait_for_connection_success_events(&test_context, session_present, 1); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); + aws_test311_wait_for_disconnect_to_complete(&mqtt311_context); + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(&mqtt311_context); + + s_wait_for_connection_success_events(&test_context, session_present, 2); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); + aws_test311_wait_for_disconnect_to_complete(&mqtt311_context); + + mqtt311_listener_test_context_clean_up(&test_context); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_mqtt311_listener_connection_success_event_no_session_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_mqtt311_listener_connection_success_event_test(allocator, false); +} + +AWS_TEST_CASE( + mqtt311_listener_connection_success_event_no_session, + s_mqtt311_listener_connection_success_event_no_session_fn) + +static int s_mqtt311_listener_connection_success_event_with_session_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_mqtt311_listener_connection_success_event_test(allocator, true); +} + +AWS_TEST_CASE( + mqtt311_listener_connection_success_event_with_session, + s_mqtt311_listener_connection_success_event_with_session_fn) + +struct publish_event_test_context { + struct mqtt311_listener_test_context *context; + struct aws_byte_cursor expected_topic; + struct aws_byte_cursor expected_payload; + size_t expected_count; +}; + +static bool s_contains_publish_events(void *userdata) { + struct publish_event_test_context *wait_context = userdata; + struct mqtt311_listener_test_context *context = wait_context->context; + + size_t found = 0; + for (size_t i = 0; i < aws_array_list_length(&context->publish_events); ++i) { + struct mqtt311_listener_publish_record record; + aws_array_list_get_at(&context->publish_events, &record, i); + + struct aws_byte_cursor actual_topic = aws_byte_cursor_from_buf(&record.topic); + if (!aws_byte_cursor_eq(&wait_context->expected_topic, &actual_topic)) { + continue; + } + + struct aws_byte_cursor actual_payload = aws_byte_cursor_from_buf(&record.payload); + if (!aws_byte_cursor_eq(&wait_context->expected_payload, &actual_payload)) { + continue; + } + + ++found; + } + + return found >= wait_context->expected_count; +} + +static void s_wait_for_publish_events( + struct mqtt311_listener_test_context *context, + struct aws_byte_cursor topic, + struct aws_byte_cursor payload, + size_t expected_count) { + struct publish_event_test_context wait_context = { + .context = context, + .expected_topic = topic, + .expected_payload = payload, + .expected_count = expected_count, + }; + + aws_mutex_lock(&context->lock); + aws_condition_variable_wait_pred(&context->signal, &context->lock, s_contains_publish_events, &wait_context); + aws_mutex_unlock(&context->lock); +} + +static int s_mqtt311_listener_publish_event_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt_connection_state_test mqtt311_context; + AWS_ZERO_STRUCT(mqtt311_context); + + struct mqtt311_listener_test_context test_context; + ASSERT_SUCCESS(mqtt311_listener_test_context_init(&test_context, allocator, &mqtt311_context)); + + mqtt_mock_server_set_publish_reflection(mqtt311_context.mock_server, true); + + struct aws_mqtt_connection_options connection_options = { + .user_data = &mqtt311_context, + .clean_session = true, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(mqtt311_context.endpoint.address), + .socket_options = &mqtt311_context.socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(&mqtt311_context); + + struct aws_byte_cursor topic1 = aws_byte_cursor_from_c_str("hello/world/1"); + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("payload1"); + aws_mqtt_client_connection_publish( + mqtt311_context.mqtt_connection, &topic1, AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload1, NULL, NULL); + + s_wait_for_publish_events(&test_context, topic1, payload1, 1); + + struct aws_byte_cursor topic2 = aws_byte_cursor_from_c_str("nothing/important"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("somethingneeddoing?"); + aws_mqtt_client_connection_publish( + mqtt311_context.mqtt_connection, &topic2, AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload2, NULL, NULL); + aws_mqtt_client_connection_publish( + mqtt311_context.mqtt_connection, &topic2, AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload2, NULL, NULL); + + s_wait_for_publish_events(&test_context, topic2, payload2, 2); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); + aws_test311_wait_for_disconnect_to_complete(&mqtt311_context); + + mqtt311_listener_test_context_clean_up(&test_context); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(mqtt311_listener_publish_event, s_mqtt311_listener_publish_event_fn) \ No newline at end of file diff --git a/tests/v3/mqtt311_testing_utils.c b/tests/v3/mqtt311_testing_utils.c new file mode 100644 index 00000000..52a55e92 --- /dev/null +++ b/tests/v3/mqtt311_testing_utils.c @@ -0,0 +1,580 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include "mqtt311_testing_utils.h" + +#include +#include +#include +#include +#include +#include + +#include "mqtt_mock_server_handler.h" + +static void s_on_incoming_channel_setup_fn( + struct aws_server_bootstrap *bootstrap, + int error_code, + struct aws_channel *channel, + void *user_data) { + (void)bootstrap; + struct mqtt_connection_state_test *state_test_data = user_data; + + state_test_data->error = error_code; + + if (!error_code) { + aws_mutex_lock(&state_test_data->lock); + state_test_data->server_disconnect_completed = false; + aws_mutex_unlock(&state_test_data->lock); + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "server channel setup completed"); + + state_test_data->server_channel = channel; + struct aws_channel_slot *test_handler_slot = aws_channel_slot_new(channel); + aws_channel_slot_insert_end(channel, test_handler_slot); + mqtt_mock_server_handler_update_slot(state_test_data->mock_server, test_handler_slot); + aws_channel_slot_set_handler(test_handler_slot, state_test_data->mock_server); + } +} + +static void s_on_incoming_channel_shutdown_fn( + struct aws_server_bootstrap *bootstrap, + int error_code, + struct aws_channel *channel, + void *user_data) { + (void)bootstrap; + (void)error_code; + (void)channel; + struct mqtt_connection_state_test *state_test_data = user_data; + aws_mutex_lock(&state_test_data->lock); + state_test_data->server_disconnect_completed = true; + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "server channel shutdown completed"); + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static void s_on_listener_destroy(struct aws_server_bootstrap *bootstrap, void *user_data) { + (void)bootstrap; + struct mqtt_connection_state_test *state_test_data = user_data; + aws_mutex_lock(&state_test_data->lock); + state_test_data->listener_destroyed = true; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_listener_destroyed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->listener_destroyed; +} + +static void s_wait_on_listener_cleanup(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_listener_destroyed, state_test_data); + aws_mutex_unlock(&state_test_data->lock); +} + +static void s_on_connection_interrupted(struct aws_mqtt_client_connection *connection, int error_code, void *userdata) { + (void)connection; + (void)error_code; + struct mqtt_connection_state_test *state_test_data = userdata; + + aws_mutex_lock(&state_test_data->lock); + state_test_data->connection_interrupted = true; + state_test_data->interruption_error = error_code; + aws_mutex_unlock(&state_test_data->lock); + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "connection interrupted"); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_connection_interrupted(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_interrupted; +} + +void aws_test311_wait_for_interrupt_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_interrupted, state_test_data); + state_test_data->connection_interrupted = false; + aws_mutex_unlock(&state_test_data->lock); +} + +static void s_on_connection_resumed( + struct aws_mqtt_client_connection *connection, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata) { + (void)connection; + (void)return_code; + (void)session_present; + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "reconnect completed"); + + struct mqtt_connection_state_test *state_test_data = userdata; + + aws_mutex_lock(&state_test_data->lock); + state_test_data->connection_resumed = true; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_connection_resumed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_resumed; +} + +void aws_test311_wait_for_reconnect_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_resumed, state_test_data); + state_test_data->connection_resumed = false; + aws_mutex_unlock(&state_test_data->lock); +} + +static void s_on_connection_success( + struct aws_mqtt_client_connection *connection, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + aws_mutex_lock(&state_test_data->lock); + + state_test_data->session_present = session_present; + state_test_data->mqtt_return_code = return_code; + state_test_data->connection_success = true; + aws_mutex_unlock(&state_test_data->lock); + + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static void s_on_connection_failure(struct aws_mqtt_client_connection *connection, int error_code, void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + aws_mutex_lock(&state_test_data->lock); + + state_test_data->error = error_code; + state_test_data->connection_failure = true; + aws_mutex_unlock(&state_test_data->lock); + + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_connection_succeed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_success; +} + +static bool s_is_connection_failed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_failure; +} + +void aws_test311_wait_for_connection_to_succeed(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_succeed, state_test_data); + state_test_data->connection_success = false; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_wait_for_connection_to_fail(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_failed, state_test_data); + state_test_data->connection_failure = false; + aws_mutex_unlock(&state_test_data->lock); +} + +static bool s_is_termination_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_terminated; +} + +static void s_wait_for_termination_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_termination_completed, state_test_data); + state_test_data->connection_terminated = false; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_connection_termination_fn(void *userdata) { + struct mqtt_connection_state_test *state_test_data = (struct mqtt_connection_state_test *)userdata; + + aws_mutex_lock(&state_test_data->lock); + state_test_data->connection_termination_calls += 1; + state_test_data->connection_terminated = true; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static void s_on_any_publish_received( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain, + void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + + struct aws_byte_buf payload_cp; + aws_byte_buf_init_copy_from_cursor(&payload_cp, state_test_data->allocator, *payload); + struct aws_byte_buf topic_cp; + aws_byte_buf_init_copy_from_cursor(&topic_cp, state_test_data->allocator, *topic); + struct received_publish_packet received_packet = { + .payload = payload_cp, + .topic = topic_cp, + .dup = dup, + .qos = qos, + .retain = retain, + }; + + aws_mutex_lock(&state_test_data->lock); + aws_array_list_push_back(&state_test_data->any_published_messages, &received_packet); + state_test_data->any_publishes_received++; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +/** + * sets up a unix domain socket server and socket options. Creates an mqtt connection configured to use + * the domain socket. + */ +int aws_test311_setup_mqtt_server_fn(struct aws_allocator *allocator, void *ctx) { + aws_mqtt_library_init(allocator); + + struct mqtt_connection_state_test *state_test_data = ctx; + + AWS_ZERO_STRUCT(*state_test_data); + + state_test_data->allocator = allocator; + state_test_data->el_group = aws_event_loop_group_new_default(allocator, 1, NULL); + + state_test_data->mock_server = new_mqtt_mock_server(allocator); + ASSERT_NOT_NULL(state_test_data->mock_server); + + state_test_data->server_bootstrap = aws_server_bootstrap_new(allocator, state_test_data->el_group); + ASSERT_NOT_NULL(state_test_data->server_bootstrap); + + struct aws_socket_options socket_options = { + .connect_timeout_ms = 100, + .domain = AWS_SOCKET_LOCAL, + }; + + state_test_data->socket_options = socket_options; + ASSERT_SUCCESS(aws_condition_variable_init(&state_test_data->cvar)); + ASSERT_SUCCESS(aws_mutex_init(&state_test_data->lock)); + + aws_socket_endpoint_init_local_address_for_test(&state_test_data->endpoint); + + struct aws_server_socket_channel_bootstrap_options server_bootstrap_options = { + .bootstrap = state_test_data->server_bootstrap, + .host_name = state_test_data->endpoint.address, + .port = state_test_data->endpoint.port, + .socket_options = &state_test_data->socket_options, + .incoming_callback = s_on_incoming_channel_setup_fn, + .shutdown_callback = s_on_incoming_channel_shutdown_fn, + .destroy_callback = s_on_listener_destroy, + .user_data = state_test_data, + }; + state_test_data->listener = aws_server_bootstrap_new_socket_listener(&server_bootstrap_options); + + ASSERT_NOT_NULL(state_test_data->listener); + + struct aws_host_resolver_default_options resolver_options = { + .el_group = state_test_data->el_group, + .max_entries = 1, + }; + state_test_data->host_resolver = aws_host_resolver_new_default(allocator, &resolver_options); + + struct aws_client_bootstrap_options bootstrap_options = { + .event_loop_group = state_test_data->el_group, + .user_data = state_test_data, + .host_resolver = state_test_data->host_resolver, + }; + + state_test_data->client_bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); + + state_test_data->mqtt_client = aws_mqtt_client_new(allocator, state_test_data->client_bootstrap); + state_test_data->mqtt_connection = aws_mqtt_client_connection_new(state_test_data->mqtt_client); + ASSERT_NOT_NULL(state_test_data->mqtt_connection); + + ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_interruption_handlers( + state_test_data->mqtt_connection, + s_on_connection_interrupted, + state_test_data, + s_on_connection_resumed, + state_test_data)); + + ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_result_handlers( + state_test_data->mqtt_connection, + s_on_connection_success, + state_test_data, + s_on_connection_failure, + state_test_data)); + + ASSERT_SUCCESS(aws_mqtt_client_connection_set_on_any_publish_handler( + state_test_data->mqtt_connection, s_on_any_publish_received, state_test_data)); + + ASSERT_SUCCESS(aws_array_list_init_dynamic( + &state_test_data->published_messages, allocator, 4, sizeof(struct received_publish_packet))); + ASSERT_SUCCESS(aws_array_list_init_dynamic( + &state_test_data->any_published_messages, allocator, 4, sizeof(struct received_publish_packet))); + ASSERT_SUCCESS(aws_array_list_init_dynamic(&state_test_data->qos_returned, allocator, 2, sizeof(uint8_t))); + + ASSERT_SUCCESS(aws_mqtt_client_connection_set_connection_termination_handler( + state_test_data->mqtt_connection, aws_test311_on_connection_termination_fn, state_test_data)); + + return AWS_OP_SUCCESS; +} + +static void s_received_publish_packet_list_clean_up(struct aws_array_list *list) { + for (size_t i = 0; i < aws_array_list_length(list); ++i) { + struct received_publish_packet *val_ptr = NULL; + aws_array_list_get_at_ptr(list, (void **)&val_ptr, i); + aws_byte_buf_clean_up(&val_ptr->payload); + aws_byte_buf_clean_up(&val_ptr->topic); + } + aws_array_list_clean_up(list); +} + +int aws_test311_clean_up_mqtt_server_fn(struct aws_allocator *allocator, int setup_result, void *ctx) { + (void)allocator; + + if (!setup_result) { + struct mqtt_connection_state_test *state_test_data = ctx; + + s_received_publish_packet_list_clean_up(&state_test_data->published_messages); + s_received_publish_packet_list_clean_up(&state_test_data->any_published_messages); + aws_array_list_clean_up(&state_test_data->qos_returned); + aws_mqtt_client_connection_release(state_test_data->mqtt_connection); + + s_wait_for_termination_to_complete(state_test_data); + ASSERT_UINT_EQUALS(1, state_test_data->connection_termination_calls); + + aws_mqtt_client_release(state_test_data->mqtt_client); + aws_client_bootstrap_release(state_test_data->client_bootstrap); + aws_host_resolver_release(state_test_data->host_resolver); + aws_server_bootstrap_destroy_socket_listener(state_test_data->server_bootstrap, state_test_data->listener); + s_wait_on_listener_cleanup(state_test_data); + aws_server_bootstrap_release(state_test_data->server_bootstrap); + aws_event_loop_group_release(state_test_data->el_group); + destroy_mqtt_mock_server(state_test_data->mock_server); + } + + aws_mqtt_library_clean_up(); + return AWS_OP_SUCCESS; +} + +void aws_test311_on_connection_complete_fn( + struct aws_mqtt_client_connection *connection, + int error_code, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + aws_mutex_lock(&state_test_data->lock); + + state_test_data->session_present = session_present; + state_test_data->mqtt_return_code = return_code; + state_test_data->error = error_code; + state_test_data->connection_completed = true; + aws_mutex_unlock(&state_test_data->lock); + + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_connection_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->connection_completed; +} + +void aws_test311_wait_for_connection_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_connection_completed, state_test_data); + state_test_data->connection_completed = false; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_disconnect_fn(struct aws_mqtt_client_connection *connection, void *userdata) { + (void)connection; + struct mqtt_connection_state_test *state_test_data = userdata; + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "disconnect completed"); + aws_mutex_lock(&state_test_data->lock); + state_test_data->client_disconnect_completed = true; + aws_mutex_unlock(&state_test_data->lock); + + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_disconnect_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->client_disconnect_completed && state_test_data->server_disconnect_completed; +} + +void aws_test311_wait_for_disconnect_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_disconnect_completed, state_test_data); + state_test_data->client_disconnect_completed = false; + state_test_data->server_disconnect_completed = false; + aws_mutex_unlock(&state_test_data->lock); +} + +static bool s_is_any_publish_received(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->any_publishes_received == state_test_data->expected_any_publishes; +} + +void aws_test311_wait_for_any_publish(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_any_publish_received, state_test_data); + state_test_data->any_publishes_received = 0; + state_test_data->expected_any_publishes = 0; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_publish_received( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain, + void *userdata) { + + (void)connection; + (void)topic; + struct mqtt_connection_state_test *state_test_data = userdata; + + struct aws_byte_buf payload_cp; + aws_byte_buf_init_copy_from_cursor(&payload_cp, state_test_data->allocator, *payload); + struct aws_byte_buf topic_cp; + aws_byte_buf_init_copy_from_cursor(&topic_cp, state_test_data->allocator, *topic); + struct received_publish_packet received_packet = { + .payload = payload_cp, + .topic = topic_cp, + .dup = dup, + .qos = qos, + .retain = retain, + }; + + aws_mutex_lock(&state_test_data->lock); + aws_array_list_push_back(&state_test_data->published_messages, &received_packet); + state_test_data->publishes_received++; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_publish_received(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->publishes_received == state_test_data->expected_publishes; +} + +void aws_test311_wait_for_publish(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_publish_received, state_test_data); + state_test_data->publishes_received = 0; + state_test_data->expected_publishes = 0; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_suback( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + int error_code, + void *userdata) { + (void)connection; + (void)packet_id; + (void)topic; + + struct mqtt_connection_state_test *state_test_data = userdata; + + aws_mutex_lock(&state_test_data->lock); + if (!error_code) { + aws_array_list_push_back(&state_test_data->qos_returned, &qos); + } + state_test_data->subscribe_completed = true; + state_test_data->subscribe_complete_error = error_code; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_subscribe_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->subscribe_completed; +} + +void aws_test311_wait_for_subscribe_to_complete(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_subscribe_completed, state_test_data); + state_test_data->subscribe_completed = false; + aws_mutex_unlock(&state_test_data->lock); +} + +void aws_test311_on_multi_suback( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + const struct aws_array_list *topic_subacks, /* contains aws_mqtt_topic_subscription pointers */ + int error_code, + void *userdata) { + (void)connection; + (void)packet_id; + (void)topic_subacks; + (void)error_code; + + struct mqtt_connection_state_test *state_test_data = userdata; + + aws_mutex_lock(&state_test_data->lock); + state_test_data->subscribe_completed = true; + if (!error_code) { + size_t length = aws_array_list_length(topic_subacks); + for (size_t i = 0; i < length; ++i) { + struct aws_mqtt_topic_subscription *subscription = NULL; + aws_array_list_get_at(topic_subacks, &subscription, i); + aws_array_list_push_back(&state_test_data->qos_returned, &subscription->qos); + } + } + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +void aws_test311_on_op_complete( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + int error_code, + void *userdata) { + (void)connection; + (void)packet_id; + + struct mqtt_connection_state_test *state_test_data = userdata; + AWS_LOGF_DEBUG(TEST_LOG_SUBJECT, "pub op completed"); + aws_mutex_lock(&state_test_data->lock); + state_test_data->ops_completed++; + state_test_data->op_complete_error = error_code; + aws_mutex_unlock(&state_test_data->lock); + aws_condition_variable_notify_one(&state_test_data->cvar); +} + +static bool s_is_ops_completed(void *arg) { + struct mqtt_connection_state_test *state_test_data = arg; + return state_test_data->ops_completed == state_test_data->expected_ops_completed; +} + +void aws_test311_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data) { + aws_mutex_lock(&state_test_data->lock); + aws_condition_variable_wait_for_pred( + &state_test_data->cvar, &state_test_data->lock, 10000000000, s_is_ops_completed, state_test_data); + aws_mutex_unlock(&state_test_data->lock); +} \ No newline at end of file diff --git a/tests/v3/mqtt311_testing_utils.h b/tests/v3/mqtt311_testing_utils.h new file mode 100644 index 00000000..e9162c20 --- /dev/null +++ b/tests/v3/mqtt311_testing_utils.h @@ -0,0 +1,155 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#ifndef AWS_C_MQTT_MQTT311_TESTING_UTILS_H +#define AWS_C_MQTT_MQTT311_TESTING_UTILS_H + +#include + +#include +#include +#include +#include +#include + +#include + +#define TEST_LOG_SUBJECT 60000 +#define ONE_SEC 1000000000 +// The value is extract from aws-c-mqtt/source/client.c +#define AWS_RESET_RECONNECT_BACKOFF_DELAY_SECONDS 10 +#define RECONNECT_BACKOFF_DELAY_ERROR_MARGIN_NANO_SECONDS 500000000 +#define DEFAULT_MIN_RECONNECT_DELAY_SECONDS 1 + +#define DEFAULT_TEST_PING_TIMEOUT_MS 1000 +#define DEFAULT_TEST_KEEP_ALIVE_S 2 + +struct received_publish_packet { + struct aws_byte_buf topic; + struct aws_byte_buf payload; + bool dup; + enum aws_mqtt_qos qos; + bool retain; +}; + +struct mqtt_connection_state_test { + struct aws_allocator *allocator; + struct aws_channel *server_channel; + struct aws_channel_handler *mock_server; + struct aws_client_bootstrap *client_bootstrap; + struct aws_server_bootstrap *server_bootstrap; + struct aws_event_loop_group *el_group; + struct aws_host_resolver *host_resolver; + struct aws_socket_endpoint endpoint; + struct aws_socket *listener; + struct aws_mqtt_client *mqtt_client; + struct aws_mqtt_client_connection *mqtt_connection; + struct aws_socket_options socket_options; + + bool session_present; + bool connection_completed; + bool connection_success; + bool connection_failure; + bool client_disconnect_completed; + bool server_disconnect_completed; + bool connection_interrupted; + bool connection_resumed; + bool subscribe_completed; + bool listener_destroyed; + bool connection_terminated; + int interruption_error; + int subscribe_complete_error; + int op_complete_error; + enum aws_mqtt_connect_return_code mqtt_return_code; + int error; + struct aws_condition_variable cvar; + struct aws_mutex lock; + /* any published messages from mock server, that you may not subscribe to. (Which should not happen in real life) */ + struct aws_array_list any_published_messages; /* list of struct received_publish_packet */ + size_t any_publishes_received; + size_t expected_any_publishes; + /* the published messages from mock server, that you did subscribe to. */ + struct aws_array_list published_messages; /* list of struct received_publish_packet */ + size_t publishes_received; + size_t expected_publishes; + /* The returned QoS from mock server */ + struct aws_array_list qos_returned; /* list of uint_8 */ + size_t ops_completed; + size_t expected_ops_completed; + size_t connection_close_calls; /* All of the times on_connection_closed has been called */ + + size_t connection_termination_calls; /* How many times on_connection_termination has been called, should be 1 */ +}; + +AWS_EXTERN_C_BEGIN + +int aws_test311_setup_mqtt_server_fn(struct aws_allocator *allocator, void *ctx); + +int aws_test311_clean_up_mqtt_server_fn(struct aws_allocator *allocator, int setup_result, void *ctx); + +void aws_test311_wait_for_interrupt_to_complete(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_wait_for_reconnect_to_complete(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_wait_for_connection_to_succeed(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_wait_for_connection_to_fail(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_on_connection_complete_fn( + struct aws_mqtt_client_connection *connection, + int error_code, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata); + +void aws_test311_wait_for_connection_to_complete(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_on_disconnect_fn(struct aws_mqtt_client_connection *connection, void *userdata); + +void aws_test311_wait_for_disconnect_to_complete(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_wait_for_any_publish(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_on_publish_received( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain, + void *userdata); + +void aws_test311_wait_for_publish(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_on_suback( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + int error_code, + void *userdata); + +void aws_test311_wait_for_subscribe_to_complete(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_on_multi_suback( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + const struct aws_array_list *topic_subacks, + int error_code, + void *userdata); + +void aws_test311_on_op_complete( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + int error_code, + void *userdata); + +void aws_test311_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data); + +void aws_test311_on_connection_termination_fn(void *userdata); + +AWS_EXTERN_C_END + +#endif // AWS_C_MQTT_MQTT311_TESTING_UTILS_H diff --git a/tests/v3/mqtt_mock_server_handler.c b/tests/v3/mqtt_mock_server_handler.c index 73576a7d..0e770690 100644 --- a/tests/v3/mqtt_mock_server_handler.c +++ b/tests/v3/mqtt_mock_server_handler.c @@ -30,6 +30,8 @@ struct mqtt_mock_server_handler { size_t pubacks_received; size_t ping_received; size_t connacks_avail; + bool session_present; + bool reflect_publishes; bool auto_ack; /* last ID used when sending PUBLISH (QoS1+) to client */ @@ -77,12 +79,14 @@ static int s_mqtt_mock_server_handler_process_packet( switch (packet_type) { case AWS_MQTT_PACKET_CONNECT: { size_t connacks_available = 0; + bool session_present = false; aws_mutex_lock(&server->synced.lock); AWS_LOGF_DEBUG( MOCK_LOG_SUBJECT, "server, CONNECT received, %llu available connacks.", (long long unsigned)server->synced.connacks_avail); connacks_available = server->synced.connacks_avail > 0 ? server->synced.connacks_avail-- : 0; + session_present = server->synced.session_present; aws_mutex_unlock(&server->synced.lock); if (connacks_available) { @@ -90,7 +94,7 @@ static int s_mqtt_mock_server_handler_process_packet( aws_channel_acquire_message_from_pool(server->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, 256); struct aws_mqtt_packet_connack conn_ack; - err |= aws_mqtt_packet_connack_init(&conn_ack, false, AWS_MQTT_CONNECT_ACCEPTED); + err |= aws_mqtt_packet_connack_init(&conn_ack, session_present, AWS_MQTT_CONNECT_ACCEPTED); err |= aws_mqtt_packet_connack_encode(&connack_msg->message_data, &conn_ack); if (aws_channel_slot_send_message(server->slot, connack_msg, AWS_CHANNEL_DIR_WRITE)) { err |= 1; @@ -185,6 +189,7 @@ static int s_mqtt_mock_server_handler_process_packet( aws_mutex_lock(&server->synced.lock); bool auto_ack = server->synced.auto_ack; + bool reflect_publishes = server->synced.reflect_publishes; aws_mutex_unlock(&server->synced.lock); uint8_t qos = (publish_packet.fixed_header.flags >> 1) & 0x3; @@ -197,6 +202,23 @@ static int s_mqtt_mock_server_handler_process_packet( err |= aws_mqtt_packet_ack_encode(&puback_msg->message_data, &puback); err |= aws_channel_slot_send_message(server->slot, puback_msg, AWS_CHANNEL_DIR_WRITE); } + + if (reflect_publishes) { + struct aws_io_message *publish_msg = + aws_channel_acquire_message_from_pool(server->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, 1024); + struct aws_mqtt_packet_publish publish; + // reusing the packet identifier here is weird but reasonably safe, they're separate id spaces + err |= aws_mqtt_packet_publish_init( + &publish, + false, + qos, + false, + publish_packet.topic_name, + publish_packet.packet_identifier, + publish_packet.payload); + err |= aws_mqtt_packet_publish_encode(&publish_msg->message_data, &publish); + err |= aws_channel_slot_send_message(server->slot, publish_msg, AWS_CHANNEL_DIR_WRITE); + } break; } @@ -466,6 +488,22 @@ void destroy_mqtt_mock_server(struct aws_channel_handler *handler) { aws_mem_release(handler->alloc, server); } +void mqtt_mock_server_set_session_present(struct aws_channel_handler *handler, bool session_present) { + struct mqtt_mock_server_handler *server = handler->impl; + + aws_mutex_lock(&server->synced.lock); + server->synced.session_present = session_present; + aws_mutex_unlock(&server->synced.lock); +} + +void mqtt_mock_server_set_publish_reflection(struct aws_channel_handler *handler, bool reflect_publishes) { + struct mqtt_mock_server_handler *server = handler->impl; + + aws_mutex_lock(&server->synced.lock); + server->synced.reflect_publishes = reflect_publishes; + aws_mutex_unlock(&server->synced.lock); +} + void mqtt_mock_server_set_max_ping_resp(struct aws_channel_handler *handler, size_t max_ping) { struct mqtt_mock_server_handler *server = handler->impl; diff --git a/tests/v3/mqtt_mock_server_handler.h b/tests/v3/mqtt_mock_server_handler.h index 170201e6..8187ae11 100644 --- a/tests/v3/mqtt_mock_server_handler.h +++ b/tests/v3/mqtt_mock_server_handler.h @@ -68,10 +68,21 @@ int mqtt_mock_server_send_publish_by_id( enum aws_mqtt_qos qos, bool retain); +/** + * Sets whether or not connacks return session present + */ +void mqtt_mock_server_set_session_present(struct aws_channel_handler *handler, bool session_present); + +/** + * Sets whether or not connacks return session present + */ +void mqtt_mock_server_set_publish_reflection(struct aws_channel_handler *handler, bool reflect_publishes); + /** * Set max number of PINGRESP that mock server will send back to client */ void mqtt_mock_server_set_max_ping_resp(struct aws_channel_handler *handler, size_t max_ping); + /** * Set max number of CONACK that mock server will send back to client */ diff --git a/tests/v5/mqtt5_testing_utils.c b/tests/v5/mqtt5_testing_utils.c index 4b682d83..9d1163c9 100644 --- a/tests/v5/mqtt5_testing_utils.c +++ b/tests/v5/mqtt5_testing_utils.c @@ -1765,4 +1765,4 @@ int aws_mqtt5_mock_server_send_packet( } return AWS_OP_SUCCESS; -} \ No newline at end of file +} From 7512e38099944eae7b520ad73ba88ce29e3e275e Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 14:57:16 -0800 Subject: [PATCH 032/124] Subscribe timeout and add ability to override timeout internally --- include/aws/mqtt/private/client_impl.h | 22 ++++++ source/client.c | 95 +++++++++++++++----------- tests/CMakeLists.txt | 1 + tests/v3/connection_state_test.c | 61 ++++++++++++++++- 4 files changed, 138 insertions(+), 41 deletions(-) diff --git a/include/aws/mqtt/private/client_impl.h b/include/aws/mqtt/private/client_impl.h index ce041b2d..3f6ed408 100644 --- a/include/aws/mqtt/private/client_impl.h +++ b/include/aws/mqtt/private/client_impl.h @@ -154,6 +154,25 @@ struct aws_mqtt_reconnect_task { struct aws_allocator *allocator; }; +struct request_timeout_wrapper; + +/* used for timeout task */ +struct request_timeout_task_arg { + uint16_t packet_id; + struct aws_mqtt_client_connection_311_impl *connection; + struct request_timeout_wrapper *task_arg_wrapper; +}; + +/* + * We want the timeout task to be able to destroy the forward reference from the operation's task arg structure + * to the timeout task. But the operation task arg structures don't have any data structure in common. So to allow + * the timeout to refer back to a zero-able forward pointer, we wrap a pointer to the timeout task and embed it + * in every operation's task arg that needs to create a timeout. + */ +struct request_timeout_wrapper { + struct request_timeout_task_arg *timeout_task_arg; +}; + /* The lifetime of this struct is from subscribe -> suback */ struct subscribe_task_arg { @@ -173,6 +192,9 @@ struct subscribe_task_arg { aws_mqtt_suback_fn *single; } on_suback; void *on_suback_ud; + + struct request_timeout_wrapper timeout_wrapper; + uint64_t timeout_duration_in_ns; }; /* The lifetime of this struct is the same as the lifetime of the subscription */ diff --git a/source/client.c b/source/client.c index 8a8bc386..dfa099c7 100644 --- a/source/client.c +++ b/source/client.c @@ -93,25 +93,6 @@ void mqtt_connection_set_state( connection->synced_data.state = state; } -struct request_timeout_wrapper; - -/* used for timeout task */ -struct request_timeout_task_arg { - uint16_t packet_id; - struct aws_mqtt_client_connection_311_impl *connection; - struct request_timeout_wrapper *task_arg_wrapper; -}; - -/* - * We want the timeout task to be able to destroy the forward reference from the operation's task arg structure - * to the timeout task. But the operation task arg structures don't have any data structure in common. So to allow - * the timeout to refer back to a zero-able forward pointer, we wrap a pointer to the timeout task and embed it - * in every operation's task arg that needs to create a timeout. - */ -struct request_timeout_wrapper { - struct request_timeout_task_arg *timeout_task_arg; -}; - 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; @@ -139,8 +120,14 @@ 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_311_impl *connection, - uint16_t packet_id) { - /* schedule a timeout task to run, in case server consider the publish is not received */ + uint16_t packet_id, + uint64_t timeout_duration_in_ns) { + + if (timeout_duration_in_ns == UINT64_MAX || timeout_duration_in_ns == 0 || packet_id == 0) { + return NULL; + } + + /* schedule a timeout task to run, in case server never sends us an ack */ struct aws_channel_task *request_timeout_task = NULL; struct request_timeout_task_arg *timeout_task_arg = NULL; if (!aws_mem_acquire_many( @@ -1897,6 +1884,20 @@ static enum aws_mqtt_client_request_state s_subscribe_send(uint16_t packet_id, b aws_mem_release(message->allocator, message); } + /* 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, task_arg->timeout_duration_in_ns); + if (timeout_task_arg) { + /* + * 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; @@ -1962,6 +1963,18 @@ static void s_subscribe_complete( error_code, task_arg->on_suback_ud); } + + /* + * If we have a forward pointer to a timeout task, then that means the timeout task has not run yet. So we should + * follow it and zero out the back pointer to us, because we're going away now. The timeout task will run later + * and be harmless (even vs. future operations with the same packet id) because it only cancels if it has a back + * pointer. + */ + if (task_arg->timeout_wrapper.timeout_task_arg) { + task_arg->timeout_wrapper.timeout_task_arg->task_arg_wrapper = NULL; + task_arg->timeout_wrapper.timeout_task_arg = NULL; + } + for (size_t i = 0; i < list_len; i++) { aws_array_list_get_at(&task_arg->topics, &topic, i); s_task_topic_release(topic); @@ -1994,6 +2007,7 @@ static uint16_t s_aws_mqtt_client_connection_311_subscribe_multiple( task_arg->connection = connection; task_arg->on_suback.multi = on_suback; task_arg->on_suback_ud = on_suback_ud; + task_arg->timeout_duration_in_ns = connection->operation_timeout_ns; const size_t num_topics = aws_array_list_length(topic_filters); @@ -2177,6 +2191,7 @@ static uint16_t s_aws_mqtt_client_connection_311_subscribe( task_arg->connection = connection; task_arg->on_suback.single = on_suback; task_arg->on_suback_ud = on_suback_ud; + task_arg->timeout_duration_in_ns = connection->operation_timeout_ns; /* It stores the pointer */ aws_array_list_init_static(&task_arg->topics, task_topic_storage, 1, sizeof(void *)); @@ -2448,6 +2463,7 @@ static uint16_t s_aws_mqtt_311_resubscribe_existing_topics( task_arg->connection = connection; task_arg->on_suback.multi = on_suback; task_arg->on_suback_ud = on_suback_ud; + task_arg->timeout_duration_in_ns = connection->operation_timeout_ns; /* Calculate the size of the packet. * The fixed header is 2 bytes and the packet ID is 2 bytes @@ -2508,6 +2524,7 @@ struct unsubscribe_task_arg { void *on_unsuback_ud; struct request_timeout_wrapper timeout_wrapper; + uint64_t timeout_duration_in_ns; }; static enum aws_mqtt_client_request_state s_unsubscribe_send( @@ -2599,18 +2616,17 @@ static enum aws_mqtt_client_request_state s_unsubscribe_send( /* 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; + struct request_timeout_task_arg *timeout_task_arg = + s_schedule_timeout_task(task_arg->connection, packet_id, task_arg->timeout_duration_in_ns); + if (timeout_task_arg) { + /* + * 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; } - /* - * 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; @@ -2691,6 +2707,7 @@ static uint16_t s_aws_mqtt_client_connection_311_unsubscribe( task_arg->filter = aws_byte_cursor_from_string(task_arg->filter_string); task_arg->on_unsuback = on_unsuback; task_arg->on_unsuback_ud = on_unsuback_ud; + task_arg->timeout_duration_in_ns = connection->operation_timeout_ns; /* Calculate the size of the unsubscribe packet. * The fixed header is always 2 bytes, the packet ID is always 2 bytes @@ -2745,6 +2762,7 @@ struct publish_task_arg { aws_mqtt_op_complete_fn *on_complete; void *userdata; + uint64_t timeout_duration_in_ns; struct request_timeout_wrapper timeout_wrapper; }; @@ -2879,15 +2897,13 @@ static enum aws_mqtt_client_request_state s_publish_send(uint16_t packet_id, boo goto write_payload_chunk; } } - if (!is_qos_0 && connection->operation_timeout_ns != UINT64_MAX) { - /* 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 fire the on_completion callbacks. */ - struct request_timeout_task_arg *timeout_task_arg = s_schedule_timeout_task(connection, packet_id); - if (!timeout_task_arg) { - return AWS_MQTT_CLIENT_REQUEST_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 fire the on_completion callbacks. */ + struct request_timeout_task_arg *timeout_task_arg = + s_schedule_timeout_task(connection, packet_id, task_arg->timeout_duration_in_ns); + if (timeout_task_arg != NULL) { /* * 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. @@ -2965,6 +2981,7 @@ static uint16_t s_aws_mqtt_client_connection_311_publish( arg->topic = aws_byte_cursor_from_string(arg->topic_string); arg->qos = qos; arg->retain = retain; + arg->timeout_duration_in_ns = connection->operation_timeout_ns; struct aws_byte_cursor payload_cursor; AWS_ZERO_STRUCT(payload_cursor); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e52138a6..c0c6e275 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -79,6 +79,7 @@ add_test_case(mqtt_clean_session_discard_previous) add_test_case(mqtt_clean_session_keep_next_session) add_test_case(mqtt_connection_publish_QoS1_timeout) add_test_case(mqtt_connection_unsub_timeout) +add_test_case(mqtt_connection_sub_timeout) add_test_case(mqtt_connection_publish_QoS1_timeout_connection_lost_reset_time) add_test_case(mqtt_connection_ping_norm) add_test_case(mqtt_connection_ping_no) diff --git a/tests/v3/connection_state_test.c b/tests/v3/connection_state_test.c index 65f8b9e7..f790adcc 100644 --- a/tests/v3/connection_state_test.c +++ b/tests/v3/connection_state_test.c @@ -2412,9 +2412,9 @@ static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocat state_test_data->mqtt_connection, &pub_topic, aws_test311_on_op_complete, state_test_data); ASSERT_TRUE(unsub_packet_id > 0); - /* publish should complete after the shutdown */ + /* unsubscribe should complete after the timeout */ aws_test311_wait_for_ops_completed(state_test_data); - /* Check the publish has been completed with timeout error */ + /* Check that the unsubscribe has been completed with a timeout error */ ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_TIMEOUT); ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); @@ -2430,6 +2430,63 @@ AWS_TEST_CASE_FIXTURE( s_clean_up_mqtt_server_fn, &test_data) +/** + * Test that connection is healthy, user set the timeout for request, and timeout happens and the subscribe failed. + */ +static int s_test_mqtt_connection_sub_timeout_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + struct mqtt_connection_state_test *state_test_data = ctx; + + struct aws_mqtt_connection_options connection_options = { + .user_data = state_test_data, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), + .socket_options = &state_test_data->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .protocol_operation_timeout_ms = 3000, + .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(state_test_data); + + /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ + mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); + + /* subscribe */ + struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); + uint16_t sub_packet_id = aws_mqtt_client_connection_subscribe( + state_test_data->mqtt_connection, + &pub_topic, + AWS_MQTT_QOS_AT_LEAST_ONCE, + aws_test311_on_publish_received, + state_test_data, + NULL, + aws_test311_on_suback, + state_test_data); + ASSERT_TRUE(sub_packet_id > 0); + + /* subscribe should complete after the timeout */ + aws_test311_wait_for_subscribe_to_complete(state_test_data); + /* Check that the subscribe has been completed with a timeout error */ + ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_MQTT_TIMEOUT); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE_FIXTURE( + mqtt_connection_sub_timeout, + s_setup_mqtt_server_fn, + s_test_mqtt_connection_sub_timeout_fn, + s_clean_up_mqtt_server_fn, + &test_data) + /** * Test that connection is healthy, user set the timeout for request, and connection lost will reset timeout. */ From 75a8a6a1d715e85729717cf9d965fd7624787b24 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 29 Jan 2024 09:20:37 -0800 Subject: [PATCH 033/124] Detach other subscribe completion callbacks --- source/client.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/source/client.c b/source/client.c index dfa099c7..f330eb82 100644 --- a/source/client.c +++ b/source/client.c @@ -2146,6 +2146,18 @@ static void s_subscribe_single_complete( error_code, task_arg->on_suback_ud); } + + /* + * If we have a forward pointer to a timeout task, then that means the timeout task has not run yet. So we should + * follow it and zero out the back pointer to us, because we're going away now. The timeout task will run later + * and be harmless (even vs. future operations with the same packet id) because it only cancels if it has a back + * pointer. + */ + if (task_arg->timeout_wrapper.timeout_task_arg) { + task_arg->timeout_wrapper.timeout_task_arg->task_arg_wrapper = NULL; + task_arg->timeout_wrapper.timeout_task_arg = NULL; + } + s_task_topic_release(topic); aws_array_list_clean_up(&task_arg->topics); aws_mqtt_packet_subscribe_clean_up(&task_arg->subscribe); @@ -2434,6 +2446,17 @@ static void s_resubscribe_complete( clean_up: + /* + * If we have a forward pointer to a timeout task, then that means the timeout task has not run yet. So we should + * follow it and zero out the back pointer to us, because we're going away now. The timeout task will run later + * and be harmless (even vs. future operations with the same packet id) because it only cancels if it has a back + * pointer. + */ + if (task_arg->timeout_wrapper.timeout_task_arg) { + task_arg->timeout_wrapper.timeout_task_arg->task_arg_wrapper = NULL; + task_arg->timeout_wrapper.timeout_task_arg = NULL; + } + /* We need to cleanup the subscribe_task_topics, since they are not inserted into the topic tree by resubscribe. We * take the ownership to clean it up */ for (size_t i = 0; i < list_len; i++) { From 1e7c4aecec97258ed930a91d3e71d77a98afa751 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 29 Jan 2024 12:59:27 -0800 Subject: [PATCH 034/124] Subscribe timeout impl and tests --- include/aws/mqtt/private/client_impl.h | 28 +++++ source/client.c | 97 ++++++++++++--- tests/CMakeLists.txt | 6 +- tests/v3/connection_state_test.c | 160 ++++++++++++++++++++++++- tests/v3/mqtt311_testing_utils.c | 1 + 5 files changed, 266 insertions(+), 26 deletions(-) diff --git a/include/aws/mqtt/private/client_impl.h b/include/aws/mqtt/private/client_impl.h index 3f6ed408..ab9822c3 100644 --- a/include/aws/mqtt/private/client_impl.h +++ b/include/aws/mqtt/private/client_impl.h @@ -451,4 +451,32 @@ void aws_mqtt_connection_statistics_change_operation_statistic_state( AWS_MQTT_API const struct aws_mqtt_client_connection_packet_handlers *aws_mqtt311_get_default_packet_handlers(void); +AWS_MQTT_API uint16_t aws_mqtt_client_connection_311_unsubscribe( + struct aws_mqtt_client_connection_311_impl *connection, + const struct aws_byte_cursor *topic_filter, + aws_mqtt_op_complete_fn *on_unsuback, + void *on_unsuback_ud, + uint64_t timeout_ns); + +AWS_MQTT_API uint16_t aws_mqtt_client_connection_311_subscribe( + struct aws_mqtt_client_connection_311_impl *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, + uint64_t timeout_ns); + +AWS_MQTT_API uint16_t aws_mqtt_client_connection_311_publish( + struct aws_mqtt_client_connection_311_impl *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, + uint64_t timeout_ns); + #endif /* AWS_MQTT_PRIVATE_CLIENT_IMPL_H */ diff --git a/source/client.c b/source/client.c index f330eb82..d8cbe85e 100644 --- a/source/client.c +++ b/source/client.c @@ -2164,17 +2164,16 @@ static void s_subscribe_single_complete( aws_mem_release(task_arg->connection->allocator, task_arg); } -static uint16_t s_aws_mqtt_client_connection_311_subscribe( - void *impl, +uint16_t aws_mqtt_client_connection_311_subscribe( + struct aws_mqtt_client_connection_311_impl *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) { - - struct aws_mqtt_client_connection_311_impl *connection = impl; + void *on_suback_ud, + uint64_t timeout_ns) { AWS_PRECONDITION(connection); @@ -2203,7 +2202,7 @@ static uint16_t s_aws_mqtt_client_connection_311_subscribe( task_arg->connection = connection; task_arg->on_suback.single = on_suback; task_arg->on_suback_ud = on_suback_ud; - task_arg->timeout_duration_in_ns = connection->operation_timeout_ns; + task_arg->timeout_duration_in_ns = timeout_ns; /* It stores the pointer */ aws_array_list_init_static(&task_arg->topics, task_topic_storage, 1, sizeof(void *)); @@ -2279,6 +2278,29 @@ static uint16_t s_aws_mqtt_client_connection_311_subscribe( return 0; } +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, + void *on_publish_ud, + aws_mqtt_userdata_cleanup_fn *on_ud_cleanup, + aws_mqtt_suback_fn *on_suback, + void *on_suback_ud) { + + struct aws_mqtt_client_connection_311_impl *connection = impl; + return aws_mqtt_client_connection_311_subscribe( + connection, + topic_filter, + qos, + on_publish, + on_publish_ud, + on_ud_cleanup, + on_suback, + on_suback_ud, + connection->operation_timeout_ns); +} + /******************************************************************************* * Resubscribe ******************************************************************************/ @@ -2383,6 +2405,20 @@ static enum aws_mqtt_client_request_state s_resubscribe_send( aws_mem_release(message->allocator, message); } + /* 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, task_arg->timeout_duration_in_ns); + if (timeout_task_arg) { + /* + * 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; + } + return AWS_MQTT_CLIENT_REQUEST_ONGOING; handle_error: @@ -2704,13 +2740,12 @@ static void s_unsubscribe_complete( aws_mem_release(task_arg->connection->allocator, task_arg); } -static uint16_t s_aws_mqtt_client_connection_311_unsubscribe( - void *impl, +uint16_t aws_mqtt_client_connection_311_unsubscribe( + struct aws_mqtt_client_connection_311_impl *connection, 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; + void *on_unsuback_ud, + uint64_t timeout_ns) { AWS_PRECONDITION(connection); @@ -2730,7 +2765,7 @@ static uint16_t s_aws_mqtt_client_connection_311_unsubscribe( task_arg->filter = aws_byte_cursor_from_string(task_arg->filter_string); task_arg->on_unsuback = on_unsuback; task_arg->on_unsuback_ud = on_unsuback_ud; - task_arg->timeout_duration_in_ns = connection->operation_timeout_ns; + task_arg->timeout_duration_in_ns = timeout_ns; /* Calculate the size of the unsubscribe packet. * The fixed header is always 2 bytes, the packet ID is always 2 bytes @@ -2766,6 +2801,18 @@ static uint16_t s_aws_mqtt_client_connection_311_unsubscribe( return 0; } +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; + + return aws_mqtt_client_connection_311_unsubscribe( + connection, topic_filter, on_unsuback, on_unsuback_ud, connection->operation_timeout_ns); +} + /******************************************************************************* * Publish ******************************************************************************/ @@ -2971,16 +3018,15 @@ static void s_publish_complete( aws_mem_release(connection->allocator, task_arg); } -static uint16_t s_aws_mqtt_client_connection_311_publish( - void *impl, +uint16_t aws_mqtt_client_connection_311_publish( + struct aws_mqtt_client_connection_311_impl *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) { - - struct aws_mqtt_client_connection_311_impl *connection = impl; + void *userdata, + uint64_t timeout_ns) { AWS_PRECONDITION(connection); @@ -3004,7 +3050,7 @@ static uint16_t s_aws_mqtt_client_connection_311_publish( arg->topic = aws_byte_cursor_from_string(arg->topic_string); arg->qos = qos; arg->retain = retain; - arg->timeout_duration_in_ns = connection->operation_timeout_ns; + arg->timeout_duration_in_ns = timeout_ns; struct aws_byte_cursor payload_cursor; AWS_ZERO_STRUCT(payload_cursor); @@ -3062,6 +3108,21 @@ static uint16_t s_aws_mqtt_client_connection_311_publish( return 0; } +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, + const struct aws_byte_cursor *payload, + aws_mqtt_op_complete_fn *on_complete, + void *userdata) { + + struct aws_mqtt_client_connection_311_impl *connection = impl; + + return aws_mqtt_client_connection_311_publish( + connection, topic, qos, retain, payload, on_complete, userdata, connection->operation_timeout_ns); +} + /******************************************************************************* * Ping ******************************************************************************/ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c0c6e275..658da749 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,8 +78,10 @@ add_test_case(mqtt_clean_session_not_retry) add_test_case(mqtt_clean_session_discard_previous) add_test_case(mqtt_clean_session_keep_next_session) add_test_case(mqtt_connection_publish_QoS1_timeout) -add_test_case(mqtt_connection_unsub_timeout) -add_test_case(mqtt_connection_sub_timeout) +add_test_case(mqtt_connection_unsubscribe_timeout) +add_test_case(mqtt_connection_subscribe_single_timeout) +add_test_case(mqtt_connection_subscribe_multi_timeout) +add_test_case(mqtt_connection_resubscribe_timeout) add_test_case(mqtt_connection_publish_QoS1_timeout_connection_lost_reset_time) add_test_case(mqtt_connection_ping_norm) add_test_case(mqtt_connection_ping_no) diff --git a/tests/v3/connection_state_test.c b/tests/v3/connection_state_test.c index f790adcc..149d8c25 100644 --- a/tests/v3/connection_state_test.c +++ b/tests/v3/connection_state_test.c @@ -2379,7 +2379,7 @@ AWS_TEST_CASE_FIXTURE( /** * Test that connection is healthy, user set the timeout for request, and timeout happens and the unsubscribe failed. */ -static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocator, void *ctx) { +static int s_test_mqtt_connection_unsubscribe_timeout_fn(struct aws_allocator *allocator, void *ctx) { (void)allocator; struct mqtt_connection_state_test *state_test_data = ctx; @@ -2424,16 +2424,16 @@ static int s_test_mqtt_connection_unsub_timeout_fn(struct aws_allocator *allocat } AWS_TEST_CASE_FIXTURE( - mqtt_connection_unsub_timeout, + mqtt_connection_unsubscribe_timeout, s_setup_mqtt_server_fn, - s_test_mqtt_connection_unsub_timeout_fn, + s_test_mqtt_connection_unsubscribe_timeout_fn, s_clean_up_mqtt_server_fn, &test_data) /** * Test that connection is healthy, user set the timeout for request, and timeout happens and the subscribe failed. */ -static int s_test_mqtt_connection_sub_timeout_fn(struct aws_allocator *allocator, void *ctx) { +static int s_test_mqtt_connection_subscribe_single_timeout_fn(struct aws_allocator *allocator, void *ctx) { (void)allocator; struct mqtt_connection_state_test *state_test_data = ctx; @@ -2481,9 +2481,157 @@ static int s_test_mqtt_connection_sub_timeout_fn(struct aws_allocator *allocator } AWS_TEST_CASE_FIXTURE( - mqtt_connection_sub_timeout, + mqtt_connection_subscribe_single_timeout, s_setup_mqtt_server_fn, - s_test_mqtt_connection_sub_timeout_fn, + s_test_mqtt_connection_subscribe_single_timeout_fn, + s_clean_up_mqtt_server_fn, + &test_data) + +/** + * Test that connection is healthy, user set the timeout for request, and timeout happens and the multi-subscribe + * failed. + */ +static int s_test_mqtt_connection_subscribe_multi_timeout_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + struct mqtt_connection_state_test *state_test_data = ctx; + + struct aws_mqtt_connection_options connection_options = { + .user_data = state_test_data, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), + .socket_options = &state_test_data->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .protocol_operation_timeout_ms = 3000, + .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(state_test_data); + + /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ + mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); + + struct aws_byte_cursor sub_topic_1 = aws_byte_cursor_from_c_str("/test/topic1"); + struct aws_byte_cursor sub_topic_2 = aws_byte_cursor_from_c_str("/test/topic2"); + + struct aws_mqtt_topic_subscription sub1 = { + .topic = sub_topic_1, + .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, + .on_publish = aws_test311_on_publish_received, + .on_cleanup = NULL, + .on_publish_ud = state_test_data, + }; + struct aws_mqtt_topic_subscription sub2 = { + .topic = sub_topic_2, + .qos = AWS_MQTT_QOS_AT_LEAST_ONCE, + .on_publish = aws_test311_on_publish_received, + .on_cleanup = NULL, + .on_publish_ud = state_test_data, + }; + + struct aws_array_list topic_filters; + size_t list_len = 2; + AWS_VARIABLE_LENGTH_ARRAY(uint8_t, static_buf, list_len * sizeof(struct aws_mqtt_topic_subscription)); + aws_array_list_init_static(&topic_filters, static_buf, list_len, sizeof(struct aws_mqtt_topic_subscription)); + + aws_array_list_push_back(&topic_filters, &sub1); + aws_array_list_push_back(&topic_filters, &sub2); + + uint16_t sub_packet_id = aws_mqtt_client_connection_subscribe_multiple( + state_test_data->mqtt_connection, &topic_filters, aws_test311_on_multi_suback, state_test_data); + + ASSERT_TRUE(sub_packet_id > 0); + + /* subscribe should complete after the timeout */ + aws_test311_wait_for_subscribe_to_complete(state_test_data); + /* Check that the subscribe has been completed with a timeout error */ + ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_MQTT_TIMEOUT); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE_FIXTURE( + mqtt_connection_subscribe_multi_timeout, + s_setup_mqtt_server_fn, + s_test_mqtt_connection_subscribe_multi_timeout_fn, + s_clean_up_mqtt_server_fn, + &test_data) + +/** + * Test that connection is healthy, user set the timeout for request, and timeout happens and the subscribe failed. + */ +static int s_test_mqtt_connection_resubscribe_timeout_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + struct mqtt_connection_state_test *state_test_data = ctx; + + struct aws_mqtt_connection_options connection_options = { + .user_data = state_test_data, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), + .socket_options = &state_test_data->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .protocol_operation_timeout_ms = 3000, + .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(state_test_data); + + /* subscribe */ + struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); + uint16_t sub_packet_id = aws_mqtt_client_connection_subscribe( + state_test_data->mqtt_connection, + &pub_topic, + AWS_MQTT_QOS_AT_LEAST_ONCE, + aws_test311_on_publish_received, + state_test_data, + NULL, + aws_test311_on_suback, + state_test_data); + ASSERT_TRUE(sub_packet_id > 0); + + /* subscribe should complete after the timeout */ + aws_test311_wait_for_subscribe_to_complete(state_test_data); + ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_SUCCESS); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); + + /* reconnection to the same server */ + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(state_test_data); + + /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ + mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); + + uint16_t resub_packet_id = aws_mqtt_resubscribe_existing_topics( + state_test_data->mqtt_connection, aws_test311_on_multi_suback, state_test_data); + ASSERT_TRUE(resub_packet_id > 0); + aws_test311_wait_for_subscribe_to_complete(state_test_data); + + /* Check that the resubscribe has been completed with a timeout error */ + ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_MQTT_TIMEOUT); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE_FIXTURE( + mqtt_connection_resubscribe_timeout, + s_setup_mqtt_server_fn, + s_test_mqtt_connection_resubscribe_timeout_fn, s_clean_up_mqtt_server_fn, &test_data) diff --git a/tests/v3/mqtt311_testing_utils.c b/tests/v3/mqtt311_testing_utils.c index 52a55e92..738443d9 100644 --- a/tests/v3/mqtt311_testing_utils.c +++ b/tests/v3/mqtt311_testing_utils.c @@ -538,6 +538,7 @@ void aws_test311_on_multi_suback( aws_mutex_lock(&state_test_data->lock); state_test_data->subscribe_completed = true; + state_test_data->subscribe_complete_error = error_code; if (!error_code) { size_t length = aws_array_list_length(topic_subacks); for (size_t i = 0; i < length; ++i) { From d3055a1c791df7f17c520f179ddbfe90b28749e7 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 29 Jan 2024 14:53:02 -0800 Subject: [PATCH 035/124] override tests --- source/client.c | 2 +- tests/CMakeLists.txt | 3 + tests/v3/connection_state_test.c | 175 +++++++++++++++++++++++++++++++ tests/v3/mqtt311_testing_utils.c | 4 +- 4 files changed, 181 insertions(+), 3 deletions(-) diff --git a/source/client.c b/source/client.c index d8cbe85e..fb436249 100644 --- a/source/client.c +++ b/source/client.c @@ -148,7 +148,7 @@ static struct request_timeout_task_arg *s_schedule_timeout_task( aws_mem_release(connection->allocator, timeout_task_arg); return NULL; } - timestamp = aws_add_u64_saturating(timestamp, connection->operation_timeout_ns); + timestamp = aws_add_u64_saturating(timestamp, timeout_duration_in_ns); aws_channel_schedule_task_future(connection->slot->channel, request_timeout_task, timestamp); return timeout_task_arg; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 658da749..018ad5d0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,8 +78,11 @@ add_test_case(mqtt_clean_session_not_retry) add_test_case(mqtt_clean_session_discard_previous) add_test_case(mqtt_clean_session_keep_next_session) add_test_case(mqtt_connection_publish_QoS1_timeout) +add_test_case(mqtt_connection_publish_QoS1_timeout_override) add_test_case(mqtt_connection_unsubscribe_timeout) +add_test_case(mqtt_connection_unsubscribe_timeout_override) add_test_case(mqtt_connection_subscribe_single_timeout) +add_test_case(mqtt_connection_subscribe_single_timeout_override) add_test_case(mqtt_connection_subscribe_multi_timeout) add_test_case(mqtt_connection_resubscribe_timeout) add_test_case(mqtt_connection_publish_QoS1_timeout_connection_lost_reset_time) diff --git a/tests/v3/connection_state_test.c b/tests/v3/connection_state_test.c index 149d8c25..6447bb7b 100644 --- a/tests/v3/connection_state_test.c +++ b/tests/v3/connection_state_test.c @@ -9,6 +9,7 @@ #include "mqtt_mock_server_handler.h" #include +#include #include static struct mqtt_connection_state_test test_data = {0}; @@ -2376,6 +2377,66 @@ AWS_TEST_CASE_FIXTURE( s_clean_up_mqtt_server_fn, &test_data) +/** + * Test that connection is healthy, user set the timeout for request, and timeout happens and the publish failed. + */ +static int s_test_mqtt_connection_publish_QoS1_timeout_override_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + struct mqtt_connection_state_test *state_test_data = ctx; + + struct aws_mqtt_connection_options connection_options = { + .user_data = state_test_data, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), + .socket_options = &state_test_data->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ + }; + + struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); + struct aws_byte_cursor payload_1 = aws_byte_cursor_from_c_str("Test Message 1"); + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(state_test_data); + + /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ + mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); + + /* make a publish with QoS 1 immediate. */ + aws_mutex_lock(&state_test_data->lock); + state_test_data->expected_ops_completed = 1; + aws_mutex_unlock(&state_test_data->lock); + uint16_t packet_id_1 = aws_mqtt_client_connection_311_publish( + state_test_data->mqtt_connection->impl, + &pub_topic, + AWS_MQTT_QOS_AT_LEAST_ONCE, + false, + &payload_1, + aws_test311_on_op_complete, + state_test_data, + aws_timestamp_convert(3, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); + ASSERT_TRUE(packet_id_1 > 0); + + /* publish should complete after the shutdown */ + aws_test311_wait_for_ops_completed(state_test_data); + /* Check the publish has been completed with timeout error */ + ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_TIMEOUT); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE_FIXTURE( + mqtt_connection_publish_QoS1_timeout_override, + s_setup_mqtt_server_fn, + s_test_mqtt_connection_publish_QoS1_timeout_override_fn, + s_clean_up_mqtt_server_fn, + &test_data) + /** * Test that connection is healthy, user set the timeout for request, and timeout happens and the unsubscribe failed. */ @@ -2430,6 +2491,63 @@ AWS_TEST_CASE_FIXTURE( s_clean_up_mqtt_server_fn, &test_data) +/** + * Test that connection is healthy, user set the timeout for request, and timeout happens and the unsubscribe failed. + */ +static int s_test_mqtt_connection_unsubscribe_timeout_override_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + struct mqtt_connection_state_test *state_test_data = ctx; + + struct aws_mqtt_connection_options connection_options = { + .user_data = state_test_data, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), + .socket_options = &state_test_data->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ + }; + + struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(state_test_data); + + /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ + mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); + + aws_mutex_lock(&state_test_data->lock); + state_test_data->expected_ops_completed = 1; + aws_mutex_unlock(&state_test_data->lock); + + /* unsubscribe to the first topic */ + uint16_t unsub_packet_id = aws_mqtt_client_connection_311_unsubscribe( + state_test_data->mqtt_connection->impl, + &pub_topic, + aws_test311_on_op_complete, + state_test_data, + aws_timestamp_convert(3, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); + ASSERT_TRUE(unsub_packet_id > 0); + + /* unsubscribe should complete after the timeout */ + aws_test311_wait_for_ops_completed(state_test_data); + /* Check that the unsubscribe has been completed with a timeout error */ + ASSERT_UINT_EQUALS(state_test_data->op_complete_error, AWS_ERROR_MQTT_TIMEOUT); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE_FIXTURE( + mqtt_connection_unsubscribe_timeout_override, + s_setup_mqtt_server_fn, + s_test_mqtt_connection_unsubscribe_timeout_override_fn, + s_clean_up_mqtt_server_fn, + &test_data) + /** * Test that connection is healthy, user set the timeout for request, and timeout happens and the subscribe failed. */ @@ -2487,6 +2605,63 @@ AWS_TEST_CASE_FIXTURE( s_clean_up_mqtt_server_fn, &test_data) +/** + * Test that connection is healthy, user set the timeout for request, and timeout happens and the subscribe failed. + */ +static int s_test_mqtt_connection_subscribe_single_timeout_override_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + struct mqtt_connection_state_test *state_test_data = ctx; + + struct aws_mqtt_connection_options connection_options = { + .user_data = state_test_data, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(state_test_data->endpoint.address), + .socket_options = &state_test_data->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(state_test_data->mqtt_connection, &connection_options)); + aws_test311_wait_for_connection_to_complete(state_test_data); + + /* Disable the auto ACK packets sent by the server, which blocks the requests to complete */ + mqtt_mock_server_disable_auto_ack(state_test_data->mock_server); + + /* subscribe */ + struct aws_byte_cursor pub_topic = aws_byte_cursor_from_c_str("/test/topic"); + uint16_t sub_packet_id = aws_mqtt_client_connection_311_subscribe( + state_test_data->mqtt_connection->impl, + &pub_topic, + AWS_MQTT_QOS_AT_LEAST_ONCE, + aws_test311_on_publish_received, + state_test_data, + NULL, + aws_test311_on_suback, + state_test_data, + aws_timestamp_convert(3, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); + ASSERT_TRUE(sub_packet_id > 0); + + /* subscribe should complete after the timeout */ + aws_test311_wait_for_subscribe_to_complete(state_test_data); + /* Check that the subscribe has been completed with a timeout error */ + ASSERT_UINT_EQUALS(state_test_data->subscribe_complete_error, AWS_ERROR_MQTT_TIMEOUT); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + aws_test311_wait_for_disconnect_to_complete(state_test_data); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE_FIXTURE( + mqtt_connection_subscribe_single_timeout_override, + s_setup_mqtt_server_fn, + s_test_mqtt_connection_subscribe_single_timeout_override_fn, + s_clean_up_mqtt_server_fn, + &test_data) + /** * Test that connection is healthy, user set the timeout for request, and timeout happens and the multi-subscribe * failed. diff --git a/tests/v3/mqtt311_testing_utils.c b/tests/v3/mqtt311_testing_utils.c index 738443d9..4f3781d1 100644 --- a/tests/v3/mqtt311_testing_utils.c +++ b/tests/v3/mqtt311_testing_utils.c @@ -575,7 +575,7 @@ static bool s_is_ops_completed(void *arg) { void aws_test311_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data) { aws_mutex_lock(&state_test_data->lock); - aws_condition_variable_wait_for_pred( - &state_test_data->cvar, &state_test_data->lock, 10000000000, s_is_ops_completed, state_test_data); + aws_condition_variable_wait_pred( + &state_test_data->cvar, &state_test_data->lock, s_is_ops_completed, state_test_data); aws_mutex_unlock(&state_test_data->lock); } \ No newline at end of file From 4783ea1cfed65a8c2ab57e325db5ad32a5c25071 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 30 Jan 2024 11:06:17 -0800 Subject: [PATCH 036/124] On further reflection, we need connection state in the subscription manager --- .../request-response/protocol_adapter.h | 21 +++++- source/request-response/protocol_adapter.c | 40 ++++++++--- tests/CMakeLists.txt | 4 +- .../request_response_protocol_adapter_tests.c | 69 +++++++++++-------- 4 files changed, 92 insertions(+), 42 deletions(-) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index f09d6135..6561c5ab 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -88,10 +88,16 @@ struct aws_protocol_adapter_incoming_publish_event { struct aws_byte_cursor payload; }; +enum aws_protocol_adapter_connection_event_type { + AWS_PACET_CONNECTED, + AWS_PACET_DISCONNECTED, +}; + /* * An event emitted by the protocol adapter whenever the protocol client successfully reconnects to the broker. */ -struct aws_protocol_adapter_session_event { +struct aws_protocol_adapter_connection_event { + enum aws_protocol_adapter_connection_event_type event_type; bool joined_session; }; @@ -101,7 +107,8 @@ typedef void(aws_protocol_adapter_incoming_publish_fn)( struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data); typedef void(aws_protocol_adapter_terminate_callback_fn)(void *user_data); -typedef void(aws_protocol_adapter_session_event_fn)(struct aws_protocol_adapter_session_event *event, void *user_data); +typedef void( + aws_protocol_adapter_connection_event_fn)(struct aws_protocol_adapter_connection_event *event, void *user_data); /* * Set of callbacks invoked by the protocol adapter. These must all be set. @@ -110,7 +117,7 @@ struct aws_mqtt_protocol_adapter_options { aws_protocol_adapter_subscription_event_fn *subscription_event_callback; aws_protocol_adapter_incoming_publish_fn *incoming_publish_callback; aws_protocol_adapter_terminate_callback_fn *terminate_callback; - aws_protocol_adapter_session_event_fn *session_event_callback; + aws_protocol_adapter_connection_event_fn *connection_event_callback; /* * User data to pass into all singleton protocol adapter callbacks. Likely either the request-response client @@ -128,6 +135,8 @@ struct aws_mqtt_protocol_adapter_vtable { int (*aws_mqtt_protocol_adapter_unsubscribe_fn)(void *, struct aws_protocol_adapter_unsubscribe_options *); int (*aws_mqtt_protocol_adapter_publish_fn)(void *, struct aws_protocol_adapter_publish_options *); + + bool (*aws_mqtt_protocol_adapter_is_connected_fn)(void *); }; struct aws_mqtt_protocol_adapter { @@ -180,6 +189,12 @@ AWS_MQTT_API int aws_mqtt_protocol_adapter_publish( struct aws_mqtt_protocol_adapter *adapter, struct aws_protocol_adapter_publish_options *options); +/* + * Synchronously checks the connection state of the adapted protocol client. May only be called from the + * protocol client's event loop. + */ +AWS_MQTT_API bool aws_mqtt_protocol_adapter_is_connected(struct aws_mqtt_protocol_adapter *adapter); + AWS_EXTERN_C_END #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_PROTOCOL_ADAPTER_H */ diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 68cc8667..ac8105d4 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -309,6 +309,16 @@ int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapte return AWS_OP_ERR; } +static bool s_aws_mqtt_protocol_adapter_5_is_connected(void *impl) { + struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(adapter->client->loop)); + + enum aws_mqtt5_client_state current_state = adapter->client->current_state; + + return current_state == AWS_MCS_CONNECTED; +} + static bool s_protocol_adapter_mqtt5_listener_publish_received( const struct aws_mqtt5_packet_publish_view *publish, void *user_data) { @@ -327,15 +337,25 @@ static bool s_protocol_adapter_mqtt5_listener_publish_received( static void s_protocol_adapter_mqtt5_lifecycle_event_callback(const struct aws_mqtt5_client_lifecycle_event *event) { struct aws_mqtt_protocol_adapter_5_impl *adapter = event->user_data; - if (event->event_type != AWS_MQTT5_CLET_CONNECTION_SUCCESS) { - return; - } + switch (event->event_type) { + case AWS_MQTT5_CLET_CONNECTION_SUCCESS: { + struct aws_protocol_adapter_connection_event connection_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = event->settings->rejoined_session, + }; - struct aws_protocol_adapter_session_event session_event = { - .joined_session = event->settings->rejoined_session, - }; + (*adapter->config.connection_event_callback)(&connection_event, adapter->config.user_data); + } + case AWS_MQTT5_CLET_DISCONNECTION: { + struct aws_protocol_adapter_connection_event connection_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; - (*adapter->config.session_event_callback)(&session_event, adapter->config.user_data); + (*adapter->config.connection_event_callback)(&connection_event, adapter->config.user_data); + } + default: + break; + } } static void s_protocol_adapter_mqtt5_listener_termination_callback(void *user_data) { @@ -363,7 +383,7 @@ static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt5_vtable = .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_5_subscribe, .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_5_unsubscribe, .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_5_publish, -}; + .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_5_is_connected}; struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( struct aws_allocator *allocator, @@ -425,3 +445,7 @@ int aws_mqtt_protocol_adapter_publish( struct aws_protocol_adapter_publish_options *options) { return (*adapter->vtable->aws_mqtt_protocol_adapter_publish_fn)(adapter->impl, options); } + +bool aws_mqtt_protocol_adapter_is_connected(struct aws_mqtt_protocol_adapter *adapter) { + return (*adapter->vtable->aws_mqtt_protocol_adapter_is_connected_fn)(adapter->impl); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d93b7427..da1bf3a4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -454,8 +454,8 @@ add_test_case(request_response_mqtt5_protocol_adapter_publish_success) add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_error_code) add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_reason_code) add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_timeout) -add_test_case(request_response_mqtt5_protocol_adapter_session_event_no_rejoin) -add_test_case(request_response_mqtt5_protocol_adapter_session_event_rejoin) +add_test_case(request_response_mqtt5_protocol_adapter_connection_event_connect_no_session) +add_test_case(request_response_mqtt5_protocol_adapter_connection_event_connect_session) add_test_case(request_response_mqtt5_protocol_adapter_incoming_publish) add_test_case(request_response_mqtt5_protocol_adapter_shutdown_while_pending) diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c index 2034f460..2ffca2aa 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -63,7 +63,7 @@ struct aws_request_response_mqtt5_adapter_test_fixture { struct aws_mqtt_protocol_adapter *protocol_adapter; struct aws_array_list incoming_publish_events; - struct aws_array_list session_events; + struct aws_array_list connection_events; struct aws_array_list subscription_events; struct aws_array_list publish_results; @@ -113,13 +113,13 @@ static void s_rr_mqtt5_protocol_adapter_test_on_terminate_callback(void *user_da aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_session_event( - struct aws_protocol_adapter_session_event *event, +static void s_rr_mqtt5_protocol_adapter_test_on_connection_event( + struct aws_protocol_adapter_connection_event *event, void *user_data) { struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; aws_mutex_lock(&fixture->lock); - aws_array_list_push_back(&fixture->session_events, event); + aws_array_list_push_back(&fixture->connection_events, event); aws_mutex_unlock(&fixture->lock); aws_condition_variable_notify_all(&fixture->signal); } @@ -150,7 +150,7 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( .subscription_event_callback = s_rr_mqtt5_protocol_adapter_test_on_subscription_event, .incoming_publish_callback = s_rr_mqtt5_protocol_adapter_test_on_incoming_publish, .terminate_callback = s_rr_mqtt5_protocol_adapter_test_on_terminate_callback, - .session_event_callback = s_rr_mqtt5_protocol_adapter_test_on_session_event, + .connection_event_callback = s_rr_mqtt5_protocol_adapter_test_on_connection_event, .user_data = fixture}; fixture->protocol_adapter = @@ -163,7 +163,7 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( 10, sizeof(struct request_response_protocol_adapter_incoming_publish_event_record)); aws_array_list_init_dynamic( - &fixture->session_events, allocator, 10, sizeof(struct aws_protocol_adapter_session_event)); + &fixture->connection_events, allocator, 10, sizeof(struct aws_protocol_adapter_connection_event)); aws_array_list_init_dynamic( &fixture->subscription_events, allocator, @@ -216,7 +216,7 @@ static void s_aws_request_response_mqtt5_adapter_test_fixture_clean_up( } aws_array_list_clean_up(&fixture->incoming_publish_events); - aws_array_list_clean_up(&fixture->session_events); + aws_array_list_clean_up(&fixture->connection_events); aws_array_list_clean_up(&fixture->publish_results); aws_mutex_clean_up(&fixture->lock); @@ -276,21 +276,25 @@ static void s_wait_for_subscription_events_contains( aws_mutex_unlock(&fixture->lock); } -struct test_session_event_wait_context { - struct aws_protocol_adapter_session_event *expected_event; +struct test_connection_event_wait_context { + struct aws_protocol_adapter_connection_event *expected_event; size_t expected_count; struct aws_request_response_mqtt5_adapter_test_fixture *fixture; }; -static bool s_do_session_events_contain(void *context) { - struct test_session_event_wait_context *wait_context = context; +static bool s_do_connection_events_contain(void *context) { + struct test_connection_event_wait_context *wait_context = context; size_t found = 0; - size_t num_events = aws_array_list_length(&wait_context->fixture->session_events); + size_t num_events = aws_array_list_length(&wait_context->fixture->connection_events); for (size_t i = 0; i < num_events; ++i) { - struct aws_protocol_adapter_session_event record; - aws_array_list_get_at(&wait_context->fixture->session_events, &record, i); + struct aws_protocol_adapter_connection_event record; + aws_array_list_get_at(&wait_context->fixture->connection_events, &record, i); + + if (record.event_type != wait_context->expected_event->event_type) { + continue; + } if (record.joined_session != wait_context->expected_event->joined_session) { continue; @@ -302,19 +306,19 @@ static bool s_do_session_events_contain(void *context) { return found >= wait_context->expected_count; } -static void s_wait_for_session_events_contains( +static void s_wait_for_connection_events_contains( struct aws_request_response_mqtt5_adapter_test_fixture *fixture, - struct aws_protocol_adapter_session_event *expected_event, + struct aws_protocol_adapter_connection_event *expected_event, size_t expected_count) { - struct test_session_event_wait_context context = { + struct test_connection_event_wait_context context = { .expected_event = expected_event, .expected_count = expected_count, .fixture = fixture, }; aws_mutex_lock(&fixture->lock); - aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_do_session_events_contain, &context); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_do_connection_events_contain, &context); aws_mutex_unlock(&fixture->lock); } @@ -843,7 +847,7 @@ AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_publish_failure_error_code, s_request_response_mqtt5_protocol_adapter_publish_failure_error_code_fn) -static int s_do_request_response_mqtt5_protocol_adapter_session_event_test( +static int s_do_request_response_mqtt5_protocol_adapter_connection_event_connect_test( struct aws_allocator *allocator, bool rejoin_session) { aws_mqtt_library_init(allocator); @@ -866,16 +870,23 @@ static int s_do_request_response_mqtt5_protocol_adapter_session_event_test( struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; - struct aws_protocol_adapter_session_event expected_session_record = { + struct aws_protocol_adapter_connection_event expected_connect_record = { + .event_type = AWS_PACET_CONNECTED, .joined_session = rejoin_session, }; ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - s_wait_for_session_events_contains(&fixture, &expected_session_record, 1); + s_wait_for_connection_events_contains(&fixture, &expected_connect_record, 1); ASSERT_SUCCESS(aws_mqtt5_client_stop(client, NULL, NULL)); aws_wait_for_stopped_lifecycle_event(&fixture.mqtt5_fixture); + struct aws_protocol_adapter_connection_event expected_disconnect_record = { + .event_type = AWS_PACET_DISCONNECTED, + }; + + s_wait_for_connection_events_contains(&fixture, &expected_disconnect_record, 1); + s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -883,29 +894,29 @@ static int s_do_request_response_mqtt5_protocol_adapter_session_event_test( return AWS_OP_SUCCESS; } -static int s_request_response_mqtt5_protocol_adapter_session_event_no_rejoin_fn( +static int s_request_response_mqtt5_protocol_adapter_connection_event_connect_no_session_fn( struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_request_response_mqtt5_protocol_adapter_session_event_test(allocator, false); + return s_do_request_response_mqtt5_protocol_adapter_connection_event_connect_test(allocator, false); } AWS_TEST_CASE( - request_response_mqtt5_protocol_adapter_session_event_no_rejoin, - s_request_response_mqtt5_protocol_adapter_session_event_no_rejoin_fn) + request_response_mqtt5_protocol_adapter_connection_event_connect_no_session, + s_request_response_mqtt5_protocol_adapter_connection_event_connect_no_session_fn) -static int s_request_response_mqtt5_protocol_adapter_session_event_rejoin_fn( +static int s_request_response_mqtt5_protocol_adapter_connection_event_connect_session_fn( struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_request_response_mqtt5_protocol_adapter_session_event_test(allocator, true); + return s_do_request_response_mqtt5_protocol_adapter_connection_event_connect_test(allocator, true); } AWS_TEST_CASE( - request_response_mqtt5_protocol_adapter_session_event_rejoin, - s_request_response_mqtt5_protocol_adapter_session_event_rejoin_fn) + request_response_mqtt5_protocol_adapter_connection_event_connect_session, + s_request_response_mqtt5_protocol_adapter_connection_event_connect_session_fn) static int s_request_response_mqtt5_protocol_adapter_incoming_publish_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; From 165827ed1cfcb47022c4d3b33ada9f5d358a9834 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 30 Jan 2024 12:16:32 -0800 Subject: [PATCH 037/124] Checkpoint --- include/aws/mqtt/private/mqtt311_listener.h | 13 +++++++++++++ source/mqtt311_listener.c | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/aws/mqtt/private/mqtt311_listener.h b/include/aws/mqtt/private/mqtt311_listener.h index 2a4f5b53..6d87db67 100644 --- a/include/aws/mqtt/private/mqtt311_listener.h +++ b/include/aws/mqtt/private/mqtt311_listener.h @@ -37,6 +37,9 @@ struct aws_mqtt311_callback_set { /* Called from s_packet_handler_connack which is event-loop invoked */ aws_mqtt_client_on_connection_success_fn *connection_success_handler; + /* Called from s_mqtt_client_shutdown which is event-loop invoked */ + aws_mqtt_client_on_connection_interrupted_fn *connection_interrupted_handler; + void *user_data; }; @@ -173,6 +176,16 @@ void aws_mqtt311_callback_set_manager_on_connection_success( enum aws_mqtt_connect_return_code return_code, bool rejoined_session); +/** + * Invokes a connection interrupted event on each listener in the manager's collection of callback sets. + * + * May only be called on the client's event loop thread. + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_on_connection_interrupted( + struct aws_mqtt311_callback_set_manager *manager, + int error_code); + AWS_EXTERN_C_END AWS_POP_SANE_WARNING_LEVEL diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c index caf9b650..863e9864 100644 --- a/source/mqtt311_listener.c +++ b/source/mqtt311_listener.c @@ -289,3 +289,13 @@ void aws_mqtt311_callback_set_manager_on_connection_success( } } } + +void aws_mqtt311_callback_set_manager_on_connection_interrupted( + struct aws_mqtt311_callback_set_manager *manager, + int error_code) { + + struct aws_mqtt_client_connection_311_impl *connection_impl = manager->connection->impl; + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection_impl->loop)); + + +} From 38532f0b8a5aec5a8988cea2cbab88912688794e Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 30 Jan 2024 12:17:17 -0800 Subject: [PATCH 038/124] Oops --- source/request-response/protocol_adapter.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index ac8105d4..229b1edd 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -345,6 +345,7 @@ static void s_protocol_adapter_mqtt5_lifecycle_event_callback(const struct aws_m }; (*adapter->config.connection_event_callback)(&connection_event, adapter->config.user_data); + break; } case AWS_MQTT5_CLET_DISCONNECTION: { struct aws_protocol_adapter_connection_event connection_event = { @@ -352,6 +353,7 @@ static void s_protocol_adapter_mqtt5_lifecycle_event_callback(const struct aws_m }; (*adapter->config.connection_event_callback)(&connection_event, adapter->config.user_data); + break; } default: break; From 8cd978a00ac4ee08ba0a71e0979b320843f41b72 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 30 Jan 2024 13:46:38 -0800 Subject: [PATCH 039/124] Need both interrupt and disconnect events --- include/aws/mqtt/private/mqtt311_listener.h | 13 +- source/client.c | 3 + source/mqtt311_listener.c | 30 +++- tests/CMakeLists.txt | 4 +- .../request_response_protocol_adapter_tests.c | 2 +- tests/v3/mqtt311_listener_test.c | 137 ++++++++++++++++-- 6 files changed, 172 insertions(+), 17 deletions(-) diff --git a/include/aws/mqtt/private/mqtt311_listener.h b/include/aws/mqtt/private/mqtt311_listener.h index 6d87db67..c66e4f24 100644 --- a/include/aws/mqtt/private/mqtt311_listener.h +++ b/include/aws/mqtt/private/mqtt311_listener.h @@ -38,7 +38,10 @@ struct aws_mqtt311_callback_set { aws_mqtt_client_on_connection_success_fn *connection_success_handler; /* Called from s_mqtt_client_shutdown which is event-loop invoked */ - aws_mqtt_client_on_connection_interrupted_fn *connection_interrupted_handler; + aws_mqtt_client_on_connection_interrupted_fn *connection_interrupted_handler; + + /* Called from s_mqtt_client_shutdown which is event-loop invoked */ + aws_mqtt_client_on_disconnect_fn *disconnect_handler; void *user_data; }; @@ -186,6 +189,14 @@ void aws_mqtt311_callback_set_manager_on_connection_interrupted( struct aws_mqtt311_callback_set_manager *manager, int error_code); +/** + * Invokes a disconnection event on each listener in the manager's collection of callback sets. + * + * May only be called on the client's event loop thread. + */ +AWS_MQTT_API +void aws_mqtt311_callback_set_manager_on_disconnect(struct aws_mqtt311_callback_set_manager *manager); + AWS_EXTERN_C_END AWS_POP_SANE_WARNING_LEVEL diff --git a/source/client.c b/source/client.c index 8a8bc386..2d896aeb 100644 --- a/source/client.c +++ b/source/client.c @@ -405,6 +405,7 @@ static void s_mqtt_client_shutdown( "id=%p: Connection interrupted, calling callback and attempting reconnect", (void *)connection); MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_interrupted, error_code); + aws_mqtt311_callback_set_manager_on_connection_interrupted(&connection->callback_manager, error_code); /* In case user called disconnect from the on_interrupted callback */ bool stop_reconnect; @@ -443,6 +444,7 @@ static void s_mqtt_client_shutdown( (void *)connection); MQTT_CLIENT_CALL_CALLBACK(connection, on_disconnect); MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_closed, NULL); + aws_mqtt311_callback_set_manager_on_disconnect(&connection->callback_manager); break; case AWS_MQTT_CLIENT_STATE_DISCONNECTING: AWS_LOGF_DEBUG( @@ -451,6 +453,7 @@ static void s_mqtt_client_shutdown( (void *)connection); MQTT_CLIENT_CALL_CALLBACK(connection, on_disconnect); MQTT_CLIENT_CALL_CALLBACK_ARGS(connection, on_closed, NULL); + aws_mqtt311_callback_set_manager_on_disconnect(&connection->callback_manager); break; case AWS_MQTT_CLIENT_STATE_CONNECTING: AWS_LOGF_TRACE( diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c index 863e9864..eab08983 100644 --- a/source/mqtt311_listener.c +++ b/source/mqtt311_listener.c @@ -297,5 +297,33 @@ void aws_mqtt311_callback_set_manager_on_connection_interrupted( struct aws_mqtt_client_connection_311_impl *connection_impl = manager->connection->impl; AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection_impl->loop)); - + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); + + struct aws_mqtt311_callback_set *callback_set = &entry->callbacks; + if (callback_set->connection_interrupted_handler != NULL) { + (*callback_set->connection_interrupted_handler)(manager->connection, error_code, callback_set->user_data); + } + } +} + +void aws_mqtt311_callback_set_manager_on_disconnect(struct aws_mqtt311_callback_set_manager *manager) { + + struct aws_mqtt_client_connection_311_impl *connection_impl = manager->connection->impl; + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection_impl->loop)); + + struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); + while (node != aws_linked_list_end(&manager->callback_set_entries)) { + struct aws_mqtt311_callback_set_entry *entry = + AWS_CONTAINER_OF(node, struct aws_mqtt311_callback_set_entry, node); + node = aws_linked_list_next(node); + + struct aws_mqtt311_callback_set *callback_set = &entry->callbacks; + if (callback_set->disconnect_handler != NULL) { + (*callback_set->disconnect_handler)(manager->connection, callback_set->user_data); + } + } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 71ebb5d9..a6080135 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -459,8 +459,8 @@ add_test_case(request_response_mqtt5_protocol_adapter_connection_event_connect_s add_test_case(request_response_mqtt5_protocol_adapter_incoming_publish) add_test_case(request_response_mqtt5_protocol_adapter_shutdown_while_pending) -add_test_case(mqtt311_listener_connection_success_event_no_session) -add_test_case(mqtt311_listener_connection_success_event_with_session) +add_test_case(mqtt311_listener_connection_events_no_session) +add_test_case(mqtt311_listener_connection_events_with_session) add_test_case(mqtt311_listener_publish_event) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c index 639900dc..2ffca2aa 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -299,7 +299,7 @@ static bool s_do_connection_events_contain(void *context) { if (record.joined_session != wait_context->expected_event->joined_session) { continue; } - + ++found; } diff --git a/tests/v3/mqtt311_listener_test.c b/tests/v3/mqtt311_listener_test.c index 758ee89f..e908ff9c 100644 --- a/tests/v3/mqtt311_listener_test.c +++ b/tests/v3/mqtt311_listener_test.c @@ -16,6 +16,10 @@ struct mqtt311_listener_connection_success_record { bool joined_session; }; +struct mqtt311_listener_connection_interrupted_record { + int error_code; +}; + struct mqtt311_listener_publish_record { struct aws_byte_buf topic; struct aws_byte_buf payload; @@ -30,7 +34,9 @@ struct mqtt311_listener_test_context { int mqtt311_test_context_setup_result; struct aws_array_list connection_success_events; + struct aws_array_list connection_interrupted_events; struct aws_array_list publish_events; + size_t disconnect_event_count; bool terminated; struct aws_mutex lock; @@ -84,6 +90,35 @@ static void s_311_listener_test_on_connection_success( aws_condition_variable_notify_all(&context->signal); } +static void s_311_listener_test_on_connection_interrupted( + struct aws_mqtt_client_connection *connection, + int error_code, + void *userdata) { + (void)connection; + + struct mqtt311_listener_test_context *context = userdata; + + struct mqtt311_listener_connection_interrupted_record connection_interrupted_record = { + .error_code = error_code, + }; + + aws_mutex_lock(&context->lock); + aws_array_list_push_back(&context->connection_interrupted_events, &connection_interrupted_record); + aws_mutex_unlock(&context->lock); + aws_condition_variable_notify_all(&context->signal); +} + +static void s_311_listener_test_on_disconnect(struct aws_mqtt_client_connection *connection, void *userdata) { + (void)connection; + + struct mqtt311_listener_test_context *context = userdata; + + aws_mutex_lock(&context->lock); + ++context->disconnect_event_count; + aws_mutex_unlock(&context->lock); + aws_condition_variable_notify_all(&context->signal); +} + static void s_311_listener_test_on_termination(void *complete_ctx) { struct mqtt311_listener_test_context *context = complete_ctx; @@ -104,6 +139,11 @@ static int mqtt311_listener_test_context_init( aws_array_list_init_dynamic( &context->connection_success_events, allocator, 10, sizeof(struct mqtt311_listener_connection_success_record)); + aws_array_list_init_dynamic( + &context->connection_interrupted_events, + allocator, + 10, + sizeof(struct mqtt311_listener_connection_interrupted_record)); aws_array_list_init_dynamic( &context->publish_events, allocator, 10, sizeof(struct mqtt311_listener_publish_record)); @@ -119,6 +159,8 @@ static int mqtt311_listener_test_context_init( { .publish_received_handler = s_311_listener_test_on_publish_received, .connection_success_handler = s_311_listener_test_on_connection_success, + .connection_interrupted_handler = s_311_listener_test_on_connection_interrupted, + .disconnect_handler = s_311_listener_test_on_disconnect, .user_data = context, }, .termination_callback = s_311_listener_test_on_termination, @@ -153,6 +195,7 @@ static void mqtt311_listener_test_context_clean_up(struct mqtt311_listener_test_ aws_condition_variable_clean_up(&context->signal); aws_array_list_clean_up(&context->connection_success_events); + aws_array_list_clean_up(&context->connection_interrupted_events); for (size_t i = 0; i < aws_array_list_length(&context->publish_events); ++i) { struct mqtt311_listener_publish_record publish_record; @@ -206,7 +249,70 @@ static void s_wait_for_connection_success_events( aws_mutex_unlock(&context->lock); } -static int s_do_mqtt311_listener_connection_success_event_test(struct aws_allocator *allocator, bool session_present) { +struct connection_interrupted_event_test_context { + struct mqtt311_listener_test_context *context; + int error_code; + size_t expected_count; +}; + +static bool s_contains_connection_interrupted_events(void *userdata) { + struct connection_interrupted_event_test_context *wait_context = userdata; + struct mqtt311_listener_test_context *context = wait_context->context; + + size_t found = 0; + for (size_t i = 0; i < aws_array_list_length(&context->connection_interrupted_events); ++i) { + struct mqtt311_listener_connection_interrupted_record record; + aws_array_list_get_at(&context->connection_interrupted_events, &record, i); + + if (record.error_code != wait_context->error_code) { + continue; + } + + ++found; + } + + return found >= wait_context->expected_count; +} + +static void s_wait_for_connection_interrupted_events( + struct mqtt311_listener_test_context *context, + int error_code, + size_t expected_count) { + struct connection_interrupted_event_test_context wait_context = { + .context = context, + .error_code = error_code, + .expected_count = expected_count, + }; + + aws_mutex_lock(&context->lock); + aws_condition_variable_wait_pred( + &context->signal, &context->lock, s_contains_connection_interrupted_events, &wait_context); + aws_mutex_unlock(&context->lock); +} + +struct disconnect_event_test_context { + struct mqtt311_listener_test_context *context; + size_t expected_count; +}; + +static bool s_contains_disconnect_events(void *userdata) { + struct disconnect_event_test_context *wait_context = userdata; + + return wait_context->context->disconnect_event_count >= wait_context->expected_count; +} + +static void s_wait_for_disconnect_events(struct mqtt311_listener_test_context *context, size_t expected_count) { + struct disconnect_event_test_context wait_context = { + .context = context, + .expected_count = expected_count, + }; + + aws_mutex_lock(&context->lock); + aws_condition_variable_wait_pred(&context->signal, &context->lock, s_contains_disconnect_events, &wait_context); + aws_mutex_unlock(&context->lock); +} + +static int s_do_mqtt311_listener_connection_events_test(struct aws_allocator *allocator, bool session_present) { aws_mqtt_library_init(allocator); struct mqtt_connection_state_test mqtt311_context; @@ -224,6 +330,8 @@ static int s_do_mqtt311_listener_connection_success_event_test(struct aws_alloca .host_name = aws_byte_cursor_from_c_str(mqtt311_context.endpoint.address), .socket_options = &mqtt311_context.socket_options, .on_connection_complete = aws_test311_on_connection_complete_fn, + .keep_alive_time_secs = DEFAULT_TEST_KEEP_ALIVE_S, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, }; ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); @@ -235,15 +343,24 @@ static int s_do_mqtt311_listener_connection_success_event_test(struct aws_alloca mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); aws_test311_wait_for_disconnect_to_complete(&mqtt311_context); + s_wait_for_disconnect_events(&test_context, 1); + + mqtt_mock_server_set_max_ping_resp(mqtt311_context.mock_server, 0); + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt311_context.mqtt_connection, &connection_options)); aws_test311_wait_for_connection_to_complete(&mqtt311_context); - s_wait_for_connection_success_events(&test_context, session_present, 2); + // the ping configuration leads to an interruption event after 3 seconds due to ping timeout + s_wait_for_connection_interrupted_events(&test_context, AWS_ERROR_MQTT_TIMEOUT, 1); + + aws_test311_wait_for_reconnect_to_complete(&mqtt311_context); ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( mqtt311_context.mqtt_connection, aws_test311_on_disconnect_fn, &mqtt311_context)); aws_test311_wait_for_disconnect_to_complete(&mqtt311_context); + s_wait_for_disconnect_events(&test_context, 2); + mqtt311_listener_test_context_clean_up(&test_context); aws_mqtt_library_clean_up(); @@ -251,25 +368,21 @@ static int s_do_mqtt311_listener_connection_success_event_test(struct aws_alloca return AWS_OP_SUCCESS; } -static int s_mqtt311_listener_connection_success_event_no_session_fn(struct aws_allocator *allocator, void *ctx) { +static int s_mqtt311_listener_connection_events_no_session_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_mqtt311_listener_connection_success_event_test(allocator, false); + return s_do_mqtt311_listener_connection_events_test(allocator, false); } -AWS_TEST_CASE( - mqtt311_listener_connection_success_event_no_session, - s_mqtt311_listener_connection_success_event_no_session_fn) +AWS_TEST_CASE(mqtt311_listener_connection_events_no_session, s_mqtt311_listener_connection_events_no_session_fn) -static int s_mqtt311_listener_connection_success_event_with_session_fn(struct aws_allocator *allocator, void *ctx) { +static int s_mqtt311_listener_connection_events_with_session_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_mqtt311_listener_connection_success_event_test(allocator, true); + return s_do_mqtt311_listener_connection_events_test(allocator, true); } -AWS_TEST_CASE( - mqtt311_listener_connection_success_event_with_session, - s_mqtt311_listener_connection_success_event_with_session_fn) +AWS_TEST_CASE(mqtt311_listener_connection_events_with_session, s_mqtt311_listener_connection_events_with_session_fn) struct publish_event_test_context { struct mqtt311_listener_test_context *context; From d55553f9e9ad15507ab76233f1efac5f427a14b4 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 30 Jan 2024 15:04:55 -0800 Subject: [PATCH 040/124] feedback --- source/mqtt311_listener.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/mqtt311_listener.c b/source/mqtt311_listener.c index eab08983..69bec528 100644 --- a/source/mqtt311_listener.c +++ b/source/mqtt311_listener.c @@ -190,7 +190,7 @@ static struct aws_mqtt311_callback_set_entry *s_new_311_callback_set_entry( AWS_LOGF_INFO( AWS_LS_MQTT_GENERAL, - "id=%p: MQTT311 callback manager created new entry :%" PRIu64, + "id=%p: MQTT311 callback manager created new entry id=%" PRIu64, (void *)manager->connection, entry->id); From be95877689c49fcd4d3e7fa6e6fe0f277c516a8d Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 31 Jan 2024 15:26:39 -0800 Subject: [PATCH 041/124] Check point --- source/request-response/protocol_adapter.c | 421 +++++++++++++++++---- 1 file changed, 340 insertions(+), 81 deletions(-) diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 229b1edd..a3ae124b 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -5,8 +5,11 @@ #include +#include #include +#include #include +#include #include #include #include @@ -21,7 +24,7 @@ * * Additional Notes * - * Entries are not tracked with the exception of eventstream impl which needs the stream handles to close. + * Entries are not tracked with the exception of the eventstream impl which needs the stream handles to close. * * A subscribe failure does not trigger an unsubscribe, a status event. * @@ -31,66 +34,351 @@ * Retries, when appropriate, are the responsibility of the caller. */ -struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( +// used by both subscribe and unsubscribe +struct aws_mqtt_protocol_adapter_subscription_op_data { + struct aws_allocator *allocator; + + struct aws_byte_buf topic_filter; + struct aws_weak_ref *callback_ref; +}; + +static struct aws_mqtt_protocol_adapter_subscription_op_data *s_aws_mqtt_protocol_adapter_subscription_op_data_new( struct aws_allocator *allocator, - struct aws_mqtt_protocol_adapter_options *options, - struct aws_mqtt_client_connection *connection) { - (void)allocator; - (void)options; - (void)connection; + struct aws_byte_cursor topic_filter, + struct aws_weak_ref *callback_ref) { + struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_subscription_op_data)); + + subscribe_data->allocator = allocator; + subscribe_data->callback_ref = aws_weak_ref_acquire(callback_ref); + aws_byte_buf_init_copy_from_cursor(&subscribe_data->topic_filter, allocator, topic_filter); - // TODO - return NULL; + return subscribe_data; } -/******************************************************************************************************************/ +static void s_aws_mqtt_protocol_adapter_subscription_op_data_destroy( + struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data) { + aws_weak_ref_release(subscribe_data->callback_ref); + aws_byte_buf_clean_up(&subscribe_data->topic_filter); -struct aws_mqtt_protocol_adapter_5_impl { + aws_mem_release(subscribe_data->allocator, subscribe_data); +} + +struct aws_mqtt_protocol_adapter_publish_op_data { + struct aws_allocator *allocator; + struct aws_weak_ref *callback_ref; + + void (*completion_callback_fn)(int, void *); + void *user_data; +}; + +static struct aws_mqtt_protocol_adapter_publish_op_data *s_aws_mqtt_protocol_adapter_publish_op_data_new( + struct aws_allocator *allocator, + const struct aws_protocol_adapter_publish_options *publish_options, + struct aws_weak_ref *callback_ref) { + struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_publish_op_data)); + + publish_data->allocator = allocator; + publish_data->callback_ref = aws_weak_ref_acquire(callback_ref); + publish_data->completion_callback_fn = publish_options->completion_callback_fn; + publish_data->user_data = publish_options->user_data; + + return publish_data; +} + +static void s_aws_mqtt_protocol_adapter_publish_op_data_destroy( + struct aws_mqtt_protocol_adapter_publish_op_data *publish_data) { + aws_weak_ref_release(publish_data->callback_ref); + + aws_mem_release(publish_data->allocator, publish_data); +} + +/*****************************************************************************************************************/ + +struct aws_mqtt_protocol_adapter_311_impl { struct aws_allocator *allocator; struct aws_mqtt_protocol_adapter base; struct aws_weak_ref *callback_ref; struct aws_mqtt_protocol_adapter_options config; struct aws_event_loop *loop; - struct aws_mqtt5_client *client; - struct aws_mqtt5_listener *listener; + struct aws_mqtt_client_connection *connection; + struct aws_mqtt311_listener *listener; }; -static void s_aws_mqtt_protocol_adapter_5_destroy(void *impl) { - struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; +static void s_aws_mqtt_protocol_adapter_311_destroy(void *impl) { + struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; // all the real cleanup is done in the listener termination callback - aws_mqtt5_listener_release(adapter->listener); + aws_mqtt311_listener_release(adapter->listener); } -// used by both subscribe and unsubscribe -struct aws_mqtt_protocol_adapter_5_subscription_op_data { - struct aws_allocator *allocator; +/* Subscribe */ - struct aws_byte_buf topic_filter; - struct aws_weak_ref *callback_ref; +static void s_protocol_adapter_311_subscribe_completion( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + const struct aws_byte_cursor *topic, + enum aws_mqtt_qos qos, + int error_code, + void *userdata) { + (void)connection; + (void)topic; + (void)packet_id; + + struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = userdata; + struct aws_mqtt_protocol_adapter_311_impl *adapter = aws_weak_ref_get_reference(subscribe_data->callback_ref); + + if (adapter == NULL) { + goto done; + } + + if (error_code == AWS_ERROR_SUCCESS) { + if (qos >= 128) { + error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; + } + } + + struct aws_protocol_adapter_subscription_event subscribe_event = { + .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = error_code, + }; + + (*adapter->config.subscription_event_callback)(&subscribe_event, adapter->config.user_data); + +done: + + s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(subscribe_data); +} + +int s_aws_mqtt_protocol_adapter_311_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { + struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; + struct aws_mqtt_client_connection_311_impl *connection_impl = adapter->connection->impl; + + struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = + s_aws_mqtt_protocol_adapter_subscription_op_data_new( + adapter->allocator, options->topic_filter, adapter->callback_ref); + + uint64_t timeout_nanos = aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + if (aws_mqtt_client_connection_311_subscribe(connection_impl, &options->topic_filter, AWS_MQTT_QOS_AT_LEAST_ONCE, NULL, NULL, NULL, s_protocol_adapter_311_subscribe_completion, subscribe_data, timeout_nanos)) { + goto error; + } + + return AWS_OP_SUCCESS; + +error: + + s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(subscribe_data); + + return AWS_OP_ERR; +} + +/* Unsubscribe */ + +static void s_protocol_adapter_311_unsubscribe_completion( struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + int error_code, + void *userdata) { + (void)connection; + (void)packet_id; + + struct aws_mqtt_protocol_adapter_subscription_op_data *unsubscribe_data = userdata; + struct aws_mqtt_protocol_adapter_311_impl *adapter = aws_weak_ref_get_reference(unsubscribe_data->callback_ref); + + if (adapter == NULL) { + goto done; + } + + struct aws_protocol_adapter_subscription_event unsubscribe_event = { + .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), + .event_type = AWS_PASET_UNSUBSCRIBE, + .error_code = error_code, + }; + + (*adapter->config.subscription_event_callback)(&unsubscribe_event, adapter->config.user_data); + +done: + + s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(unsubscribe_data); +} + +int s_aws_mqtt_protocol_adapter_311_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { + struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; + struct aws_mqtt_client_connection_311_impl *connection_impl = adapter->connection->impl; + + struct aws_mqtt_protocol_adapter_subscription_op_data *unsubscribe_data = + s_aws_mqtt_protocol_adapter_subscription_op_data_new( + adapter->allocator, options->topic_filter, adapter->callback_ref); + + uint64_t timeout_nanos = aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + if (aws_mqtt_client_connection_311_unsubscribe(connection_impl, &options->topic_filter, s_protocol_adapter_311_unsubscribe_completion, unsubscribe_data, timeout_nanos)) { + goto error; + } + + return AWS_OP_SUCCESS; + +error: + + s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(unsubscribe_data); + + return AWS_OP_ERR; +} + +/* Publish */ + +static void s_protocol_adapter_311_publish_completion( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + int error_code, + void *userdata) { + + (void)connection; + (void)packet_id; + + struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = userdata; + struct aws_mqtt_protocol_adapter_311_impl *adapter = aws_weak_ref_get_reference(publish_data->callback_ref); + + if (adapter == NULL) { + goto done; + } + + (*publish_data->completion_callback_fn)(error_code, publish_data->user_data); + +done: + + s_aws_mqtt_protocol_adapter_publish_op_data_destroy(publish_data); +} + +int s_aws_mqtt_protocol_adapter_311_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { + struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; + struct aws_mqtt_client_connection_311_impl *connection_impl = adapter->connection->impl; + + struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = + s_aws_mqtt_protocol_adapter_publish_op_data_new(adapter->allocator, options, adapter->callback_ref); + + uint64_t timeout_nanos = aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + if (aws_mqtt_client_connection_311_publish(connection_impl, &options->topic, AWS_MQTT_QOS_AT_LEAST_ONCE, false, &options->payload, s_protocol_adapter_311_publish_completion, publish_data, timeout_nanos)) { + goto error; + } + + return AWS_OP_SUCCESS; + +error: + + s_aws_mqtt_protocol_adapter_publish_op_data_destroy(publish_data); + + return AWS_OP_ERR; +} + +static bool s_aws_mqtt_protocol_adapter_311_is_connected(void *impl) { + struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; + struct aws_mqtt_client_connection_311_impl *connection_impl = adapter->connection->impl; + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(connection_impl->loop)); + + mqtt_connection_lock_synced_data(connection_impl); + enum aws_mqtt_client_connection_state current_state = connection_impl->synced_data.state; + mqtt_connection_unlock_synced_data(connection_impl); + + return current_state == AWS_MQTT_CLIENT_STATE_CONNECTED; +} + +static void s_protocol_adapter_mqtt311_listener_termination_callback(void *user_data) { + struct aws_mqtt_protocol_adapter_311_impl *adapter = user_data; + struct aws_mqtt_client_connection_311_impl *impl = adapter->connection->impl; + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(impl->loop)); + + aws_weak_ref_zero_reference(adapter->callback_ref); + aws_weak_ref_release(adapter->callback_ref); + + aws_mqtt_client_connection_release(adapter->connection); + + aws_protocol_adapter_terminate_callback_fn *terminate_callback = adapter->config.terminate_callback; + void *terminate_user_data = adapter->config.user_data; + + aws_mem_release(adapter->allocator, adapter); + + if (terminate_callback) { + (*terminate_callback)(terminate_user_data); + } +} + +static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt311_vtable = { + .aws_mqtt_protocol_adapter_destroy_fn = s_aws_mqtt_protocol_adapter_311_destroy, + .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_311_subscribe, + .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_311_unsubscribe, + .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_311_publish, + .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_311_is_connected, }; -static struct aws_mqtt_protocol_adapter_5_subscription_op_data *s_aws_mqtt_protocol_adapter_5_subscription_op_data_new( +struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( struct aws_allocator *allocator, - struct aws_byte_cursor topic_filter, - struct aws_weak_ref *callback_ref) { - struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = - aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_subscription_op_data)); + struct aws_mqtt_protocol_adapter_options *options, + struct aws_mqtt_client_connection *connection) { - subscribe_data->allocator = allocator; - subscribe_data->callback_ref = aws_weak_ref_acquire(callback_ref); - aws_byte_buf_init_copy_from_cursor(&subscribe_data->topic_filter, allocator, topic_filter); + if (options == NULL || connection == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } - return subscribe_data; + if (aws_mqtt_client_connection_get_impl_type(connection) != AWS_MQTT311_IT_311_CONNECTION) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_mqtt_client_connection_311_impl *impl = connection->impl; + + struct aws_mqtt_protocol_adapter_311_impl *adapter = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_311_impl)); + + adapter->allocator = allocator; + adapter->base.impl = adapter; + adapter->base.vtable = &s_protocol_adapter_mqtt311_vtable; + adapter->callback_ref = aws_weak_ref_new(allocator, adapter); + adapter->config = *options; + adapter->loop = impl->loop; + adapter->connection = aws_mqtt_client_connection_acquire(connection); + + struct aws_mqtt311_listener_config listener_options = { + .connection = connection, + .listener_callbacks = + { + .publish_received_handler = NULL, + .connection_success_handler = NULL, + .connection_interrupted_handler = NULL, + .disconnect_handler = NULL, + .user_data = adapter, + }, + .termination_callback = s_protocol_adapter_mqtt311_listener_termination_callback, + .termination_callback_user_data = adapter, + }; + + adapter->listener = aws_mqtt311_listener_new(allocator, &listener_options); + + return &adapter->base; } -static void s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy( - struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data) { - aws_weak_ref_release(subscribe_data->callback_ref); - aws_byte_buf_clean_up(&subscribe_data->topic_filter); +/******************************************************************************************************************/ - aws_mem_release(subscribe_data->allocator, subscribe_data); +struct aws_mqtt_protocol_adapter_5_impl { + struct aws_allocator *allocator; + struct aws_mqtt_protocol_adapter base; + struct aws_weak_ref *callback_ref; + struct aws_mqtt_protocol_adapter_options config; + + struct aws_event_loop *loop; + struct aws_mqtt5_client *client; + struct aws_mqtt5_listener *listener; +}; + +static void s_aws_mqtt_protocol_adapter_5_destroy(void *impl) { + struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; + + // all the real cleanup is done in the listener termination callback + aws_mqtt5_listener_release(adapter->listener); } /* Subscribe */ @@ -99,7 +387,7 @@ static void s_protocol_adapter_5_subscribe_completion( const struct aws_mqtt5_packet_suback_view *suback, int error_code, void *complete_ctx) { - struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = complete_ctx; + struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = complete_ctx; struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(subscribe_data->callback_ref); if (adapter == NULL) { @@ -122,14 +410,14 @@ static void s_protocol_adapter_5_subscribe_completion( done: - s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy(subscribe_data); + s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(subscribe_data); } int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_5_subscription_op_data *subscribe_data = - s_aws_mqtt_protocol_adapter_5_subscription_op_data_new( + struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = + s_aws_mqtt_protocol_adapter_subscription_op_data_new( adapter->allocator, options->topic_filter, adapter->callback_ref); struct aws_mqtt5_subscription_view subscription_view = { @@ -156,7 +444,7 @@ int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adap error: - s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy(subscribe_data); + s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(subscribe_data); return AWS_OP_ERR; } @@ -167,7 +455,7 @@ static void s_protocol_adapter_5_unsubscribe_completion( const struct aws_mqtt5_packet_unsuback_view *unsuback, int error_code, void *complete_ctx) { - struct aws_mqtt_protocol_adapter_5_subscription_op_data *unsubscribe_data = complete_ctx; + struct aws_mqtt_protocol_adapter_subscription_op_data *unsubscribe_data = complete_ctx; struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(unsubscribe_data->callback_ref); if (adapter == NULL) { @@ -190,14 +478,14 @@ static void s_protocol_adapter_5_unsubscribe_completion( done: - s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy(unsubscribe_data); + s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(unsubscribe_data); } int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_5_subscription_op_data *unsubscribe_data = - s_aws_mqtt_protocol_adapter_5_subscription_op_data_new( + struct aws_mqtt_protocol_adapter_subscription_op_data *unsubscribe_data = + s_aws_mqtt_protocol_adapter_subscription_op_data_new( adapter->allocator, options->topic_filter, adapter->callback_ref); struct aws_mqtt5_packet_unsubscribe_view unsubscribe_view = { @@ -219,49 +507,19 @@ int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_ad error: - s_aws_mqtt_protocol_adapter_5_subscription_op_data_destroy(unsubscribe_data); + s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(unsubscribe_data); return AWS_OP_ERR; } /* Publish */ -struct aws_mqtt_protocol_adapter_5_publish_op_data { - struct aws_allocator *allocator; - struct aws_weak_ref *callback_ref; - - void (*completion_callback_fn)(int, void *); - void *user_data; -}; - -static struct aws_mqtt_protocol_adapter_5_publish_op_data *s_aws_mqtt_protocol_adapter_5_publish_op_data_new( - struct aws_allocator *allocator, - const struct aws_protocol_adapter_publish_options *publish_options, - struct aws_weak_ref *callback_ref) { - struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = - aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_5_publish_op_data)); - - publish_data->allocator = allocator; - publish_data->callback_ref = aws_weak_ref_acquire(callback_ref); - publish_data->completion_callback_fn = publish_options->completion_callback_fn; - publish_data->user_data = publish_options->user_data; - - return publish_data; -} - -static void s_aws_mqtt_protocol_adapter_5_publish_op_data_destroy( - struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data) { - aws_weak_ref_release(publish_data->callback_ref); - - aws_mem_release(publish_data->allocator, publish_data); -} - static void s_protocol_adapter_5_publish_completion( enum aws_mqtt5_packet_type packet_type, const void *packet, int error_code, void *complete_ctx) { - struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = complete_ctx; + struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = complete_ctx; struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(publish_data->callback_ref); if (adapter == NULL) { @@ -279,13 +537,13 @@ static void s_protocol_adapter_5_publish_completion( done: - s_aws_mqtt_protocol_adapter_5_publish_op_data_destroy(publish_data); + s_aws_mqtt_protocol_adapter_publish_op_data_destroy(publish_data); } int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_5_publish_op_data *publish_data = - s_aws_mqtt_protocol_adapter_5_publish_op_data_new(adapter->allocator, options, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = + s_aws_mqtt_protocol_adapter_publish_op_data_new(adapter->allocator, options, adapter->callback_ref); struct aws_mqtt5_packet_publish_view publish_view = { .topic = options->topic, .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, .payload = options->payload}; @@ -304,7 +562,7 @@ int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapte error: - s_aws_mqtt_protocol_adapter_5_publish_op_data_destroy(publish_data); + s_aws_mqtt_protocol_adapter_publish_op_data_destroy(publish_data); return AWS_OP_ERR; } @@ -385,7 +643,8 @@ static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt5_vtable = .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_5_subscribe, .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_5_unsubscribe, .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_5_publish, - .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_5_is_connected}; + .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_5_is_connected, +}; struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( struct aws_allocator *allocator, From e1c5a1c343649f61ace1435f8c8f9d4acd805899 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 31 Jan 2024 15:29:42 -0800 Subject: [PATCH 042/124] Formatting --- source/request-response/protocol_adapter.c | 46 +++++++++++++++++----- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index a3ae124b..338e15b3 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -161,8 +161,18 @@ int s_aws_mqtt_protocol_adapter_311_subscribe(void *impl, struct aws_protocol_ad s_aws_mqtt_protocol_adapter_subscription_op_data_new( adapter->allocator, options->topic_filter, adapter->callback_ref); - uint64_t timeout_nanos = aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); - if (aws_mqtt_client_connection_311_subscribe(connection_impl, &options->topic_filter, AWS_MQTT_QOS_AT_LEAST_ONCE, NULL, NULL, NULL, s_protocol_adapter_311_subscribe_completion, subscribe_data, timeout_nanos)) { + uint64_t timeout_nanos = + aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + if (aws_mqtt_client_connection_311_subscribe( + connection_impl, + &options->topic_filter, + AWS_MQTT_QOS_AT_LEAST_ONCE, + NULL, + NULL, + NULL, + s_protocol_adapter_311_subscribe_completion, + subscribe_data, + timeout_nanos)) { goto error; } @@ -177,10 +187,11 @@ int s_aws_mqtt_protocol_adapter_311_subscribe(void *impl, struct aws_protocol_ad /* Unsubscribe */ -static void s_protocol_adapter_311_unsubscribe_completion( struct aws_mqtt_client_connection *connection, - uint16_t packet_id, - int error_code, - void *userdata) { +static void s_protocol_adapter_311_unsubscribe_completion( + struct aws_mqtt_client_connection *connection, + uint16_t packet_id, + int error_code, + void *userdata) { (void)connection; (void)packet_id; @@ -212,8 +223,14 @@ int s_aws_mqtt_protocol_adapter_311_unsubscribe(void *impl, struct aws_protocol_ s_aws_mqtt_protocol_adapter_subscription_op_data_new( adapter->allocator, options->topic_filter, adapter->callback_ref); - uint64_t timeout_nanos = aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); - if (aws_mqtt_client_connection_311_unsubscribe(connection_impl, &options->topic_filter, s_protocol_adapter_311_unsubscribe_completion, unsubscribe_data, timeout_nanos)) { + uint64_t timeout_nanos = + aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + if (aws_mqtt_client_connection_311_unsubscribe( + connection_impl, + &options->topic_filter, + s_protocol_adapter_311_unsubscribe_completion, + unsubscribe_data, + timeout_nanos)) { goto error; } @@ -258,8 +275,17 @@ int s_aws_mqtt_protocol_adapter_311_publish(void *impl, struct aws_protocol_adap struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = s_aws_mqtt_protocol_adapter_publish_op_data_new(adapter->allocator, options, adapter->callback_ref); - uint64_t timeout_nanos = aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); - if (aws_mqtt_client_connection_311_publish(connection_impl, &options->topic, AWS_MQTT_QOS_AT_LEAST_ONCE, false, &options->payload, s_protocol_adapter_311_publish_completion, publish_data, timeout_nanos)) { + uint64_t timeout_nanos = + aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + if (aws_mqtt_client_connection_311_publish( + connection_impl, + &options->topic, + AWS_MQTT_QOS_AT_LEAST_ONCE, + false, + &options->payload, + s_protocol_adapter_311_publish_completion, + publish_data, + timeout_nanos)) { goto error; } From 7b5f8885f9dc170f44083d0220b081c07e385668 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 1 Feb 2024 15:01:15 -0800 Subject: [PATCH 043/124] Checkpoint --- source/request-response/protocol_adapter.c | 86 +++- tests/CMakeLists.txt | 6 + .../request_response_protocol_adapter_tests.c | 395 +++++++++++++----- tests/v3/mqtt_mock_server_handler.c | 13 +- tests/v3/mqtt_mock_server_handler.h | 7 + 5 files changed, 404 insertions(+), 103 deletions(-) diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 338e15b3..4e29b286 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -172,7 +172,7 @@ int s_aws_mqtt_protocol_adapter_311_subscribe(void *impl, struct aws_protocol_ad NULL, s_protocol_adapter_311_subscribe_completion, subscribe_data, - timeout_nanos)) { + timeout_nanos) == 0) { goto error; } @@ -230,7 +230,7 @@ int s_aws_mqtt_protocol_adapter_311_unsubscribe(void *impl, struct aws_protocol_ &options->topic_filter, s_protocol_adapter_311_unsubscribe_completion, unsubscribe_data, - timeout_nanos)) { + timeout_nanos) == 0) { goto error; } @@ -285,7 +285,7 @@ int s_aws_mqtt_protocol_adapter_311_publish(void *impl, struct aws_protocol_adap &options->payload, s_protocol_adapter_311_publish_completion, publish_data, - timeout_nanos)) { + timeout_nanos) == 0) { goto error; } @@ -298,6 +298,78 @@ int s_aws_mqtt_protocol_adapter_311_publish(void *impl, struct aws_protocol_adap return AWS_OP_ERR; } +static void s_protocol_adapter_mqtt311_listener_publish_received( + struct aws_mqtt_client_connection *connection, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, + bool dup, + enum aws_mqtt_qos qos, + bool retain, + void *userdata) { + + (void)connection; + (void)dup; + (void)qos; + (void)retain; + + struct aws_mqtt_protocol_adapter_311_impl *adapter = userdata; + + struct aws_protocol_adapter_incoming_publish_event publish_event = { + .topic = *topic, + .payload = *payload, + }; + + (*adapter->config.incoming_publish_callback)(&publish_event, adapter->config.user_data); +} + +static void s_protocol_adapter_mqtt311_listener_connection_success( + struct aws_mqtt_client_connection *connection, + enum aws_mqtt_connect_return_code return_code, + bool session_present, + void *userdata) { + (void)connection; + (void)return_code; + + struct aws_mqtt_protocol_adapter_311_impl *adapter = userdata; + + if (adapter->config.connection_event_callback != NULL) { + struct aws_protocol_adapter_connection_event connection_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = session_present, + }; + + (*adapter->config.connection_event_callback)(&connection_event, adapter->config.user_data); + } +} + +static void s_protocol_adapter_mqtt311_emit_disconnect_event(struct aws_mqtt_protocol_adapter_311_impl *adapter) { + if (adapter->config.connection_event_callback != NULL) { + struct aws_protocol_adapter_connection_event connection_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + + (*adapter->config.connection_event_callback)(&connection_event, adapter->config.user_data); + } +} + +static void s_protocol_adapter_mqtt311_listener_connection_interrupted( + struct aws_mqtt_client_connection *connection, + int error_code, + void *userdata) { + (void)connection; + (void)error_code; + + s_protocol_adapter_mqtt311_emit_disconnect_event(userdata); +} + +static void s_aws_mqtt_protocol_adapter_311_disconnect_fn( + struct aws_mqtt_client_connection *connection, + void *userdata) { + (void)connection; + + s_protocol_adapter_mqtt311_emit_disconnect_event(userdata); +} + static bool s_aws_mqtt_protocol_adapter_311_is_connected(void *impl) { struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; struct aws_mqtt_client_connection_311_impl *connection_impl = adapter->connection->impl; @@ -372,10 +444,10 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( .connection = connection, .listener_callbacks = { - .publish_received_handler = NULL, - .connection_success_handler = NULL, - .connection_interrupted_handler = NULL, - .disconnect_handler = NULL, + .publish_received_handler = s_protocol_adapter_mqtt311_listener_publish_received, + .connection_success_handler = s_protocol_adapter_mqtt311_listener_connection_success, + .connection_interrupted_handler = s_protocol_adapter_mqtt311_listener_connection_interrupted, + .disconnect_handler = s_aws_mqtt_protocol_adapter_311_disconnect_fn, .user_data = adapter, }, .termination_callback = s_protocol_adapter_mqtt311_listener_termination_callback, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 720339ca..d09c7f00 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -465,6 +465,12 @@ add_test_case(request_response_mqtt5_protocol_adapter_connection_event_connect_s add_test_case(request_response_mqtt5_protocol_adapter_incoming_publish) add_test_case(request_response_mqtt5_protocol_adapter_shutdown_while_pending) +add_test_case(request_response_mqtt311_protocol_adapter_subscribe_success) +add_test_case(request_response_mqtt311_protocol_adapter_subscribe_failure_timeout) +add_test_case(request_response_mqtt311_protocol_adapter_subscribe_failure_reason_code) +#add_test_case(request_response_mqtt311_protocol_adapter_unsubscribe_success) +#add_test_case(request_response_mqtt311_protocol_adapter_publish_success) + add_test_case(mqtt311_listener_connection_events_no_session) add_test_case(mqtt311_listener_connection_events_with_session) add_test_case(mqtt311_listener_publish_event) diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c index 2ffca2aa..5ae91c11 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0. */ +#include "../v3/mqtt311_testing_utils.h" +#include "../v3/mqtt_mock_server_handler.h" #include "../v5/mqtt5_testing_utils.h" #include @@ -56,9 +58,25 @@ static void s_request_response_protocol_adapter_subscription_event_record_cleanu aws_byte_buf_clean_up(&record->topic_filter); } -struct aws_request_response_mqtt5_adapter_test_fixture { +enum aws_protocol_adapter_protocol_type { + AWS_PAPT_MQTT5, + AWS_PAPT_MQTT311, +}; + +struct mqtt311_protocol_testing_context { + struct mqtt_connection_state_test mqtt311_test_context; + int mqtt311_test_context_setup_result; +}; + +struct aws_request_response_protocol_adapter_test_fixture { struct aws_allocator *allocator; - struct aws_mqtt5_client_mock_test_fixture mqtt5_fixture; + + enum aws_protocol_adapter_protocol_type protocol_type; + + union { + struct aws_mqtt5_client_mock_test_fixture mqtt5_fixture; + struct mqtt311_protocol_testing_context mqtt311_fixture; + } protocol_context; struct aws_mqtt_protocol_adapter *protocol_adapter; @@ -73,10 +91,10 @@ struct aws_request_response_mqtt5_adapter_test_fixture { struct aws_condition_variable signal; }; -static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event( +static void s_rr_mqtt_protocol_adapter_test_on_subscription_event( struct aws_protocol_adapter_subscription_event *event, void *user_data) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + struct aws_request_response_protocol_adapter_test_fixture *fixture = user_data; struct request_response_protocol_adapter_subscription_event_record record; s_request_response_protocol_adapter_subscription_event_record_init( @@ -88,10 +106,10 @@ static void s_rr_mqtt5_protocol_adapter_test_on_subscription_event( aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_incoming_publish( +static void s_rr_mqtt_protocol_adapter_test_on_incoming_publish( struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + struct aws_request_response_protocol_adapter_test_fixture *fixture = user_data; struct request_response_protocol_adapter_incoming_publish_event_record record; AWS_ZERO_STRUCT(record); @@ -104,8 +122,8 @@ static void s_rr_mqtt5_protocol_adapter_test_on_incoming_publish( aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_terminate_callback(void *user_data) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; +static void s_rr_mqtt_protocol_adapter_test_on_terminate_callback(void *user_data) { + struct aws_request_response_protocol_adapter_test_fixture *fixture = user_data; aws_mutex_lock(&fixture->lock); fixture->adapter_terminated = true; @@ -113,10 +131,10 @@ static void s_rr_mqtt5_protocol_adapter_test_on_terminate_callback(void *user_da aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_connection_event( +static void s_rr_mqtt_protocol_adapter_test_on_connection_event( struct aws_protocol_adapter_connection_event *event, void *user_data) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; + struct aws_request_response_protocol_adapter_test_fixture *fixture = user_data; aws_mutex_lock(&fixture->lock); aws_array_list_push_back(&fixture->connection_events, event); @@ -124,8 +142,8 @@ static void s_rr_mqtt5_protocol_adapter_test_on_connection_event( aws_condition_variable_notify_all(&fixture->signal); } -static void s_rr_mqtt5_protocol_adapter_test_on_publish_result(int error_code, void *user_data) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = user_data; +static void s_rr_mqtt_protocol_adapter_test_on_publish_result(int error_code, void *user_data) { + struct aws_request_response_protocol_adapter_test_fixture *fixture = user_data; aws_mutex_lock(&fixture->lock); aws_array_list_push_back(&fixture->publish_results, &error_code); @@ -133,28 +151,29 @@ static void s_rr_mqtt5_protocol_adapter_test_on_publish_result(int error_code, v aws_condition_variable_notify_all(&fixture->signal); } -static int s_aws_request_response_mqtt5_adapter_test_fixture_init( - struct aws_request_response_mqtt5_adapter_test_fixture *fixture, - struct aws_allocator *allocator, - struct aws_mqtt5_client_mqtt5_mock_test_fixture_options *mqtt5_fixture_config) { - - AWS_ZERO_STRUCT(*fixture); - - fixture->allocator = allocator; +static int s_aws_request_response_protocol_adapter_test_fixture_init_shared( + struct aws_request_response_protocol_adapter_test_fixture *fixture, + enum aws_protocol_adapter_protocol_type protocol_type) { - if (aws_mqtt5_client_mock_test_fixture_init(&fixture->mqtt5_fixture, allocator, mqtt5_fixture_config)) { - return AWS_OP_ERR; - } + struct aws_allocator *allocator = fixture->allocator; + fixture->protocol_type = protocol_type; struct aws_mqtt_protocol_adapter_options protocol_adapter_options = { - .subscription_event_callback = s_rr_mqtt5_protocol_adapter_test_on_subscription_event, - .incoming_publish_callback = s_rr_mqtt5_protocol_adapter_test_on_incoming_publish, - .terminate_callback = s_rr_mqtt5_protocol_adapter_test_on_terminate_callback, - .connection_event_callback = s_rr_mqtt5_protocol_adapter_test_on_connection_event, + .subscription_event_callback = s_rr_mqtt_protocol_adapter_test_on_subscription_event, + .incoming_publish_callback = s_rr_mqtt_protocol_adapter_test_on_incoming_publish, + .terminate_callback = s_rr_mqtt_protocol_adapter_test_on_terminate_callback, + .connection_event_callback = s_rr_mqtt_protocol_adapter_test_on_connection_event, .user_data = fixture}; - fixture->protocol_adapter = - aws_mqtt_protocol_adapter_new_from_5(allocator, &protocol_adapter_options, fixture->mqtt5_fixture.client); + if (protocol_type == AWS_PAPT_MQTT5) { + fixture->protocol_adapter = aws_mqtt_protocol_adapter_new_from_5( + allocator, &protocol_adapter_options, fixture->protocol_context.mqtt5_fixture.client); + } else { + fixture->protocol_adapter = aws_mqtt_protocol_adapter_new_from_311( + allocator, + &protocol_adapter_options, + fixture->protocol_context.mqtt311_fixture.mqtt311_test_context.mqtt_connection); + } AWS_FATAL_ASSERT(fixture->protocol_adapter != NULL); aws_array_list_init_dynamic( @@ -177,14 +196,47 @@ static int s_aws_request_response_mqtt5_adapter_test_fixture_init( return AWS_OP_SUCCESS; } +static int s_aws_request_response_protocol_adapter_test_fixture_init_mqtt5( + struct aws_request_response_protocol_adapter_test_fixture *fixture, + struct aws_allocator *allocator, + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options *mqtt5_fixture_config) { + + AWS_ZERO_STRUCT(*fixture); + + fixture->allocator = allocator; + + if (aws_mqtt5_client_mock_test_fixture_init( + &fixture->protocol_context.mqtt5_fixture, allocator, mqtt5_fixture_config)) { + return AWS_OP_ERR; + } + + return s_aws_request_response_protocol_adapter_test_fixture_init_shared(fixture, AWS_PAPT_MQTT5); +} + +static int s_aws_request_response_protocol_adapter_test_fixture_init_mqtt311( + struct aws_request_response_protocol_adapter_test_fixture *fixture, + struct aws_allocator *allocator) { + + AWS_ZERO_STRUCT(*fixture); + + fixture->allocator = allocator; + + int result = + aws_test311_setup_mqtt_server_fn(allocator, &fixture->protocol_context.mqtt311_fixture.mqtt311_test_context); + ASSERT_SUCCESS(result); + fixture->protocol_context.mqtt311_fixture.mqtt311_test_context_setup_result = result; + + return s_aws_request_response_protocol_adapter_test_fixture_init_shared(fixture, AWS_PAPT_MQTT311); +} + static bool s_is_adapter_terminated(void *context) { - struct aws_request_response_mqtt5_adapter_test_fixture *fixture = context; + struct aws_request_response_protocol_adapter_test_fixture *fixture = context; return fixture->adapter_terminated; } static void s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters( - struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { + struct aws_request_response_protocol_adapter_test_fixture *fixture) { if (fixture->protocol_adapter != NULL) { aws_mqtt_protocol_adapter_destroy(fixture->protocol_adapter); @@ -195,12 +247,19 @@ static void s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters( } } -static void s_aws_request_response_mqtt5_adapter_test_fixture_clean_up( - struct aws_request_response_mqtt5_adapter_test_fixture *fixture) { +static void s_aws_request_response_protocol_adapter_test_fixture_clean_up( + struct aws_request_response_protocol_adapter_test_fixture *fixture) { s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters(fixture); - aws_mqtt5_client_mock_test_fixture_clean_up(&fixture->mqtt5_fixture); + if (fixture->protocol_type == AWS_PAPT_MQTT5) { + aws_mqtt5_client_mock_test_fixture_clean_up(&fixture->protocol_context.mqtt5_fixture); + } else { + aws_test311_clean_up_mqtt_server_fn( + fixture->allocator, + fixture->protocol_context.mqtt311_fixture.mqtt311_test_context_setup_result, + &fixture->protocol_context.mqtt311_fixture.mqtt311_test_context); + } for (size_t i = 0; i < aws_array_list_length(&fixture->subscription_events); ++i) { struct request_response_protocol_adapter_subscription_event_record record; @@ -226,7 +285,7 @@ static void s_aws_request_response_mqtt5_adapter_test_fixture_clean_up( struct test_subscription_event_wait_context { struct request_response_protocol_adapter_subscription_event_record *expected_event; size_t expected_count; - struct aws_request_response_mqtt5_adapter_test_fixture *fixture; + struct aws_request_response_protocol_adapter_test_fixture *fixture; }; static bool s_do_subscription_events_contain(void *context) { @@ -261,7 +320,7 @@ static bool s_do_subscription_events_contain(void *context) { } static void s_wait_for_subscription_events_contains( - struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct aws_request_response_protocol_adapter_test_fixture *fixture, struct request_response_protocol_adapter_subscription_event_record *expected_event, size_t expected_count) { @@ -279,7 +338,7 @@ static void s_wait_for_subscription_events_contains( struct test_connection_event_wait_context { struct aws_protocol_adapter_connection_event *expected_event; size_t expected_count; - struct aws_request_response_mqtt5_adapter_test_fixture *fixture; + struct aws_request_response_protocol_adapter_test_fixture *fixture; }; static bool s_do_connection_events_contain(void *context) { @@ -307,7 +366,7 @@ static bool s_do_connection_events_contain(void *context) { } static void s_wait_for_connection_events_contains( - struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct aws_request_response_protocol_adapter_test_fixture *fixture, struct aws_protocol_adapter_connection_event *expected_event, size_t expected_count) { @@ -325,7 +384,7 @@ static void s_wait_for_connection_events_contains( struct test_incoming_publish_event_wait_context { struct request_response_protocol_adapter_incoming_publish_event_record *expected_event; size_t expected_count; - struct aws_request_response_mqtt5_adapter_test_fixture *fixture; + struct aws_request_response_protocol_adapter_test_fixture *fixture; }; static bool s_do_incoming_publish_events_contain(void *context) { @@ -357,7 +416,7 @@ static bool s_do_incoming_publish_events_contain(void *context) { } static void s_wait_for_incoming_publish_events_contains( - struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct aws_request_response_protocol_adapter_test_fixture *fixture, struct request_response_protocol_adapter_incoming_publish_event_record *expected_event, size_t expected_count) { @@ -375,7 +434,7 @@ static void s_wait_for_incoming_publish_events_contains( struct test_publish_result_wait_context { int expected_error_code; size_t expected_count; - struct aws_request_response_mqtt5_adapter_test_fixture *fixture; + struct aws_request_response_protocol_adapter_test_fixture *fixture; }; static bool s_do_publish_results_contain(void *context) { @@ -397,7 +456,7 @@ static bool s_do_publish_results_contain(void *context) { } static void s_wait_for_publish_results_contains( - struct aws_request_response_mqtt5_adapter_test_fixture *fixture, + struct aws_request_response_protocol_adapter_test_fixture *fixture, int expected_error_code, size_t expected_count) { @@ -441,16 +500,31 @@ static int s_aws_mqtt5_server_send_failed_suback_on_subscribe( return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view); } -static int s_test_type_to_expected_error_code(enum protocol_adapter_operation_test_type test_type) { - switch (test_type) { - case PAOTT_FAILURE_TIMEOUT: - return AWS_ERROR_MQTT_TIMEOUT; - case PAOTT_FAILURE_REASON_CODE: - return AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; - case PAOTT_FAILURE_ERROR_CODE: - return AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY; - default: - return AWS_ERROR_SUCCESS; +static int s_test_type_to_expected_error_code( + enum protocol_adapter_operation_test_type test_type, + enum aws_protocol_adapter_protocol_type protocol_type) { + if (protocol_type == AWS_PAPT_MQTT5) { + switch (test_type) { + case PAOTT_FAILURE_TIMEOUT: + return AWS_ERROR_MQTT_TIMEOUT; + case PAOTT_FAILURE_REASON_CODE: + return AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; + case PAOTT_FAILURE_ERROR_CODE: + return AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY; + default: + return AWS_ERROR_SUCCESS; + } + } else { + switch (test_type) { + case PAOTT_FAILURE_TIMEOUT: + return AWS_ERROR_MQTT_TIMEOUT; + case PAOTT_FAILURE_REASON_CODE: + return AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; + case PAOTT_FAILURE_ERROR_CODE: + return AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY; + default: + return AWS_ERROR_SUCCESS; + } } } @@ -479,19 +553,19 @@ static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( .server_function_table = &test_options.server_function_table, }; - struct aws_request_response_mqtt5_adapter_test_fixture fixture; - ASSERT_SUCCESS( - s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt5( + &fixture, allocator, &mqtt5_test_fixture_options)); - struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + struct aws_mqtt5_client *client = fixture.protocol_context.mqtt5_fixture.client; if (test_type != PAOTT_FAILURE_ERROR_CODE) { ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + aws_wait_for_connected_lifecycle_event(&fixture.protocol_context.mqtt5_fixture); } - int expected_error_code = s_test_type_to_expected_error_code(test_type); + int expected_error_code = s_test_type_to_expected_error_code(test_type, AWS_PAPT_MQTT5); struct request_response_protocol_adapter_subscription_event_record expected_outcome; s_request_response_protocol_adapter_subscription_event_record_init( @@ -512,7 +586,7 @@ static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( s_request_response_protocol_adapter_subscription_event_record_cleanup(&expected_outcome); - s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -620,19 +694,19 @@ static int s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test( .server_function_table = &test_options.server_function_table, }; - struct aws_request_response_mqtt5_adapter_test_fixture fixture; - ASSERT_SUCCESS( - s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt5( + &fixture, allocator, &mqtt5_test_fixture_options)); - struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + struct aws_mqtt5_client *client = fixture.protocol_context.mqtt5_fixture.client; if (test_type != PAOTT_FAILURE_ERROR_CODE) { ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + aws_wait_for_connected_lifecycle_event(&fixture.protocol_context.mqtt5_fixture); } - int expected_error_code = s_test_type_to_expected_error_code(test_type); + int expected_error_code = s_test_type_to_expected_error_code(test_type, AWS_PAPT_MQTT5); struct request_response_protocol_adapter_subscription_event_record expected_outcome; s_request_response_protocol_adapter_subscription_event_record_init( @@ -653,7 +727,7 @@ static int s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test( s_request_response_protocol_adapter_subscription_event_record_cleanup(&expected_outcome); - s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -762,31 +836,31 @@ static int s_do_request_response_mqtt5_protocol_adapter_publish_test( .server_function_table = &test_options.server_function_table, }; - struct aws_request_response_mqtt5_adapter_test_fixture fixture; - ASSERT_SUCCESS( - s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt5( + &fixture, allocator, &mqtt5_test_fixture_options)); - struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + struct aws_mqtt5_client *client = fixture.protocol_context.mqtt5_fixture.client; if (test_type != PAOTT_FAILURE_ERROR_CODE) { ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + aws_wait_for_connected_lifecycle_event(&fixture.protocol_context.mqtt5_fixture); } struct aws_protocol_adapter_publish_options publish_options = { .topic = aws_byte_cursor_from_c_str("hello/world"), .payload = aws_byte_cursor_from_c_str("SomePayload"), .ack_timeout_seconds = 2, - .completion_callback_fn = s_rr_mqtt5_protocol_adapter_test_on_publish_result, + .completion_callback_fn = s_rr_mqtt_protocol_adapter_test_on_publish_result, .user_data = &fixture}; aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); - int expected_error_code = s_test_type_to_expected_error_code(test_type); + int expected_error_code = s_test_type_to_expected_error_code(test_type, AWS_PAPT_MQTT5); s_wait_for_publish_results_contains(&fixture, expected_error_code, 1); - s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -864,11 +938,11 @@ static int s_do_request_response_mqtt5_protocol_adapter_connection_event_connect .server_function_table = &test_options.server_function_table, }; - struct aws_request_response_mqtt5_adapter_test_fixture fixture; - ASSERT_SUCCESS( - s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt5( + &fixture, allocator, &mqtt5_test_fixture_options)); - struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + struct aws_mqtt5_client *client = fixture.protocol_context.mqtt5_fixture.client; struct aws_protocol_adapter_connection_event expected_connect_record = { .event_type = AWS_PACET_CONNECTED, @@ -879,7 +953,7 @@ static int s_do_request_response_mqtt5_protocol_adapter_connection_event_connect s_wait_for_connection_events_contains(&fixture, &expected_connect_record, 1); ASSERT_SUCCESS(aws_mqtt5_client_stop(client, NULL, NULL)); - aws_wait_for_stopped_lifecycle_event(&fixture.mqtt5_fixture); + aws_wait_for_stopped_lifecycle_event(&fixture.protocol_context.mqtt5_fixture); struct aws_protocol_adapter_connection_event expected_disconnect_record = { .event_type = AWS_PACET_DISCONNECTED, @@ -887,7 +961,7 @@ static int s_do_request_response_mqtt5_protocol_adapter_connection_event_connect s_wait_for_connection_events_contains(&fixture, &expected_disconnect_record, 1); - s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -934,15 +1008,15 @@ static int s_request_response_mqtt5_protocol_adapter_incoming_publish_fn(struct .server_function_table = &test_options.server_function_table, }; - struct aws_request_response_mqtt5_adapter_test_fixture fixture; - ASSERT_SUCCESS( - s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt5( + &fixture, allocator, &mqtt5_test_fixture_options)); - struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + struct aws_mqtt5_client *client = fixture.protocol_context.mqtt5_fixture.client; ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + aws_wait_for_connected_lifecycle_event(&fixture.protocol_context.mqtt5_fixture); struct request_response_protocol_adapter_incoming_publish_event_record expected_publish; AWS_ZERO_STRUCT(expected_publish); @@ -957,7 +1031,7 @@ static int s_request_response_mqtt5_protocol_adapter_incoming_publish_fn(struct .topic = aws_byte_cursor_from_buf(&expected_publish.topic), .payload = aws_byte_cursor_from_buf(&expected_publish.payload), .ack_timeout_seconds = 2, - .completion_callback_fn = s_rr_mqtt5_protocol_adapter_test_on_publish_result, + .completion_callback_fn = s_rr_mqtt_protocol_adapter_test_on_publish_result, .user_data = &fixture}; aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); @@ -966,7 +1040,7 @@ static int s_request_response_mqtt5_protocol_adapter_incoming_publish_fn(struct s_request_response_protocol_adapter_incoming_publish_event_record_clean_up(&expected_publish); - s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -999,22 +1073,22 @@ static int s_request_response_mqtt5_protocol_adapter_shutdown_while_pending_fn( .server_function_table = &test_options.server_function_table, }; - struct aws_request_response_mqtt5_adapter_test_fixture fixture; - ASSERT_SUCCESS( - s_aws_request_response_mqtt5_adapter_test_fixture_init(&fixture, allocator, &mqtt5_test_fixture_options)); + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt5( + &fixture, allocator, &mqtt5_test_fixture_options)); - struct aws_mqtt5_client *client = fixture.mqtt5_fixture.client; + struct aws_mqtt5_client *client = fixture.protocol_context.mqtt5_fixture.client; ASSERT_SUCCESS(aws_mqtt5_client_start(client)); - aws_wait_for_connected_lifecycle_event(&fixture.mqtt5_fixture); + aws_wait_for_connected_lifecycle_event(&fixture.protocol_context.mqtt5_fixture); // publish struct aws_protocol_adapter_publish_options publish_options = { .topic = aws_byte_cursor_from_c_str("hello/world"), .payload = aws_byte_cursor_from_c_str("SomePayload"), .ack_timeout_seconds = 5, - .completion_callback_fn = s_rr_mqtt5_protocol_adapter_test_on_publish_result, + .completion_callback_fn = s_rr_mqtt_protocol_adapter_test_on_publish_result, .user_data = &fixture}; aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); @@ -1043,11 +1117,11 @@ static int s_request_response_mqtt5_protocol_adapter_shutdown_while_pending_fn( // wait for the stop to complete, which implies all the operations have been completed without calling back // into a deleted adapter - aws_wait_for_n_lifecycle_events(&fixture.mqtt5_fixture, AWS_MQTT5_CLET_STOPPED, 1); + aws_wait_for_n_lifecycle_events(&fixture.protocol_context.mqtt5_fixture, AWS_MQTT5_CLET_STOPPED, 1); // nothing to verify, we just don't want to crash - s_aws_request_response_mqtt5_adapter_test_fixture_clean_up(&fixture); + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -1057,3 +1131,134 @@ static int s_request_response_mqtt5_protocol_adapter_shutdown_while_pending_fn( AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_shutdown_while_pending, s_request_response_mqtt5_protocol_adapter_shutdown_while_pending_fn) + +static int s_do_request_response_mqtt311_protocol_adapter_subscribe_test( + struct aws_allocator *allocator, + enum protocol_adapter_operation_test_type test_type) { + aws_mqtt_library_init(allocator); + + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt311(&fixture, allocator)); + + struct aws_mqtt_client_connection *connection = + fixture.protocol_context.mqtt311_fixture.mqtt311_test_context.mqtt_connection; + + struct mqtt_connection_state_test *test_context_311 = + &fixture.protocol_context.mqtt311_fixture.mqtt311_test_context; + + switch (test_type) { + case PAOTT_SUCCESS: + mqtt_mock_server_enable_auto_ack(test_context_311->mock_server); + break; + case PAOTT_FAILURE_REASON_CODE: + mqtt_mock_server_enable_auto_ack(test_context_311->mock_server); + mqtt_mock_server_suback_reason_code(test_context_311->mock_server, 128); + break; + case PAOTT_FAILURE_ERROR_CODE: + // there is no reasonable way to generate an async error-code-based failure; so skip this case + AWS_FATAL_ASSERT(false); + break; + case PAOTT_FAILURE_TIMEOUT: + mqtt_mock_server_disable_auto_ack(test_context_311->mock_server); + default: + break; + } + + struct aws_mqtt_connection_options connection_options = { + .user_data = test_context_311, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(test_context_311->endpoint.address), + .socket_options = &test_context_311->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .protocol_operation_timeout_ms = 3000, + .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(connection, &connection_options)); + + struct aws_protocol_adapter_connection_event connection_success_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = false, + }; + + s_wait_for_connection_events_contains(&fixture, &connection_success_event, 1); + + int expected_error_code = s_test_type_to_expected_error_code(test_type, AWS_PAPT_MQTT311); + + struct request_response_protocol_adapter_subscription_event_record expected_outcome; + s_request_response_protocol_adapter_subscription_event_record_init( + &expected_outcome, + allocator, + AWS_PASET_SUBSCRIBE, + aws_byte_cursor_from_c_str("hello/world"), + expected_error_code); + + struct aws_protocol_adapter_subscribe_options subscribe_options = { + .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), + .ack_timeout_seconds = 2, + }; + + aws_mqtt_protocol_adapter_subscribe(fixture.protocol_adapter, &subscribe_options); + + s_wait_for_subscription_events_contains(&fixture, &expected_outcome, 1); + + s_request_response_protocol_adapter_subscription_event_record_cleanup(&expected_outcome); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect(connection, aws_test311_on_disconnect_fn, test_context_311)); + + struct aws_protocol_adapter_connection_event interruption_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + + s_wait_for_connection_events_contains(&fixture, &interruption_event, 1); + + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_request_response_mqtt311_protocol_adapter_subscribe_success_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt311_protocol_adapter_subscribe_test(allocator, PAOTT_SUCCESS)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_subscribe_success, + s_request_response_mqtt311_protocol_adapter_subscribe_success_fn) + +static int s_request_response_mqtt311_protocol_adapter_subscribe_failure_timeout_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt311_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_TIMEOUT)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_subscribe_failure_timeout, + s_request_response_mqtt311_protocol_adapter_subscribe_failure_timeout_fn) + +static int s_request_response_mqtt311_protocol_adapter_subscribe_failure_reason_code_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt311_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_REASON_CODE)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_subscribe_failure_reason_code, + s_request_response_mqtt311_protocol_adapter_subscribe_failure_reason_code_fn) \ No newline at end of file diff --git a/tests/v3/mqtt_mock_server_handler.c b/tests/v3/mqtt_mock_server_handler.c index 0e770690..8bee4cd6 100644 --- a/tests/v3/mqtt_mock_server_handler.c +++ b/tests/v3/mqtt_mock_server_handler.c @@ -33,6 +33,7 @@ struct mqtt_mock_server_handler { bool session_present; bool reflect_publishes; bool auto_ack; + uint8_t suback_reason_code; /* last ID used when sending PUBLISH (QoS1+) to client */ uint16_t last_packet_id; @@ -139,6 +140,7 @@ static int s_mqtt_mock_server_handler_process_packet( aws_mutex_lock(&server->synced.lock); bool auto_ack = server->synced.auto_ack; + uint8_t reason_code = server->synced.suback_reason_code; aws_mutex_unlock(&server->synced.lock); if (auto_ack) { @@ -148,7 +150,7 @@ static int s_mqtt_mock_server_handler_process_packet( err |= aws_mqtt_packet_suback_init(&suback, server->handler.alloc, subscribe_packet.packet_identifier); const size_t num_filters = aws_array_list_length(&subscribe_packet.topic_filters); for (size_t i = 0; i < num_filters; ++i) { - err |= aws_mqtt_packet_suback_add_return_code(&suback, AWS_MQTT_QOS_EXACTLY_ONCE); + err |= aws_mqtt_packet_suback_add_return_code(&suback, reason_code); } err |= aws_mqtt_packet_suback_encode(&suback_msg->message_data, &suback); err |= aws_channel_slot_send_message(server->slot, suback_msg, AWS_CHANNEL_DIR_WRITE); @@ -460,6 +462,7 @@ struct aws_channel_handler *new_mqtt_mock_server(struct aws_allocator *allocator server->synced.ping_resp_avail = SIZE_MAX; server->synced.connacks_avail = SIZE_MAX; server->synced.auto_ack = true; + server->synced.suback_reason_code = AWS_MQTT_QOS_EXACTLY_ONCE; aws_mutex_init(&server->synced.lock); aws_condition_variable_init(&server->synced.cvar); @@ -536,6 +539,14 @@ void mqtt_mock_server_enable_auto_ack(struct aws_channel_handler *handler) { aws_mutex_unlock(&server->synced.lock); } +void mqtt_mock_server_suback_reason_code(struct aws_channel_handler *handler, uint8_t reason_code) { + struct mqtt_mock_server_handler *server = handler->impl; + + aws_mutex_lock(&server->synced.lock); + server->synced.suback_reason_code = reason_code; + aws_mutex_unlock(&server->synced.lock); +} + struct mqtt_mock_server_ack_args { struct aws_channel_task task; struct aws_mqtt_packet_ack ack; diff --git a/tests/v3/mqtt_mock_server_handler.h b/tests/v3/mqtt_mock_server_handler.h index 8187ae11..70306311 100644 --- a/tests/v3/mqtt_mock_server_handler.h +++ b/tests/v3/mqtt_mock_server_handler.h @@ -92,10 +92,17 @@ void mqtt_mock_server_set_max_connack(struct aws_channel_handler *handler, size_ * Disable the automatically response (suback/unsuback/puback) to the client */ void mqtt_mock_server_disable_auto_ack(struct aws_channel_handler *handler); + /** * Enable the automatically response (suback/unsuback/puback) to the client */ void mqtt_mock_server_enable_auto_ack(struct aws_channel_handler *handler); + +/** + * Sets what reason code to return in subacks + */ +void mqtt_mock_server_suback_reason_code(struct aws_channel_handler *handler, uint8_t reason_code); + /** * Send response back the client given the packet ID */ From d6bdd2ddfe470977c969b3553f0daf61dfbd9c18 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 2 Feb 2024 11:38:20 -0800 Subject: [PATCH 044/124] Finish tests --- tests/CMakeLists.txt | 9 +- .../request_response_protocol_adapter_tests.c | 471 +++++++++++++++++- 2 files changed, 471 insertions(+), 9 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d09c7f00..3b86cf7e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -468,8 +468,13 @@ add_test_case(request_response_mqtt5_protocol_adapter_shutdown_while_pending) add_test_case(request_response_mqtt311_protocol_adapter_subscribe_success) add_test_case(request_response_mqtt311_protocol_adapter_subscribe_failure_timeout) add_test_case(request_response_mqtt311_protocol_adapter_subscribe_failure_reason_code) -#add_test_case(request_response_mqtt311_protocol_adapter_unsubscribe_success) -#add_test_case(request_response_mqtt311_protocol_adapter_publish_success) +add_test_case(request_response_mqtt311_protocol_adapter_unsubscribe_success) +add_test_case(request_response_mqtt311_protocol_adapter_unsubscribe_failure_timeout) +add_test_case(request_response_mqtt311_protocol_adapter_publish_success) +add_test_case(request_response_mqtt311_protocol_adapter_publish_failure_timeout) +add_test_case(request_response_mqtt311_protocol_adapter_connection_events) +add_test_case(request_response_mqtt311_protocol_adapter_incoming_publish) +add_test_case(request_response_mqtt311_protocol_adapter_shutdown_while_pending) add_test_case(mqtt311_listener_connection_events_no_session) add_test_case(mqtt311_listener_connection_events_with_session) diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c index 5ae91c11..0c317276 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -235,7 +235,7 @@ static bool s_is_adapter_terminated(void *context) { return fixture->adapter_terminated; } -static void s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters( +static void s_aws_request_response_protocol_adapter_test_fixture_destroy_adapters( struct aws_request_response_protocol_adapter_test_fixture *fixture) { if (fixture->protocol_adapter != NULL) { aws_mqtt_protocol_adapter_destroy(fixture->protocol_adapter); @@ -250,7 +250,7 @@ static void s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters( static void s_aws_request_response_protocol_adapter_test_fixture_clean_up( struct aws_request_response_protocol_adapter_test_fixture *fixture) { - s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters(fixture); + s_aws_request_response_protocol_adapter_test_fixture_destroy_adapters(fixture); if (fixture->protocol_type == AWS_PAPT_MQTT5) { aws_mqtt5_client_mock_test_fixture_clean_up(&fixture->protocol_context.mqtt5_fixture); @@ -853,7 +853,8 @@ static int s_do_request_response_mqtt5_protocol_adapter_publish_test( .payload = aws_byte_cursor_from_c_str("SomePayload"), .ack_timeout_seconds = 2, .completion_callback_fn = s_rr_mqtt_protocol_adapter_test_on_publish_result, - .user_data = &fixture}; + .user_data = &fixture, + }; aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); @@ -1032,7 +1033,8 @@ static int s_request_response_mqtt5_protocol_adapter_incoming_publish_fn(struct .payload = aws_byte_cursor_from_buf(&expected_publish.payload), .ack_timeout_seconds = 2, .completion_callback_fn = s_rr_mqtt_protocol_adapter_test_on_publish_result, - .user_data = &fixture}; + .user_data = &fixture, + }; aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); @@ -1089,7 +1091,8 @@ static int s_request_response_mqtt5_protocol_adapter_shutdown_while_pending_fn( .payload = aws_byte_cursor_from_c_str("SomePayload"), .ack_timeout_seconds = 5, .completion_callback_fn = s_rr_mqtt_protocol_adapter_test_on_publish_result, - .user_data = &fixture}; + .user_data = &fixture, + }; aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); @@ -1110,7 +1113,7 @@ static int s_request_response_mqtt5_protocol_adapter_shutdown_while_pending_fn( aws_mqtt_protocol_adapter_unsubscribe(fixture.protocol_adapter, &unsubscribe_options); // tear down the adapter, leaving the in-progress operations with nothing to call back into - s_aws_request_response_mqtt5_adapter_test_fixture_destroy_adapters(&fixture); + s_aws_request_response_protocol_adapter_test_fixture_destroy_adapters(&fixture); // stop the mqtt client, which fails the pending MQTT operations ASSERT_SUCCESS(aws_mqtt5_client_stop(client, NULL, NULL)); @@ -1261,4 +1264,458 @@ static int s_request_response_mqtt311_protocol_adapter_subscribe_failure_reason_ AWS_TEST_CASE( request_response_mqtt311_protocol_adapter_subscribe_failure_reason_code, - s_request_response_mqtt311_protocol_adapter_subscribe_failure_reason_code_fn) \ No newline at end of file + s_request_response_mqtt311_protocol_adapter_subscribe_failure_reason_code_fn) + +static int s_do_request_response_mqtt311_protocol_adapter_unsubscribe_test( + struct aws_allocator *allocator, + enum protocol_adapter_operation_test_type test_type) { + aws_mqtt_library_init(allocator); + + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt311(&fixture, allocator)); + + struct aws_mqtt_client_connection *connection = + fixture.protocol_context.mqtt311_fixture.mqtt311_test_context.mqtt_connection; + + struct mqtt_connection_state_test *test_context_311 = + &fixture.protocol_context.mqtt311_fixture.mqtt311_test_context; + + switch (test_type) { + case PAOTT_SUCCESS: + mqtt_mock_server_enable_auto_ack(test_context_311->mock_server); + break; + case PAOTT_FAILURE_REASON_CODE: + // 311 does not have unsuback reason codes or a way to fail + AWS_FATAL_ASSERT(false); + break; + case PAOTT_FAILURE_ERROR_CODE: + // there is no reasonable way to generate an async error-code-based failure; so skip this case + AWS_FATAL_ASSERT(false); + break; + case PAOTT_FAILURE_TIMEOUT: + mqtt_mock_server_disable_auto_ack(test_context_311->mock_server); + default: + break; + } + + struct aws_mqtt_connection_options connection_options = { + .user_data = test_context_311, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(test_context_311->endpoint.address), + .socket_options = &test_context_311->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .protocol_operation_timeout_ms = 3000, + .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(connection, &connection_options)); + + struct aws_protocol_adapter_connection_event connection_success_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = false, + }; + + s_wait_for_connection_events_contains(&fixture, &connection_success_event, 1); + + int expected_error_code = s_test_type_to_expected_error_code(test_type, AWS_PAPT_MQTT311); + + struct request_response_protocol_adapter_subscription_event_record expected_outcome; + s_request_response_protocol_adapter_subscription_event_record_init( + &expected_outcome, + allocator, + AWS_PASET_UNSUBSCRIBE, + aws_byte_cursor_from_c_str("hello/world"), + expected_error_code); + + struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { + .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), + .ack_timeout_seconds = 2, + }; + + aws_mqtt_protocol_adapter_unsubscribe(fixture.protocol_adapter, &unsubscribe_options); + + s_wait_for_subscription_events_contains(&fixture, &expected_outcome, 1); + + s_request_response_protocol_adapter_subscription_event_record_cleanup(&expected_outcome); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect(connection, aws_test311_on_disconnect_fn, test_context_311)); + + struct aws_protocol_adapter_connection_event interruption_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + + s_wait_for_connection_events_contains(&fixture, &interruption_event, 1); + + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_request_response_mqtt311_protocol_adapter_unsubscribe_success_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt311_protocol_adapter_unsubscribe_test(allocator, PAOTT_SUCCESS)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_unsubscribe_success, + s_request_response_mqtt311_protocol_adapter_unsubscribe_success_fn) + +static int s_request_response_mqtt311_protocol_adapter_unsubscribe_failure_timeout_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt311_protocol_adapter_unsubscribe_test(allocator, PAOTT_FAILURE_TIMEOUT)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_unsubscribe_failure_timeout, + s_request_response_mqtt311_protocol_adapter_unsubscribe_failure_timeout_fn) + +static int s_do_request_response_mqtt311_protocol_adapter_publish_test( + struct aws_allocator *allocator, + enum protocol_adapter_operation_test_type test_type) { + aws_mqtt_library_init(allocator); + + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt311(&fixture, allocator)); + + struct aws_mqtt_client_connection *connection = + fixture.protocol_context.mqtt311_fixture.mqtt311_test_context.mqtt_connection; + + struct mqtt_connection_state_test *test_context_311 = + &fixture.protocol_context.mqtt311_fixture.mqtt311_test_context; + + switch (test_type) { + case PAOTT_SUCCESS: + mqtt_mock_server_enable_auto_ack(test_context_311->mock_server); + break; + case PAOTT_FAILURE_REASON_CODE: + // 311 does not have puback reason codes or a way to fail + AWS_FATAL_ASSERT(false); + break; + case PAOTT_FAILURE_ERROR_CODE: + // there is no reasonable way to generate an async error-code-based failure; so skip this case + AWS_FATAL_ASSERT(false); + break; + case PAOTT_FAILURE_TIMEOUT: + mqtt_mock_server_disable_auto_ack(test_context_311->mock_server); + default: + break; + } + + struct aws_mqtt_connection_options connection_options = { + .user_data = test_context_311, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(test_context_311->endpoint.address), + .socket_options = &test_context_311->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .protocol_operation_timeout_ms = 3000, + .keep_alive_time_secs = 16960, /* basically stop automatically sending PINGREQ */ + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(connection, &connection_options)); + + struct aws_protocol_adapter_connection_event connection_success_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = false, + }; + + s_wait_for_connection_events_contains(&fixture, &connection_success_event, 1); + + struct aws_protocol_adapter_publish_options publish_options = { + .topic = aws_byte_cursor_from_c_str("hello/world"), + .payload = aws_byte_cursor_from_c_str("SomePayload"), + .ack_timeout_seconds = 2, + .completion_callback_fn = s_rr_mqtt_protocol_adapter_test_on_publish_result, + .user_data = &fixture, + }; + + aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); + + int expected_error_code = s_test_type_to_expected_error_code(test_type, AWS_PAPT_MQTT311); + s_wait_for_publish_results_contains(&fixture, expected_error_code, 1); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect(connection, aws_test311_on_disconnect_fn, test_context_311)); + + struct aws_protocol_adapter_connection_event interruption_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + + s_wait_for_connection_events_contains(&fixture, &interruption_event, 1); + + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_request_response_mqtt311_protocol_adapter_publish_success_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt311_protocol_adapter_publish_test(allocator, PAOTT_SUCCESS)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_publish_success, + s_request_response_mqtt311_protocol_adapter_publish_success_fn) + +static int s_request_response_mqtt311_protocol_adapter_publish_failure_timeout_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS(s_do_request_response_mqtt311_protocol_adapter_publish_test(allocator, PAOTT_FAILURE_TIMEOUT)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_publish_failure_timeout, + s_request_response_mqtt311_protocol_adapter_publish_failure_timeout_fn) + +static int s_request_response_mqtt311_protocol_adapter_connection_events_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt311(&fixture, allocator)); + + struct aws_mqtt_client_connection *connection = + fixture.protocol_context.mqtt311_fixture.mqtt311_test_context.mqtt_connection; + + struct mqtt_connection_state_test *test_context_311 = + &fixture.protocol_context.mqtt311_fixture.mqtt311_test_context; + + // always rejoin session and cause continual ping timeouts to generate interrupted events + mqtt_mock_server_set_session_present(test_context_311->mock_server, true); + mqtt_mock_server_set_max_ping_resp(test_context_311->mock_server, 0); + + struct aws_mqtt_connection_options connection_options = { + .user_data = test_context_311, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(test_context_311->endpoint.address), + .socket_options = &test_context_311->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .protocol_operation_timeout_ms = 3000, + .keep_alive_time_secs = 2, + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(connection, &connection_options)); + + struct aws_protocol_adapter_connection_event connection_success_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = true, + }; + + s_wait_for_connection_events_contains(&fixture, &connection_success_event, 1); + + struct aws_protocol_adapter_connection_event interruption_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + + /* wait for ping timeout disconnect */ + s_wait_for_connection_events_contains(&fixture, &interruption_event, 1); + + /* wait for automatic reconnect */ + s_wait_for_connection_events_contains(&fixture, &connection_success_event, 2); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect(connection, aws_test311_on_disconnect_fn, test_context_311)); + + s_wait_for_connection_events_contains(&fixture, &interruption_event, 2); + + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_connection_events, + s_request_response_mqtt311_protocol_adapter_connection_events_fn) + +static int s_request_response_mqtt311_protocol_adapter_incoming_publish_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt311(&fixture, allocator)); + + struct aws_mqtt_client_connection *connection = + fixture.protocol_context.mqtt311_fixture.mqtt311_test_context.mqtt_connection; + + struct mqtt_connection_state_test *test_context_311 = + &fixture.protocol_context.mqtt311_fixture.mqtt311_test_context; + + /* reflect publishes */ + mqtt_mock_server_set_publish_reflection(test_context_311->mock_server, true); + + struct aws_mqtt_connection_options connection_options = { + .user_data = test_context_311, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(test_context_311->endpoint.address), + .socket_options = &test_context_311->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = 30 * 1000, + .protocol_operation_timeout_ms = 3000, + .keep_alive_time_secs = 20000, + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(connection, &connection_options)); + + struct aws_protocol_adapter_connection_event connection_success_event = { + .event_type = AWS_PACET_CONNECTED, + }; + + s_wait_for_connection_events_contains(&fixture, &connection_success_event, 1); + + struct request_response_protocol_adapter_incoming_publish_event_record expected_publish; + AWS_ZERO_STRUCT(expected_publish); + + s_request_response_protocol_adapter_incoming_publish_event_record_init( + &expected_publish, + allocator, + aws_byte_cursor_from_c_str("hello/world"), + aws_byte_cursor_from_c_str("SomePayload")); + + struct aws_protocol_adapter_publish_options publish_options = { + .topic = aws_byte_cursor_from_buf(&expected_publish.topic), + .payload = aws_byte_cursor_from_buf(&expected_publish.payload), + .ack_timeout_seconds = 2, + .completion_callback_fn = s_rr_mqtt_protocol_adapter_test_on_publish_result, + .user_data = &fixture, + }; + + aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); + + s_wait_for_incoming_publish_events_contains(&fixture, &expected_publish, 1); + + s_request_response_protocol_adapter_incoming_publish_event_record_clean_up(&expected_publish); + + struct aws_protocol_adapter_connection_event interruption_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect(connection, aws_test311_on_disconnect_fn, test_context_311)); + + s_wait_for_connection_events_contains(&fixture, &interruption_event, 1); + + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_incoming_publish, + s_request_response_mqtt311_protocol_adapter_incoming_publish_fn) + +static int s_request_response_mqtt311_protocol_adapter_shutdown_while_pending_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_request_response_protocol_adapter_test_fixture fixture; + ASSERT_SUCCESS(s_aws_request_response_protocol_adapter_test_fixture_init_mqtt311(&fixture, allocator)); + + struct aws_mqtt_client_connection *connection = + fixture.protocol_context.mqtt311_fixture.mqtt311_test_context.mqtt_connection; + + struct mqtt_connection_state_test *test_context_311 = + &fixture.protocol_context.mqtt311_fixture.mqtt311_test_context; + + /* reflect publishes */ + mqtt_mock_server_set_publish_reflection(test_context_311->mock_server, true); + + struct aws_mqtt_connection_options connection_options = { + .user_data = test_context_311, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(test_context_311->endpoint.address), + .socket_options = &test_context_311->socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = 30 * 1000, + .protocol_operation_timeout_ms = 3000, + .keep_alive_time_secs = 20000, + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(connection, &connection_options)); + + struct aws_protocol_adapter_connection_event connection_success_event = { + .event_type = AWS_PACET_CONNECTED, + }; + + s_wait_for_connection_events_contains(&fixture, &connection_success_event, 1); + + // publish + struct aws_protocol_adapter_publish_options publish_options = { + .topic = aws_byte_cursor_from_c_str("hello/world"), + .payload = aws_byte_cursor_from_c_str("SomePayload"), + .ack_timeout_seconds = 5, + .completion_callback_fn = s_rr_mqtt_protocol_adapter_test_on_publish_result, + .user_data = &fixture, + }; + + aws_mqtt_protocol_adapter_publish(fixture.protocol_adapter, &publish_options); + + // subscribe + struct aws_protocol_adapter_subscribe_options subscribe_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .ack_timeout_seconds = 5, + }; + + aws_mqtt_protocol_adapter_subscribe(fixture.protocol_adapter, &subscribe_options); + + // unsubscribe + struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .ack_timeout_seconds = 5, + }; + + aws_mqtt_protocol_adapter_unsubscribe(fixture.protocol_adapter, &unsubscribe_options); + + // tear down the adapter, leaving the in-progress operations with nothing to call back into + s_aws_request_response_protocol_adapter_test_fixture_destroy_adapters(&fixture); + + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect(connection, aws_test311_on_disconnect_fn, test_context_311)); + + /* have to dig into the 311 test fixture because we won't be getting an interrupted event */ + aws_test311_wait_for_disconnect_to_complete(test_context_311); + + s_aws_request_response_protocol_adapter_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt311_protocol_adapter_shutdown_while_pending, + s_request_response_mqtt311_protocol_adapter_shutdown_while_pending_fn) \ No newline at end of file From 3e27f4020f185db7ab1cbec6ef443cb885f89919 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 2 Feb 2024 12:00:41 -0800 Subject: [PATCH 045/124] Remove unnecessary null assignments --- source/client.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/source/client.c b/source/client.c index 43cf9ef9..5ba4ee5c 100644 --- a/source/client.c +++ b/source/client.c @@ -1975,7 +1975,6 @@ static void s_subscribe_complete( */ if (task_arg->timeout_wrapper.timeout_task_arg) { task_arg->timeout_wrapper.timeout_task_arg->task_arg_wrapper = NULL; - task_arg->timeout_wrapper.timeout_task_arg = NULL; } for (size_t i = 0; i < list_len; i++) { @@ -2158,7 +2157,6 @@ static void s_subscribe_single_complete( */ if (task_arg->timeout_wrapper.timeout_task_arg) { task_arg->timeout_wrapper.timeout_task_arg->task_arg_wrapper = NULL; - task_arg->timeout_wrapper.timeout_task_arg = NULL; } s_task_topic_release(topic); @@ -2493,7 +2491,6 @@ static void s_resubscribe_complete( */ if (task_arg->timeout_wrapper.timeout_task_arg) { task_arg->timeout_wrapper.timeout_task_arg->task_arg_wrapper = NULL; - task_arg->timeout_wrapper.timeout_task_arg = NULL; } /* We need to cleanup the subscribe_task_topics, since they are not inserted into the topic tree by resubscribe. We @@ -2731,7 +2728,6 @@ static void s_unsubscribe_complete( */ if (task_arg->timeout_wrapper.timeout_task_arg) { task_arg->timeout_wrapper.timeout_task_arg->task_arg_wrapper = NULL; - task_arg->timeout_wrapper.timeout_task_arg = NULL; } if (task_arg->on_unsuback) { @@ -3013,7 +3009,6 @@ static void s_publish_complete( */ if (task_arg->timeout_wrapper.timeout_task_arg != NULL) { task_arg->timeout_wrapper.timeout_task_arg->task_arg_wrapper = NULL; - task_arg->timeout_wrapper.timeout_task_arg = NULL; } aws_byte_buf_clean_up(&task_arg->payload_buf); From dcd5a3a3915d2d5c5ecfd01b3e058896a316850f Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 20 Feb 2024 11:17:16 -0800 Subject: [PATCH 046/124] Sync point --- .../request-response/subscription_manager.h | 104 ++++++++++++++++++ .../request-response/subscription_manager.c | 79 +++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 include/aws/mqtt/private/request-response/subscription_manager.h create mode 100644 source/request-response/subscription_manager.c diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h new file mode 100644 index 00000000..5d27cdbe --- /dev/null +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -0,0 +1,104 @@ +#ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_SUBSCRIPTION_MANAGER_H +#define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_SUBSCRIPTION_MANAGER_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include + +struct aws_mqtt_protocol_adapter; +struct aws_protocol_adapter_connection_event; +struct aws_protocol_adapter_subscription_event; + +enum aws_rr_subscription_status { + ARRSS_INVALID, + ARRSS_SUBSCRIBING, + ARRSS_SUBSCRIBED, + ARRSS_UNSUBSCRIBING, +}; + +enum aws_rr_subscription_type { + ARRST_EVENT_STREAM, + ARRST_REQUEST_RESPONSE, +}; + +struct aws_rr_subscription { + struct aws_allocator *allocator; + + struct aws_byte_buf topic_filter; + struct aws_byte_cursor topic_filter_cursor; + + /* operation ids (uint64_t) */ + struct aws_array_list listening_operations; + + enum aws_rr_subscription_status status; +}; + +enum aws_acquire_subscription_result_type { + AASRT_SUBSCRIBED, + AASRT_SUBSCRIBING, + AASRT_BLOCKED, + AASRT_NO_CAPACITY, + AASRT_FAILURE +}; + +struct aws_rr_subscription_status_event { + struct aws_byte_cursor topic_filter; + uint64_t operation_id; + enum aws_rr_subscription_status status; +}; + +typedef void (aws_rr_subscription_status_event_callbacK_fn)(struct aws_rr_subscription_status_event, void *userdata); + +struct aws_rr_subscription_manager_options { + size_t max_subscriptions; + uint32_t operation_timeout_seconds; + + aws_rr_subscription_status_event_callbacK_fn *subscription_status_callback; + void *userdata; +}; + +struct aws_rr_subscription_manager { + struct aws_allocator *allocator; + + struct aws_rr_subscription_manager_options config; + + /* non-owning reference; the client is responsible for destroying this asynchronously (listener detachment) */ + struct aws_mqtt_protocol_adapter *protocol_adapter; + + /* &aws_request_response_subscription.topic_filter_cursor -> aws_request_response_subscription * */ + struct aws_hash_table subscriptions; +}; + +struct aws_rr_acquire_subscription_options { + struct aws_byte_cursor topic_filter; + uint64_t operation_id; + enum aws_rr_subscription_type type; +}; + +struct aws_rr_release_subscription_options { + struct aws_byte_cursor topic_filter; + uint64_t operation_id; +}; + +AWS_EXTERN_C_BEGIN + +int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, struct aws_rr_subscription_manager_options *options); + +void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager); + +enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_acquire_subscription_options *options); + +void aws_rr_subscription_manager_release_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_release_subscription_options *options); + +void aws_rr_subscription_manager_on_protocol_adapter_subscription_event(struct aws_rr_subscription_manager *manager, struct aws_protocol_adapter_subscription_event *event); + +void aws_rr_subscription_manager_on_protocol_adapter_connection_event(struct aws_rr_subscription_manager *manager, struct aws_protocol_adapter_connection_event *event); + +AWS_EXTERN_C_END + +#endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_SUBSCRIPTION_MANAGER_H */ diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c new file mode 100644 index 00000000..e10015d1 --- /dev/null +++ b/source/request-response/subscription_manager.c @@ -0,0 +1,79 @@ +/** +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0. +*/ + +#include + +#include +#include + +static void s_aws_rr_subscription_destroy(void *element) { + struct aws_rr_subscription *subscription = element; + + aws_byte_buf_clean_up(&subscription->topic_filter); + aws_array_list_clean_up(&subscription->listening_operations); + + aws_mem_release(subscription->allocator, subscription); +} + +int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, struct aws_rr_subscription_manager_options *options) { + AWS_ZERO_STRUCT(*manager); + + if (options == NULL || options->max_subscriptions < 2 || options->operation_timeout_seconds == 0) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + manager->allocator = allocator; + manager->config = *options; + manager->protocol_adapter = protocol_adapter; + + if (aws_hash_table_init(&manager->subscriptions, allocator, options->max_subscriptions, aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, NULL, s_aws_rr_subscription_destroy)) { + return AWS_OP_ERR; + } + + return AWS_OP_SUCCESS; +} + +static int s_rr_subscription_clean_up_foreach_wrap(void *context, struct aws_hash_element *elem) { + struct aws_rr_subscription_manager *manager = context; + struct aws_rr_subscription *subscription = elem->value; + + struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { + .topic_filter = subscription->topic_filter_cursor, + .ack_timeout_seconds = manager->config.operation_timeout_seconds, + }; + + aws_mqtt_protocol_adapter_unsubscribe(manager->protocol_adapter, &unsubscribe_options); + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; +} + +void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager) { + aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_clean_up_foreach_wrap, manager->protocol_adapter); + aws_hash_table_clean_up(&manager->subscriptions); +} + + +enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_acquire_subscription_options *options) { + (void)manager; + (void)options; + + return AASRT_FAILURE; +} + +void aws_rr_subscription_manager_release_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_release_subscription_options *options) { + (void)manager; + (void)options; +} + +void aws_rr_subscription_manager_on_protocol_adapter_subscription_event(struct aws_rr_subscription_manager *manager, struct aws_protocol_adapter_subscription_event *event) { + (void)manager; + (void)event; +} + +void aws_rr_subscription_manager_on_protocol_adapter_connection_event(struct aws_rr_subscription_manager *manager, struct aws_protocol_adapter_connection_event *event) { + (void)manager; + (void)event; +} \ No newline at end of file From 7c9d5028e4c16b8c30f38f292d49df7050ffe33b Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 20 Feb 2024 16:26:08 -0800 Subject: [PATCH 047/124] Check point --- .../request-response/subscription_manager.h | 41 ++-- .../request-response/subscription_manager.c | 192 ++++++++++++++++-- 2 files changed, 197 insertions(+), 36 deletions(-) diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 5d27cdbe..3211ace7 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -14,11 +14,11 @@ struct aws_mqtt_protocol_adapter; struct aws_protocol_adapter_connection_event; struct aws_protocol_adapter_subscription_event; -enum aws_rr_subscription_status { - ARRSS_INVALID, - ARRSS_SUBSCRIBING, - ARRSS_SUBSCRIBED, - ARRSS_UNSUBSCRIBING, +enum aws_rr_subscription_status_type { + ARRSST_INVALID, + ARRSST_SUBSCRIBING, + ARRSST_SUBSCRIBED, + ARRSST_UNSUBSCRIBING, }; enum aws_rr_subscription_type { @@ -26,30 +26,15 @@ enum aws_rr_subscription_type { ARRST_REQUEST_RESPONSE, }; -struct aws_rr_subscription { - struct aws_allocator *allocator; - - struct aws_byte_buf topic_filter; - struct aws_byte_cursor topic_filter_cursor; - - /* operation ids (uint64_t) */ - struct aws_array_list listening_operations; - - enum aws_rr_subscription_status status; -}; - -enum aws_acquire_subscription_result_type { - AASRT_SUBSCRIBED, - AASRT_SUBSCRIBING, - AASRT_BLOCKED, - AASRT_NO_CAPACITY, - AASRT_FAILURE +enum aws_rr_subscription_event_type { + ARRSET_SUBSCRIPTION_ESTABLISHED, + ARRSET_SUBSCRIPTION_LOST }; struct aws_rr_subscription_status_event { + enum aws_rr_subscription_event_type type; struct aws_byte_cursor topic_filter; uint64_t operation_id; - enum aws_rr_subscription_status status; }; typedef void (aws_rr_subscription_status_event_callbacK_fn)(struct aws_rr_subscription_status_event, void *userdata); @@ -85,6 +70,14 @@ struct aws_rr_release_subscription_options { uint64_t operation_id; }; +enum aws_acquire_subscription_result_type { + AASRT_SUBSCRIBED, + AASRT_SUBSCRIBING, + AASRT_BLOCKED, + AASRT_NO_CAPACITY, + AASRT_FAILURE +}; + AWS_EXTERN_C_BEGIN int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, struct aws_rr_subscription_manager_options *options); diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index e10015d1..60149b79 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -8,19 +8,73 @@ #include #include +struct aws_rr_subscription { + struct aws_allocator *allocator; + + struct aws_byte_buf topic_filter; + struct aws_byte_cursor topic_filter_cursor; + + struct aws_hash_table listening_operations; + + enum aws_rr_subscription_status_type status; + enum aws_rr_subscription_type type; +}; + static void s_aws_rr_subscription_destroy(void *element) { struct aws_rr_subscription *subscription = element; aws_byte_buf_clean_up(&subscription->topic_filter); - aws_array_list_clean_up(&subscription->listening_operations); + + ??; + aws_hash_table_clean_up(&subscription->listening_operations); aws_mem_release(subscription->allocator, subscription); } +struct aws_rr_subscription_listener { + struct aws_allocator *allocator; + uint64_t operation_id; +}; + +static uint64_t s_aws_hash_subscription_listener(const void *item) { + struct aws_rr_subscription_listener *listener = item; + + return listener->operation_id; +} + +static bool s_aws_subscription_listener_hash_equality(const void *a, const void *b) { + const struct aws_rr_subscription_listener *a_listener = a; + const struct aws_rr_subscription_listener *b_listener = b; + + return a_listener->operation_id == b_listener->operation_id; +} + +static void s_aws_subscription_listener_destroy(void *element) { + struct aws_rr_subscription_listener *listener = element; + + aws_mem_release(listener->allocator, listener); +} + +static struct aws_rr_subscription *s_create_subscription_record(struct aws_allocator *allocator, struct aws_rr_acquire_subscription_options *options) { + struct aws_rr_subscription *record = aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_subscription)); + record->allocator = allocator; + + aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, options->topic_filter); + record->topic_filter_cursor = aws_byte_cursor_from_buf(&record->topic_filter); + + aws_hash_table_init(&record->listening_operations, allocator, 4, s_aws_hash_subscription_listener, + s_aws_subscription_listener_hash_equality, NULL, s_aws_subscription_listener_destroy); + + record->status = ARRSST_INVALID; + record->type = options->type; + + return record; +} + int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, struct aws_rr_subscription_manager_options *options) { AWS_ZERO_STRUCT(*manager); - if (options == NULL || options->max_subscriptions < 2 || options->operation_timeout_seconds == 0) { + if (options == NULL || options->max_subscriptions < 1 || options->operation_timeout_seconds == 0) { return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } @@ -36,16 +90,24 @@ int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager return AWS_OP_SUCCESS; } +static void s_subscription_record_unsubscribe(struct aws_rr_subscription_manager *manager, struct aws_rr_subscription *subscription) { + if (subscription->status == ARRSST_SUBSCRIBING || subscription->status == ARRSST_SUBSCRIBED) { + struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { + .topic_filter = subscription->topic_filter_cursor, + .ack_timeout_seconds = manager->config.operation_timeout_seconds, + }; + + aws_mqtt_protocol_adapter_unsubscribe(manager->protocol_adapter, &unsubscribe_options); + + subscription->status = ARRSST_UNSUBSCRIBING; + } +} + static int s_rr_subscription_clean_up_foreach_wrap(void *context, struct aws_hash_element *elem) { struct aws_rr_subscription_manager *manager = context; struct aws_rr_subscription *subscription = elem->value; - struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { - .topic_filter = subscription->topic_filter_cursor, - .ack_timeout_seconds = manager->config.operation_timeout_seconds, - }; - - aws_mqtt_protocol_adapter_unsubscribe(manager->protocol_adapter, &unsubscribe_options); + s_subscription_record_unsubscribe(manager, subscription); return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; } @@ -55,12 +117,118 @@ void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *ma aws_hash_table_clean_up(&manager->subscriptions); } +static struct aws_rr_subscription *s_get_subscription_record(struct aws_rr_subscription_manager *manager, struct aws_byte_cursor topic_filter) { + struct aws_rr_subscription *subscription = NULL; + struct aws_hash_element *element = NULL; + if (aws_hash_table_find(&manager->subscriptions, &topic_filter, &element)) { + return NULL; + } + + if (element != NULL) { + subscription = element->value; + } + + return subscription; +} + +struct aws_subscription_stats { + size_t request_response_subscriptions; + size_t event_stream_subscriptions; +}; + +static int s_rr_subscription_count_foreach_wrap(void *context, struct aws_hash_element *elem) { + struct aws_rr_subscription *subscription = elem->value; + struct aws_subscription_stats *stats = context; + + if (subscription->type == ARRST_EVENT_STREAM) { + ++stats->event_stream_subscriptions; + } else { + ++stats->request_response_subscriptions; + } + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; +} + +static void s_get_subscription_stats(struct aws_rr_subscription_manager *manager, struct aws_subscription_stats *stats) { + AWS_ZERO_STRUCT(*stats); + + aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_count_foreach_wrap, stats); +} + +static void s_remove_operation_id_from_subscription(struct aws_rr_subscription_manager *manager, struct aws_byte_cursor topic_filter, uint64_t operation_id) { + (void)operation_id; + + struct aws_rr_subscription *subscription = s_get_subscription_record(manager, topic_filter); + if (subscription == NULL) { + return; + } + + struct aws_rr_subscription_listener listener = { + .operation_id = operation_id, + }; + + aws_hash_table_remove(&subscription->listening_operations, &listener, NULL, NULL); + if (aws_hash_table_get_entry_count(&subscription->listening_operations) == 0) { + s_subscription_record_unsubscribe(manager, subscription); + + + } +} enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_acquire_subscription_options *options) { - (void)manager; - (void)options; + struct aws_rr_subscription *existing_subscription = s_get_subscription_record(manager, options->topic_filter); + + // is no subscription present? + if (existing_subscription == NULL) { + + // is the budget used up? + struct aws_subscription_stats stats; + s_get_subscription_stats(manager, &stats); + + if (stats.event_stream_subscriptions + stats.request_response_subscriptions >= manager->config.max_subscriptions) { + // could space eventually free up? + if (options->type == ARRST_REQUEST_RESPONSE || stats.request_response_subscriptions > 1) { + return AASRT_BLOCKED; + } else { + return AASRT_NO_CAPACITY; + } + } else { + // create-and-add subscription + existing_subscription = s_create_subscription_record(manager->allocator, options); + AWS_FATAL_ASSERT(existing_subscription != NULL); + aws_hash_table_put(&manager->subscriptions, &existing_subscription->topic_filter_cursor, existing_subscription, NULL); + } + } + + // for simplicity, we require unsubscribes to complete before re-subscribing + AWS_FATAL_ASSERT(existing_subscription != NULL); + AWS_FATAL_ASSERT(existing_subscription->type == options->type); + if (existing_subscription->status == ARRSST_UNSUBSCRIBING) { + return AASRT_BLOCKED; + } + + // register the operation as a listener + aws_array_list_push_back(&existing_subscription->listening_operations, &options->operation_id); + if (existing_subscription->status == ARRSST_SUBSCRIBED) { + return AASRT_SUBSCRIBED; + } + + // do we need to send a subscribe? + if (existing_subscription->status != ARRSST_SUBSCRIBING) { + struct aws_protocol_adapter_subscribe_options subscribe_options = { + .topic_filter = options->topic_filter, + .ack_timeout_seconds = manager->config.operation_timeout_seconds, + }; - return AASRT_FAILURE; + if (aws_mqtt_protocol_adapter_subscribe(manager->protocol_adapter, &subscribe_options)) { + s_remove_operation_id_from_subscription(manager, options->topic_filter, options->operation_id); + return AASRT_FAILURE; + } + + existing_subscription->status = ARRSST_SUBSCRIBING; + } + + return AASRT_SUBSCRIBING; } void aws_rr_subscription_manager_release_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_release_subscription_options *options) { @@ -76,4 +244,4 @@ void aws_rr_subscription_manager_on_protocol_adapter_subscription_event(struct a void aws_rr_subscription_manager_on_protocol_adapter_connection_event(struct aws_rr_subscription_manager *manager, struct aws_protocol_adapter_connection_event *event) { (void)manager; (void)event; -} \ No newline at end of file +} From c565837748290c6e9cc03e155d455d42f14e4b56 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 21 Feb 2024 11:09:42 -0800 Subject: [PATCH 048/124] Retryable flag --- .../request-response/protocol_adapter.h | 1 + source/request-response/protocol_adapter.c | 31 +++++ tests/CMakeLists.txt | 3 +- .../request_response_protocol_adapter_tests.c | 117 ++++++++++++++---- 4 files changed, 124 insertions(+), 28 deletions(-) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 6561c5ab..59d9014b 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -76,6 +76,7 @@ struct aws_protocol_adapter_subscription_event { struct aws_byte_cursor topic_filter; enum aws_protocol_adapter_subscription_event_type event_type; int error_code; + bool retryable; }; /* diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 4e29b286..1890f8df 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -187,6 +187,10 @@ int s_aws_mqtt_protocol_adapter_311_subscribe(void *impl, struct aws_protocol_ad /* Unsubscribe */ +static bool s_is_retryable_unsubscribe311(int error_code) { + return error_code == AWS_ERROR_MQTT_TIMEOUT; +} + static void s_protocol_adapter_311_unsubscribe_completion( struct aws_mqtt_client_connection *connection, uint16_t packet_id, @@ -206,6 +210,7 @@ static void s_protocol_adapter_311_unsubscribe_completion( .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), .event_type = AWS_PASET_UNSUBSCRIBE, .error_code = error_code, + .retryable = s_is_retryable_unsubscribe311(error_code), }; (*adapter->config.subscription_event_callback)(&unsubscribe_event, adapter->config.user_data); @@ -549,6 +554,24 @@ int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adap /* Unsubscribe */ +static bool s_is_retryable_unsubscribe5(enum aws_mqtt5_unsuback_reason_code reason_code, int error_code) { + if (error_code == AWS_ERROR_MQTT5_PACKET_VALIDATION || + error_code == AWS_ERROR_MQTT5_UNSUBSCRIBE_OPTIONS_VALIDATION) { + return false; + } else if (error_code == AWS_ERROR_MQTT_TIMEOUT) { + return true; + } + + switch (reason_code) { + case AWS_MQTT5_UARC_UNSPECIFIED_ERROR: + case AWS_MQTT5_UARC_IMPLEMENTATION_SPECIFIC_ERROR: + return true; + + default: + return false; + } +} + static void s_protocol_adapter_5_unsubscribe_completion( const struct aws_mqtt5_packet_unsuback_view *unsuback, int error_code, @@ -560,6 +583,13 @@ static void s_protocol_adapter_5_unsubscribe_completion( goto done; } + enum aws_mqtt5_unsuback_reason_code reason_code = AWS_MQTT5_UARC_SUCCESS; + if (unsuback != NULL && unsuback->reason_code_count > 0) { + reason_code = unsuback->reason_codes[0]; + } + + bool is_retryable = s_is_retryable_unsubscribe5(reason_code, error_code); + if (error_code == AWS_ERROR_SUCCESS) { if (unsuback == NULL || unsuback->reason_code_count != 1 || unsuback->reason_codes[0] >= 128) { error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; @@ -570,6 +600,7 @@ static void s_protocol_adapter_5_unsubscribe_completion( .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), .event_type = AWS_PASET_UNSUBSCRIBE, .error_code = error_code, + .retryable = is_retryable, }; (*adapter->config.subscription_event_callback)(&unsubscribe_event, adapter->config.user_data); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3b86cf7e..6d1992c8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -454,7 +454,8 @@ add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_reason_c add_test_case(request_response_mqtt5_protocol_adapter_subscribe_failure_timeout) add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_success) add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_failure_error_code) -add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code) +add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_retryable) +add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_not_retryable) add_test_case(request_response_mqtt5_protocol_adapter_unsubscribe_failure_timeout) add_test_case(request_response_mqtt5_protocol_adapter_publish_success) add_test_case(request_response_mqtt5_protocol_adapter_publish_failure_error_code) diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/request_response_protocol_adapter_tests.c index 0c317276..2e45e065 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/request_response_protocol_adapter_tests.c @@ -37,6 +37,7 @@ struct request_response_protocol_adapter_subscription_event_record { enum aws_protocol_adapter_subscription_event_type event_type; struct aws_byte_buf topic_filter; int error_code; + bool retryable; }; static void s_request_response_protocol_adapter_subscription_event_record_init( @@ -44,12 +45,14 @@ static void s_request_response_protocol_adapter_subscription_event_record_init( struct aws_allocator *allocator, enum aws_protocol_adapter_subscription_event_type event_type, struct aws_byte_cursor topic_filter, - int error_code) { + int error_code, + bool retryable) { AWS_ZERO_STRUCT(*record); record->event_type = event_type; record->error_code = error_code; + record->retryable = retryable; aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); } @@ -98,7 +101,7 @@ static void s_rr_mqtt_protocol_adapter_test_on_subscription_event( struct request_response_protocol_adapter_subscription_event_record record; s_request_response_protocol_adapter_subscription_event_record_init( - &record, fixture->allocator, event->event_type, event->topic_filter, event->error_code); + &record, fixture->allocator, event->event_type, event->topic_filter, event->error_code, event->retryable); aws_mutex_lock(&fixture->lock); aws_array_list_push_back(&fixture->subscription_events, &record); @@ -313,6 +316,10 @@ static bool s_do_subscription_events_contain(void *context) { continue; } + if (record.retryable != wait_context->expected_event->retryable) { + continue; + } + ++found; } @@ -474,7 +481,8 @@ static void s_wait_for_publish_results_contains( enum protocol_adapter_operation_test_type { PAOTT_SUCCESS, PAOTT_FAILURE_TIMEOUT, - PAOTT_FAILURE_REASON_CODE, + PAOTT_FAILURE_REASON_CODE_RETRYABLE, + PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE, PAOTT_FAILURE_ERROR_CODE, }; @@ -507,7 +515,8 @@ static int s_test_type_to_expected_error_code( switch (test_type) { case PAOTT_FAILURE_TIMEOUT: return AWS_ERROR_MQTT_TIMEOUT; - case PAOTT_FAILURE_REASON_CODE: + case PAOTT_FAILURE_REASON_CODE_RETRYABLE: + case PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE: return AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; case PAOTT_FAILURE_ERROR_CODE: return AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY; @@ -518,7 +527,8 @@ static int s_test_type_to_expected_error_code( switch (test_type) { case PAOTT_FAILURE_TIMEOUT: return AWS_ERROR_MQTT_TIMEOUT; - case PAOTT_FAILURE_REASON_CODE: + case PAOTT_FAILURE_REASON_CODE_RETRYABLE: + case PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE: return AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; case PAOTT_FAILURE_ERROR_CODE: return AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY; @@ -539,7 +549,8 @@ static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( if (test_type == PAOTT_SUCCESS) { test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = aws_mqtt5_server_send_suback_on_subscribe; - } else if (test_type == PAOTT_FAILURE_REASON_CODE) { + } else if ( + test_type == PAOTT_FAILURE_REASON_CODE_RETRYABLE || test_type == PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE) { test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = s_aws_mqtt5_server_send_failed_suback_on_subscribe; } @@ -573,7 +584,8 @@ static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( allocator, AWS_PASET_SUBSCRIBE, aws_byte_cursor_from_c_str("hello/world"), - expected_error_code); + expected_error_code, + false); struct aws_protocol_adapter_subscribe_options subscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -624,7 +636,8 @@ static int s_request_response_mqtt5_protocol_adapter_subscribe_failure_reason_co void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_REASON_CODE)); + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_subscribe_test( + allocator, PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE)); return AWS_OP_SUCCESS; } @@ -647,11 +660,33 @@ AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_subscribe_failure_error_code, s_request_response_mqtt5_protocol_adapter_subscribe_failure_error_code_fn) -static enum aws_mqtt5_unsuback_reason_code s_failed_unsuback_reason_codes[] = { +static enum aws_mqtt5_unsuback_reason_code s_retryable_failed_unsuback_reason_codes[] = { + AWS_MQTT5_UARC_IMPLEMENTATION_SPECIFIC_ERROR, +}; + +static int s_aws_mqtt5_server_send_retryable_failed_unsuback_on_unsubscribe( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data) { + (void)packet; + (void)user_data; + + struct aws_mqtt5_packet_subscribe_view *unsubscribe_view = packet; + + struct aws_mqtt5_packet_unsuback_view unsuback_view = { + .packet_id = unsubscribe_view->packet_id, + .reason_code_count = AWS_ARRAY_SIZE(s_retryable_failed_unsuback_reason_codes), + .reason_codes = s_retryable_failed_unsuback_reason_codes, + }; + + return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_UNSUBACK, &unsuback_view); +} + +static enum aws_mqtt5_unsuback_reason_code s_not_retryable_failed_unsuback_reason_codes[] = { AWS_MQTT5_UARC_NOT_AUTHORIZED, }; -static int s_aws_mqtt5_server_send_failed_unsuback_on_unsubscribe( +static int s_aws_mqtt5_server_send_not_retryable_failed_unsuback_on_unsubscribe( void *packet, struct aws_mqtt5_server_mock_connection_context *connection, void *user_data) { @@ -662,8 +697,8 @@ static int s_aws_mqtt5_server_send_failed_unsuback_on_unsubscribe( struct aws_mqtt5_packet_unsuback_view unsuback_view = { .packet_id = unsubscribe_view->packet_id, - .reason_code_count = AWS_ARRAY_SIZE(s_failed_unsuback_reason_codes), - .reason_codes = s_failed_unsuback_reason_codes, + .reason_code_count = AWS_ARRAY_SIZE(s_not_retryable_failed_unsuback_reason_codes), + .reason_codes = s_not_retryable_failed_unsuback_reason_codes, }; return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_UNSUBACK, &unsuback_view); @@ -680,9 +715,12 @@ static int s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test( if (test_type == PAOTT_SUCCESS) { test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = aws_mqtt5_mock_server_handle_unsubscribe_unsuback_success; - } else if (test_type == PAOTT_FAILURE_REASON_CODE) { + } else if (test_type == PAOTT_FAILURE_REASON_CODE_RETRYABLE) { test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = - s_aws_mqtt5_server_send_failed_unsuback_on_unsubscribe; + s_aws_mqtt5_server_send_retryable_failed_unsuback_on_unsubscribe; + } else if (test_type == PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE) { + test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = + s_aws_mqtt5_server_send_not_retryable_failed_unsuback_on_unsubscribe; } if (test_type == PAOTT_FAILURE_ERROR_CODE) { @@ -714,7 +752,8 @@ static int s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test( allocator, AWS_PASET_UNSUBSCRIBE, aws_byte_cursor_from_c_str("hello/world"), - expected_error_code); + expected_error_code, + test_type == PAOTT_FAILURE_REASON_CODE_RETRYABLE || test_type == PAOTT_FAILURE_TIMEOUT); struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -762,19 +801,35 @@ AWS_TEST_CASE( request_response_mqtt5_protocol_adapter_unsubscribe_failure_timeout, s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_timeout_fn) -static int s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_fn( +static int s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_retryable_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + ASSERT_SUCCESS( + s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test(allocator, PAOTT_FAILURE_REASON_CODE_RETRYABLE)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_retryable, + s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_retryable_fn) + +static int s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_not_retryable_fn( struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test(allocator, PAOTT_FAILURE_REASON_CODE)); + ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_unsubscribe_test( + allocator, PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE)); return AWS_OP_SUCCESS; } AWS_TEST_CASE( - request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code, - s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_fn) + request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_not_retryable, + s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_reason_code_not_retryable_fn) static int s_request_response_mqtt5_protocol_adapter_unsubscribe_failure_error_code_fn( struct aws_allocator *allocator, @@ -822,7 +877,8 @@ static int s_do_request_response_mqtt5_protocol_adapter_publish_test( if (test_type == PAOTT_SUCCESS) { test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = aws_mqtt5_mock_server_handle_publish_puback; - } else if (test_type == PAOTT_FAILURE_REASON_CODE) { + } else if ( + test_type == PAOTT_FAILURE_REASON_CODE_RETRYABLE || test_type == PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE) { test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = s_aws_mqtt5_server_send_failed_puback_on_publish; } @@ -899,7 +955,8 @@ static int s_request_response_mqtt5_protocol_adapter_publish_failure_reason_code void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_do_request_response_mqtt5_protocol_adapter_publish_test(allocator, PAOTT_FAILURE_REASON_CODE)); + ASSERT_SUCCESS( + s_do_request_response_mqtt5_protocol_adapter_publish_test(allocator, PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE)); return AWS_OP_SUCCESS; } @@ -1153,7 +1210,8 @@ static int s_do_request_response_mqtt311_protocol_adapter_subscribe_test( case PAOTT_SUCCESS: mqtt_mock_server_enable_auto_ack(test_context_311->mock_server); break; - case PAOTT_FAILURE_REASON_CODE: + case PAOTT_FAILURE_REASON_CODE_RETRYABLE: + case PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE: mqtt_mock_server_enable_auto_ack(test_context_311->mock_server); mqtt_mock_server_suback_reason_code(test_context_311->mock_server, 128); break; @@ -1196,7 +1254,8 @@ static int s_do_request_response_mqtt311_protocol_adapter_subscribe_test( allocator, AWS_PASET_SUBSCRIBE, aws_byte_cursor_from_c_str("hello/world"), - expected_error_code); + expected_error_code, + false); struct aws_protocol_adapter_subscribe_options subscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -1257,7 +1316,8 @@ static int s_request_response_mqtt311_protocol_adapter_subscribe_failure_reason_ void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_do_request_response_mqtt311_protocol_adapter_subscribe_test(allocator, PAOTT_FAILURE_REASON_CODE)); + ASSERT_SUCCESS(s_do_request_response_mqtt311_protocol_adapter_subscribe_test( + allocator, PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE)); return AWS_OP_SUCCESS; } @@ -1284,7 +1344,8 @@ static int s_do_request_response_mqtt311_protocol_adapter_unsubscribe_test( case PAOTT_SUCCESS: mqtt_mock_server_enable_auto_ack(test_context_311->mock_server); break; - case PAOTT_FAILURE_REASON_CODE: + case PAOTT_FAILURE_REASON_CODE_RETRYABLE: + case PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE: // 311 does not have unsuback reason codes or a way to fail AWS_FATAL_ASSERT(false); break; @@ -1327,7 +1388,8 @@ static int s_do_request_response_mqtt311_protocol_adapter_unsubscribe_test( allocator, AWS_PASET_UNSUBSCRIBE, aws_byte_cursor_from_c_str("hello/world"), - expected_error_code); + expected_error_code, + expected_error_code == AWS_ERROR_MQTT_TIMEOUT); struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -1401,7 +1463,8 @@ static int s_do_request_response_mqtt311_protocol_adapter_publish_test( case PAOTT_SUCCESS: mqtt_mock_server_enable_auto_ack(test_context_311->mock_server); break; - case PAOTT_FAILURE_REASON_CODE: + case PAOTT_FAILURE_REASON_CODE_RETRYABLE: + case PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE: // 311 does not have puback reason codes or a way to fail AWS_FATAL_ASSERT(false); break; From 212e6c7c4ea72638d1676c076ad0895cb942c004 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 21 Feb 2024 14:52:11 -0800 Subject: [PATCH 049/124] Check point --- .../request-response/protocol_adapter.h | 2 +- .../request-response/subscription_manager.h | 22 +- .../request-response/subscription_manager.c | 205 +++++++++++------- 3 files changed, 134 insertions(+), 95 deletions(-) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index 59d9014b..fef91375 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -95,7 +95,7 @@ enum aws_protocol_adapter_connection_event_type { }; /* - * An event emitted by the protocol adapter whenever the protocol client successfully reconnects to the broker. + * An event emitted by the protocol adapter whenever the protocol client's connection status changes */ struct aws_protocol_adapter_connection_event { enum aws_protocol_adapter_connection_event_type event_type; diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 3211ace7..f3ab0e9d 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -14,20 +14,9 @@ struct aws_mqtt_protocol_adapter; struct aws_protocol_adapter_connection_event; struct aws_protocol_adapter_subscription_event; -enum aws_rr_subscription_status_type { - ARRSST_INVALID, - ARRSST_SUBSCRIBING, - ARRSST_SUBSCRIBED, - ARRSST_UNSUBSCRIBING, -}; - -enum aws_rr_subscription_type { - ARRST_EVENT_STREAM, - ARRST_REQUEST_RESPONSE, -}; - enum aws_rr_subscription_event_type { - ARRSET_SUBSCRIPTION_ESTABLISHED, + ARRSET_SUBSCRIPTION_SUCCESS, + ARRSET_SUBSCRIPTION_FAILURE, ARRSET_SUBSCRIPTION_LOST }; @@ -57,6 +46,13 @@ struct aws_rr_subscription_manager { /* &aws_request_response_subscription.topic_filter_cursor -> aws_request_response_subscription * */ struct aws_hash_table subscriptions; + + bool is_protocol_client_connected; +}; + +enum aws_rr_subscription_type { + ARRST_EVENT_STREAM, + ARRST_REQUEST_RESPONSE, }; struct aws_rr_acquire_subscription_options { diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 60149b79..fb1d08e1 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -8,28 +8,16 @@ #include #include -struct aws_rr_subscription { - struct aws_allocator *allocator; - - struct aws_byte_buf topic_filter; - struct aws_byte_cursor topic_filter_cursor; - - struct aws_hash_table listening_operations; - - enum aws_rr_subscription_status_type status; - enum aws_rr_subscription_type type; +enum aws_rr_subscription_status_type { + ARRSST_SUBSCRIBED, + ARRSST_NOT_SUBSCRIBED, }; -static void s_aws_rr_subscription_destroy(void *element) { - struct aws_rr_subscription *subscription = element; - - aws_byte_buf_clean_up(&subscription->topic_filter); - - ??; - aws_hash_table_clean_up(&subscription->listening_operations); - - aws_mem_release(subscription->allocator, subscription); -} +enum aws_rr_subscription_pending_action_type { + ARRSPAT_NOTHING, + ARRSPAT_SUBSCRIBING, + ARRSPAT_UNSUBSCRIBING, +}; struct aws_rr_subscription_listener { struct aws_allocator *allocator; @@ -37,7 +25,7 @@ struct aws_rr_subscription_listener { }; static uint64_t s_aws_hash_subscription_listener(const void *item) { - struct aws_rr_subscription_listener *listener = item; + const struct aws_rr_subscription_listener *listener = item; return listener->operation_id; } @@ -55,70 +43,77 @@ static void s_aws_subscription_listener_destroy(void *element) { aws_mem_release(listener->allocator, listener); } -static struct aws_rr_subscription *s_create_subscription_record(struct aws_allocator *allocator, struct aws_rr_acquire_subscription_options *options) { - struct aws_rr_subscription *record = aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_subscription)); +struct aws_rr_subscription_record { + struct aws_allocator *allocator; + + struct aws_byte_buf topic_filter; + struct aws_byte_cursor topic_filter_cursor; + + struct aws_hash_table listeners; + + enum aws_rr_subscription_status_type status; + enum aws_rr_subscription_pending_action_type pending_action; + + enum aws_rr_subscription_type type; +}; + +static void s_aws_rr_subscription_record_destroy(void *element) { + struct aws_rr_subscription_record *record = element; + + aws_byte_buf_clean_up(&record->topic_filter); + aws_hash_table_clean_up(&record->listeners); + + aws_mem_release(record->allocator, record); +} + +static struct aws_rr_subscription_record *s_aws_rr_subscription_new(struct aws_allocator *allocator, struct aws_rr_acquire_subscription_options *options) { + struct aws_rr_subscription_record *record = aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_subscription_record)); record->allocator = allocator; aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, options->topic_filter); record->topic_filter_cursor = aws_byte_cursor_from_buf(&record->topic_filter); - aws_hash_table_init(&record->listening_operations, allocator, 4, s_aws_hash_subscription_listener, + aws_hash_table_init(&record->listeners, allocator, 4, s_aws_hash_subscription_listener, s_aws_subscription_listener_hash_equality, NULL, s_aws_subscription_listener_destroy); - record->status = ARRSST_INVALID; + record->status = ARRSST_NOT_SUBSCRIBED; + record->pending_action = ARRSPAT_NOTHING; + record->type = options->type; return record; } -int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, struct aws_rr_subscription_manager_options *options) { - AWS_ZERO_STRUCT(*manager); - - if (options == NULL || options->max_subscriptions < 1 || options->operation_timeout_seconds == 0) { - return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); +static void s_subscription_record_unsubscribe(struct aws_rr_subscription_manager *manager, struct aws_rr_subscription_record *record, bool forced) { + if (!forced) { + if ((record->status != ARRSST_SUBSCRIBED) || (record->pending_action == ARRSPAT_NOTHING)) { + return; + } } - manager->allocator = allocator; - manager->config = *options; - manager->protocol_adapter = protocol_adapter; + struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { + .topic_filter = record->topic_filter_cursor, + .ack_timeout_seconds = manager->config.operation_timeout_seconds, + }; - if (aws_hash_table_init(&manager->subscriptions, allocator, options->max_subscriptions, aws_hash_byte_cursor_ptr, - aws_mqtt_byte_cursor_hash_equality, NULL, s_aws_rr_subscription_destroy)) { - return AWS_OP_ERR; + if (aws_mqtt_protocol_adapter_unsubscribe(manager->protocol_adapter, &unsubscribe_options)) { + return; } - return AWS_OP_SUCCESS; -} - -static void s_subscription_record_unsubscribe(struct aws_rr_subscription_manager *manager, struct aws_rr_subscription *subscription) { - if (subscription->status == ARRSST_SUBSCRIBING || subscription->status == ARRSST_SUBSCRIBED) { - struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { - .topic_filter = subscription->topic_filter_cursor, - .ack_timeout_seconds = manager->config.operation_timeout_seconds, - }; - - aws_mqtt_protocol_adapter_unsubscribe(manager->protocol_adapter, &unsubscribe_options); - - subscription->status = ARRSST_UNSUBSCRIBING; - } + record->pending_action = ARRSPAT_UNSUBSCRIBING; } static int s_rr_subscription_clean_up_foreach_wrap(void *context, struct aws_hash_element *elem) { struct aws_rr_subscription_manager *manager = context; - struct aws_rr_subscription *subscription = elem->value; + struct aws_rr_subscription_record *subscription = elem->value; - s_subscription_record_unsubscribe(manager, subscription); + s_subscription_record_unsubscribe(manager, subscription, true); return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; } -void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager) { - aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_clean_up_foreach_wrap, manager->protocol_adapter); - aws_hash_table_clean_up(&manager->subscriptions); -} - -static struct aws_rr_subscription *s_get_subscription_record(struct aws_rr_subscription_manager *manager, struct aws_byte_cursor topic_filter) { - struct aws_rr_subscription *subscription = NULL; +static struct aws_rr_subscription_record *s_get_subscription_record(struct aws_rr_subscription_manager *manager, struct aws_byte_cursor topic_filter) { + struct aws_rr_subscription_record *subscription = NULL; struct aws_hash_element *element = NULL; if (aws_hash_table_find(&manager->subscriptions, &topic_filter, &element)) { return NULL; @@ -137,7 +132,7 @@ struct aws_subscription_stats { }; static int s_rr_subscription_count_foreach_wrap(void *context, struct aws_hash_element *elem) { - struct aws_rr_subscription *subscription = elem->value; + struct aws_rr_subscription_record *subscription = elem->value; struct aws_subscription_stats *stats = context; if (subscription->type == ARRST_EVENT_STREAM) { @@ -155,11 +150,9 @@ static void s_get_subscription_stats(struct aws_rr_subscription_manager *manager aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_count_foreach_wrap, stats); } -static void s_remove_operation_id_from_subscription(struct aws_rr_subscription_manager *manager, struct aws_byte_cursor topic_filter, uint64_t operation_id) { - (void)operation_id; - - struct aws_rr_subscription *subscription = s_get_subscription_record(manager, topic_filter); - if (subscription == NULL) { +static void s_remove_listener_from_subscription_record(struct aws_rr_subscription_manager *manager, struct aws_byte_cursor topic_filter, uint64_t operation_id) { + struct aws_rr_subscription_record *record = s_get_subscription_record(manager, topic_filter); + if (record == NULL) { return; } @@ -167,25 +160,48 @@ static void s_remove_operation_id_from_subscription(struct aws_rr_subscription_m .operation_id = operation_id, }; - aws_hash_table_remove(&subscription->listening_operations, &listener, NULL, NULL); - if (aws_hash_table_get_entry_count(&subscription->listening_operations) == 0) { - s_subscription_record_unsubscribe(manager, subscription); + aws_hash_table_remove(&record->listeners, &listener, NULL, NULL); +} +static void s_add_listener_to_subscription_record(struct aws_rr_subscription_record *record, uint64_t operation_id) { + struct aws_rr_subscription_listener *listener = aws_mem_calloc(record->allocator, 1, sizeof(struct aws_rr_subscription_listener)); + listener->allocator = record->allocator; + listener->operation_id = operation_id; + aws_hash_table_put(&record->listeners, listener, listener, NULL); +} + +static int s_rr_subscription_cull_unused_subscriptions_wrapper(void *context, struct aws_hash_element *elem) { + struct aws_rr_subscription_record *record = elem->value; + struct aws_rr_subscription_manager *manager = context; + + if (manager->is_protocol_client_connected && aws_hash_table_get_entry_count(&record->listeners) == 0) { + s_subscription_record_unsubscribe(manager, record, false); + } + + if (record->status == ARRSST_NOT_SUBSCRIBED && record->pending_action != ARRSPAT_SUBSCRIBING) { + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; + } else { + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; } } +static void s_cull_unused_subscriptions(struct aws_rr_subscription_manager *manager) { + aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_cull_unused_subscriptions_wrapper, manager); +} + enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_acquire_subscription_options *options) { - struct aws_rr_subscription *existing_subscription = s_get_subscription_record(manager, options->topic_filter); + struct aws_rr_subscription_record *existing_record = s_get_subscription_record(manager, options->topic_filter); // is no subscription present? - if (existing_subscription == NULL) { + if (existing_record == NULL) { // is the budget used up? struct aws_subscription_stats stats; s_get_subscription_stats(manager, &stats); if (stats.event_stream_subscriptions + stats.request_response_subscriptions >= manager->config.max_subscriptions) { + s_cull_unused_subscriptions(manager); // could space eventually free up? if (options->type == ARRST_REQUEST_RESPONSE || stats.request_response_subscriptions > 1) { return AASRT_BLOCKED; @@ -194,38 +210,39 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su } } else { // create-and-add subscription - existing_subscription = s_create_subscription_record(manager->allocator, options); - AWS_FATAL_ASSERT(existing_subscription != NULL); - aws_hash_table_put(&manager->subscriptions, &existing_subscription->topic_filter_cursor, existing_subscription, NULL); + existing_record = s_aws_rr_subscription_new(manager->allocator, options); + AWS_FATAL_ASSERT(existing_record != NULL); + aws_hash_table_put(&manager->subscriptions, &existing_record->topic_filter_cursor, existing_record, NULL); } } + AWS_FATAL_ASSERT(existing_record != NULL); + AWS_FATAL_ASSERT(existing_record->type == options->type); + // for simplicity, we require unsubscribes to complete before re-subscribing - AWS_FATAL_ASSERT(existing_subscription != NULL); - AWS_FATAL_ASSERT(existing_subscription->type == options->type); - if (existing_subscription->status == ARRSST_UNSUBSCRIBING) { + if (existing_record->pending_action == ARRSPAT_UNSUBSCRIBING) { return AASRT_BLOCKED; } // register the operation as a listener - aws_array_list_push_back(&existing_subscription->listening_operations, &options->operation_id); - if (existing_subscription->status == ARRSST_SUBSCRIBED) { + s_add_listener_to_subscription_record(existing_record, options->operation_id); + if (existing_record->status == ARRSST_SUBSCRIBED) { return AASRT_SUBSCRIBED; } // do we need to send a subscribe? - if (existing_subscription->status != ARRSST_SUBSCRIBING) { + if (existing_record->pending_action != ARRSPAT_SUBSCRIBING) { struct aws_protocol_adapter_subscribe_options subscribe_options = { .topic_filter = options->topic_filter, .ack_timeout_seconds = manager->config.operation_timeout_seconds, }; if (aws_mqtt_protocol_adapter_subscribe(manager->protocol_adapter, &subscribe_options)) { - s_remove_operation_id_from_subscription(manager, options->topic_filter, options->operation_id); + s_remove_listener_from_subscription_record(manager, options->topic_filter, options->operation_id); return AASRT_FAILURE; } - existing_subscription->status = ARRSST_SUBSCRIBING; + existing_record->pending_action = ARRSPAT_SUBSCRIBING; } return AASRT_SUBSCRIBING; @@ -245,3 +262,29 @@ void aws_rr_subscription_manager_on_protocol_adapter_connection_event(struct aws (void)manager; (void)event; } + +int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, struct aws_rr_subscription_manager_options *options) { + AWS_ZERO_STRUCT(*manager); + + if (options == NULL || options->max_subscriptions < 1 || options->operation_timeout_seconds == 0) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + manager->allocator = allocator; + manager->config = *options; + manager->protocol_adapter = protocol_adapter; + + if (aws_hash_table_init(&manager->subscriptions, allocator, options->max_subscriptions, aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, NULL, s_aws_rr_subscription_record_destroy)) { + return AWS_OP_ERR; + } + + manager->is_protocol_client_connected = aws_mqtt_protocol_adapter_is_connected(protocol_adapter); + + return AWS_OP_SUCCESS; +} + +void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager) { + aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_clean_up_foreach_wrap, manager->protocol_adapter); + aws_hash_table_clean_up(&manager->subscriptions); +} From b9cee40681026ca3a14da7d5bddd00182bd61f9f Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 22 Feb 2024 14:52:51 -0800 Subject: [PATCH 050/124] Checkpoint --- include/aws/mqtt/mqtt.h | 1 + .../request-response/subscription_manager.h | 50 ++- source/mqtt.c | 1 + .../request-response/subscription_manager.c | 289 ++++++++++++++---- 4 files changed, 276 insertions(+), 65 deletions(-) diff --git a/include/aws/mqtt/mqtt.h b/include/aws/mqtt/mqtt.h index 8e7cd0ef..d8034640 100644 --- a/include/aws/mqtt/mqtt.h +++ b/include/aws/mqtt/mqtt.h @@ -94,6 +94,7 @@ enum aws_mqtt_log_subject { AWS_LS_MQTT5_CLIENT, AWS_LS_MQTT5_CANARY, AWS_LS_MQTT5_TO_MQTT3_ADAPTER, + AWS_LS_MQTT_REQUEST_RESPONSE, }; /** Function called on cleanup of a userdata. */ diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index f3ab0e9d..636cd4a8 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -15,9 +15,9 @@ struct aws_protocol_adapter_connection_event; struct aws_protocol_adapter_subscription_event; enum aws_rr_subscription_event_type { - ARRSET_SUBSCRIPTION_SUCCESS, - ARRSET_SUBSCRIPTION_FAILURE, - ARRSET_SUBSCRIPTION_LOST + ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + ARRSET_SUBSCRIPTION_ENDED }; struct aws_rr_subscription_status_event { @@ -26,7 +26,13 @@ struct aws_rr_subscription_status_event { uint64_t operation_id; }; -typedef void (aws_rr_subscription_status_event_callbacK_fn)(struct aws_rr_subscription_status_event, void *userdata); +/* + * Invariant: despite being on the same thread, these callbacks must be queued as cross-thread tasks on the native + * request-response client. This allows us to iterate internal collections without worrying about external + * callers disrupting things by invoking APIs back on us. + */ +typedef void( + aws_rr_subscription_status_event_callbacK_fn)(const struct aws_rr_subscription_status_event *event, void *userdata); struct aws_rr_subscription_manager_options { size_t max_subscriptions; @@ -36,6 +42,18 @@ struct aws_rr_subscription_manager_options { void *userdata; }; +/* + * The subscription manager works from a purely lazy perspective. Unsubscribes (from topic filters that are no longer + * referenced) occur when looking for new subscription space. Unsubscribe failures don't trigger anything special, + * we'll just try again next time we look for subscription space. Subscribes are attempted on idle subscriptions + * that still need them, either in response to a new operation listener or a connection resumption event. + * + * We only allow one subscribe or unsubscribe to be outstanding at once for a given topic. If an operation requires a + * subscription while an unsubscribe is in progress the operation is blocked until the unsubscribe resolves. + * + * These invariants are dropped during shutdown. In that case, we immediately send unsubscribes for everything + * that is not already unsubscribing. + */ struct aws_rr_subscription_manager { struct aws_allocator *allocator; @@ -44,7 +62,7 @@ struct aws_rr_subscription_manager { /* non-owning reference; the client is responsible for destroying this asynchronously (listener detachment) */ struct aws_mqtt_protocol_adapter *protocol_adapter; - /* &aws_request_response_subscription.topic_filter_cursor -> aws_request_response_subscription * */ + /* &aws_rr_subscription_record.topic_filter_cursor -> aws_rr_subscription_record * */ struct aws_hash_table subscriptions; bool is_protocol_client_connected; @@ -76,17 +94,29 @@ enum aws_acquire_subscription_result_type { AWS_EXTERN_C_BEGIN -int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, struct aws_rr_subscription_manager_options *options); +int aws_rr_subscription_manager_init( + struct aws_rr_subscription_manager *manager, + struct aws_allocator *allocator, + struct aws_mqtt_protocol_adapter *protocol_adapter, + const struct aws_rr_subscription_manager_options *options); void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager); -enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_acquire_subscription_options *options); +enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription( + struct aws_rr_subscription_manager *manager, + const struct aws_rr_acquire_subscription_options *options); -void aws_rr_subscription_manager_release_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_release_subscription_options *options); +void aws_rr_subscription_manager_release_subscription( + struct aws_rr_subscription_manager *manager, + const struct aws_rr_release_subscription_options *options); -void aws_rr_subscription_manager_on_protocol_adapter_subscription_event(struct aws_rr_subscription_manager *manager, struct aws_protocol_adapter_subscription_event *event); +void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( + struct aws_rr_subscription_manager *manager, + const struct aws_protocol_adapter_subscription_event *event); -void aws_rr_subscription_manager_on_protocol_adapter_connection_event(struct aws_rr_subscription_manager *manager, struct aws_protocol_adapter_connection_event *event); +void aws_rr_subscription_manager_on_protocol_adapter_connection_event( + struct aws_rr_subscription_manager *manager, + const struct aws_protocol_adapter_connection_event *event); AWS_EXTERN_C_END diff --git a/source/mqtt.c b/source/mqtt.c index fd16340a..0fbafdb5 100644 --- a/source/mqtt.c +++ b/source/mqtt.c @@ -254,6 +254,7 @@ static struct aws_error_info_list s_error_list = { 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"), + DEFINE_LOG_SUBJECT_INFO(AWS_LS_MQTT_REQUEST_RESPONSE, "mqtt-request-response-client", "MQTT request-response client logging"), }; /* clang-format on */ diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index fb1d08e1..31887f86 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -1,10 +1,11 @@ /** -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0. -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ #include +#include #include #include @@ -13,6 +14,12 @@ enum aws_rr_subscription_status_type { ARRSST_NOT_SUBSCRIBED, }; +/* + * Invariant: subscriptions can only transition from nothing -> {subscribing, unsubscribing} + * + * In particular, the logic blocks subscribing while unsubscribing and unsubscribing while subscribing (unless + * shutting down). + */ enum aws_rr_subscription_pending_action_type { ARRSPAT_NOTHING, ARRSPAT_SUBSCRIBING, @@ -57,6 +64,15 @@ struct aws_rr_subscription_record { enum aws_rr_subscription_type type; }; +static void s_aws_rr_subscription_record_log_invariant_violations(const struct aws_rr_subscription_record *record) { + if (record->status == ARRSST_SUBSCRIBED && record->pending_action == ARRSPAT_SUBSCRIBING) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "MQTT request response subscription ('" PRInSTR "') invalid state", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); + } +} + static void s_aws_rr_subscription_record_destroy(void *element) { struct aws_rr_subscription_record *record = element; @@ -66,15 +82,23 @@ static void s_aws_rr_subscription_record_destroy(void *element) { aws_mem_release(record->allocator, record); } -static struct aws_rr_subscription_record *s_aws_rr_subscription_new(struct aws_allocator *allocator, struct aws_rr_acquire_subscription_options *options) { +static struct aws_rr_subscription_record *s_aws_rr_subscription_new( + struct aws_allocator *allocator, + const struct aws_rr_acquire_subscription_options *options) { struct aws_rr_subscription_record *record = aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_subscription_record)); record->allocator = allocator; aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, options->topic_filter); record->topic_filter_cursor = aws_byte_cursor_from_buf(&record->topic_filter); - aws_hash_table_init(&record->listeners, allocator, 4, s_aws_hash_subscription_listener, - s_aws_subscription_listener_hash_equality, NULL, s_aws_subscription_listener_destroy); + aws_hash_table_init( + &record->listeners, + allocator, + 4, + s_aws_hash_subscription_listener, + s_aws_subscription_listener_hash_equality, + NULL, + s_aws_subscription_listener_destroy); record->status = ARRSST_NOT_SUBSCRIBED; record->pending_action = ARRSPAT_NOTHING; @@ -84,11 +108,27 @@ static struct aws_rr_subscription_record *s_aws_rr_subscription_new(struct aws_a return record; } -static void s_subscription_record_unsubscribe(struct aws_rr_subscription_manager *manager, struct aws_rr_subscription_record *record, bool forced) { - if (!forced) { - if ((record->status != ARRSST_SUBSCRIBED) || (record->pending_action == ARRSPAT_NOTHING)) { - return; - } +static void s_subscription_record_unsubscribe( + struct aws_rr_subscription_manager *manager, + struct aws_rr_subscription_record *record, + bool shutdown) { + + bool currently_subscribed = record->status == ARRSST_SUBSCRIBED; + bool currently_subscribing = record->pending_action == ARRSPAT_SUBSCRIBING; + bool currently_unsubscribing = record->pending_action == ARRSPAT_UNSUBSCRIBING; + + /* + * The difference between a shutdown unsubscribe and a normal unsubscribe is that on a shutdown we will "chase" + * a pending subscribe with an unsubscribe (breaking the invariant of never having multiple MQTT operations + * pending on a subscription). + */ + bool should_unsubscribe = currently_subscribed && !currently_unsubscribing; + if (shutdown) { + should_unsubscribe = should_unsubscribe || currently_subscribing; + } + + if (!should_unsubscribe) { + return; } struct aws_protocol_adapter_unsubscribe_options unsubscribe_options = { @@ -101,8 +141,12 @@ static void s_subscription_record_unsubscribe(struct aws_rr_subscription_manager } record->pending_action = ARRSPAT_UNSUBSCRIBING; + + // check_invariants may no longer be true now because we might have converted a pending subscribe to a pending + // unsubscribe } +/* Only called when shutting down the client */ static int s_rr_subscription_clean_up_foreach_wrap(void *context, struct aws_hash_element *elem) { struct aws_rr_subscription_manager *manager = context; struct aws_rr_subscription_record *subscription = elem->value; @@ -112,7 +156,9 @@ static int s_rr_subscription_clean_up_foreach_wrap(void *context, struct aws_has return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; } -static struct aws_rr_subscription_record *s_get_subscription_record(struct aws_rr_subscription_manager *manager, struct aws_byte_cursor topic_filter) { +static struct aws_rr_subscription_record *s_get_subscription_record( + struct aws_rr_subscription_manager *manager, + struct aws_byte_cursor topic_filter) { struct aws_rr_subscription_record *subscription = NULL; struct aws_hash_element *element = NULL; if (aws_hash_table_find(&manager->subscriptions, &topic_filter, &element)) { @@ -132,7 +178,7 @@ struct aws_subscription_stats { }; static int s_rr_subscription_count_foreach_wrap(void *context, struct aws_hash_element *elem) { - struct aws_rr_subscription_record *subscription = elem->value; + const struct aws_rr_subscription_record *subscription = elem->value; struct aws_subscription_stats *stats = context; if (subscription->type == ARRST_EVENT_STREAM) { @@ -144,13 +190,18 @@ static int s_rr_subscription_count_foreach_wrap(void *context, struct aws_hash_e return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; } -static void s_get_subscription_stats(struct aws_rr_subscription_manager *manager, struct aws_subscription_stats *stats) { +static void s_get_subscription_stats( + struct aws_rr_subscription_manager *manager, + struct aws_subscription_stats *stats) { AWS_ZERO_STRUCT(*stats); aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_count_foreach_wrap, stats); } -static void s_remove_listener_from_subscription_record(struct aws_rr_subscription_manager *manager, struct aws_byte_cursor topic_filter, uint64_t operation_id) { +static void s_remove_listener_from_subscription_record( + struct aws_rr_subscription_manager *manager, + struct aws_byte_cursor topic_filter, + uint64_t operation_id) { struct aws_rr_subscription_record *record = s_get_subscription_record(manager, topic_filter); if (record == NULL) { return; @@ -164,7 +215,8 @@ static void s_remove_listener_from_subscription_record(struct aws_rr_subscriptio } static void s_add_listener_to_subscription_record(struct aws_rr_subscription_record *record, uint64_t operation_id) { - struct aws_rr_subscription_listener *listener = aws_mem_calloc(record->allocator, 1, sizeof(struct aws_rr_subscription_listener)); + struct aws_rr_subscription_listener *listener = + aws_mem_calloc(record->allocator, 1, sizeof(struct aws_rr_subscription_listener)); listener->allocator = record->allocator; listener->operation_id = operation_id; @@ -175,49 +227,102 @@ static int s_rr_subscription_cull_unused_subscriptions_wrapper(void *context, st struct aws_rr_subscription_record *record = elem->value; struct aws_rr_subscription_manager *manager = context; - if (manager->is_protocol_client_connected && aws_hash_table_get_entry_count(&record->listeners) == 0) { - s_subscription_record_unsubscribe(manager, record, false); - } + if (aws_hash_table_get_entry_count(&record->listeners) == 0) { + if (manager->is_protocol_client_connected) { + s_subscription_record_unsubscribe(manager, record, false); + } - if (record->status == ARRSST_NOT_SUBSCRIBED && record->pending_action != ARRSPAT_SUBSCRIBING) { - return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; - } else { - return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; + if (record->status == ARRSST_NOT_SUBSCRIBED && record->pending_action == ARRSPAT_NOTHING) { + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; + } } + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; } static void s_cull_unused_subscriptions(struct aws_rr_subscription_manager *manager) { aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_cull_unused_subscriptions_wrapper, manager); } -enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_acquire_subscription_options *options) { +static int s_rr_activate_idle_subscription( + struct aws_rr_subscription_manager *manager, + struct aws_rr_subscription_record *record) { + int result = AWS_OP_SUCCESS; + + if (manager->is_protocol_client_connected && aws_hash_table_get_entry_count(&record->listeners) > 0) { + if (record->status == ARRSST_NOT_SUBSCRIBED && record->pending_action == ARRSPAT_NOTHING) { + struct aws_protocol_adapter_subscribe_options subscribe_options = { + .topic_filter = record->topic_filter_cursor, + .ack_timeout_seconds = manager->config.operation_timeout_seconds, + }; + + result = aws_mqtt_protocol_adapter_subscribe(manager->protocol_adapter, &subscribe_options); + if (result == AWS_OP_SUCCESS) { + record->pending_action = ARRSPAT_SUBSCRIBING; + } + } + } + + return result; +} + +static void s_emit_subscription_event( + const struct aws_rr_subscription_manager *manager, + const struct aws_rr_subscription_record *record, + enum aws_rr_subscription_event_type type) { + + for (struct aws_hash_iter iter = aws_hash_iter_begin(&record->listeners); !aws_hash_iter_done(&iter); + aws_hash_iter_next(&iter)) { + + struct aws_rr_subscription_listener *listener = iter.element.value; + struct aws_rr_subscription_status_event event = { + .type = type, + .topic_filter = record->topic_filter_cursor, + .operation_id = listener->operation_id, + }; + + (*manager->config.subscription_status_callback)(&event, manager->config.userdata); + } +} + +enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription( + struct aws_rr_subscription_manager *manager, + const struct aws_rr_acquire_subscription_options *options) { struct aws_rr_subscription_record *existing_record = s_get_subscription_record(manager, options->topic_filter); // is no subscription present? if (existing_record == NULL) { + s_cull_unused_subscriptions(manager); // is the budget used up? struct aws_subscription_stats stats; s_get_subscription_stats(manager, &stats); - if (stats.event_stream_subscriptions + stats.request_response_subscriptions >= manager->config.max_subscriptions) { - s_cull_unused_subscriptions(manager); + bool space_for_subscription = + stats.event_stream_subscriptions + stats.request_response_subscriptions < manager->config.max_subscriptions; + if (options->type == ARRST_EVENT_STREAM) { + // event stream subscriptions cannot hog the entire subscription budget + space_for_subscription = + space_for_subscription && (stats.event_stream_subscriptions + 1 < manager->config.max_subscriptions); + } + + if (!space_for_subscription) { // could space eventually free up? if (options->type == ARRST_REQUEST_RESPONSE || stats.request_response_subscriptions > 1) { return AASRT_BLOCKED; } else { return AASRT_NO_CAPACITY; } - } else { - // create-and-add subscription - existing_record = s_aws_rr_subscription_new(manager->allocator, options); - AWS_FATAL_ASSERT(existing_record != NULL); - aws_hash_table_put(&manager->subscriptions, &existing_record->topic_filter_cursor, existing_record, NULL); } + + // create-and-add subscription + existing_record = s_aws_rr_subscription_new(manager->allocator, options); + aws_hash_table_put(&manager->subscriptions, &existing_record->topic_filter_cursor, existing_record, NULL); } AWS_FATAL_ASSERT(existing_record != NULL); AWS_FATAL_ASSERT(existing_record->type == options->type); + s_aws_rr_subscription_record_log_invariant_violations(existing_record); // for simplicity, we require unsubscribes to complete before re-subscribing if (existing_record->pending_action == ARRSPAT_UNSUBSCRIBING) { @@ -231,39 +336,109 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su } // do we need to send a subscribe? - if (existing_record->pending_action != ARRSPAT_SUBSCRIBING) { - struct aws_protocol_adapter_subscribe_options subscribe_options = { - .topic_filter = options->topic_filter, - .ack_timeout_seconds = manager->config.operation_timeout_seconds, - }; + if (s_rr_activate_idle_subscription(manager, existing_record)) { + s_emit_subscription_event(manager, existing_record, ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE); + return AASRT_FAILURE; + } + + s_aws_rr_subscription_record_log_invariant_violations(existing_record); - if (aws_mqtt_protocol_adapter_subscribe(manager->protocol_adapter, &subscribe_options)) { - s_remove_listener_from_subscription_record(manager, options->topic_filter, options->operation_id); - return AASRT_FAILURE; + return AASRT_SUBSCRIBING; +} + +void aws_rr_subscription_manager_release_subscription( + struct aws_rr_subscription_manager *manager, + const struct aws_rr_release_subscription_options *options) { + s_remove_listener_from_subscription_record(manager, options->topic_filter, options->operation_id); +} + +void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( + struct aws_rr_subscription_manager *manager, + const struct aws_protocol_adapter_subscription_event *event) { + struct aws_rr_subscription_record *record = s_get_subscription_record(manager, event->topic_filter); + if (record == NULL) { + return; + } + + if (event->event_type == AWS_PASET_SUBSCRIBE) { + AWS_FATAL_ASSERT(record->pending_action == ARRSPAT_SUBSCRIBING); + + if (event->error_code == AWS_ERROR_SUCCESS) { + record->status = ARRSST_SUBSCRIBED; + s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS); + } else { + s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE); } + } else if (event->event_type == AWS_PASET_UNSUBSCRIBE) { + AWS_FATAL_ASSERT(record->pending_action == ARRSPAT_UNSUBSCRIBING); - existing_record->pending_action = ARRSPAT_SUBSCRIBING; + if (event->error_code == AWS_ERROR_SUCCESS) { + record->status = ARRSST_NOT_SUBSCRIBED; + s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_ENDED); + } } - return AASRT_SUBSCRIBING; + record->pending_action = ARRSPAT_NOTHING; + + s_aws_rr_subscription_record_log_invariant_violations(record); +} + +static int s_rr_activate_idle_subscriptions_wrapper(void *context, struct aws_hash_element *elem) { + struct aws_rr_subscription_record *record = elem->value; + struct aws_rr_subscription_manager *manager = context; + + s_rr_activate_idle_subscription(manager, record); + + s_aws_rr_subscription_record_log_invariant_violations(record); + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; +} + +static void s_activate_idle_subscriptions(struct aws_rr_subscription_manager *manager) { + aws_hash_table_foreach(&manager->subscriptions, s_rr_activate_idle_subscriptions_wrapper, manager); } -void aws_rr_subscription_manager_release_subscription(struct aws_rr_subscription_manager *manager, struct aws_rr_release_subscription_options *options) { - (void)manager; - (void)options; +static int s_apply_session_lost_wrapper(void *context, struct aws_hash_element *elem) { + struct aws_rr_subscription_record *record = elem->value; + struct aws_rr_subscription_manager *manager = context; + + if (record->status == ARRSST_SUBSCRIBED) { + record->status = ARRSST_NOT_SUBSCRIBED; + s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_ENDED); + + if (record->pending_action != ARRSPAT_UNSUBSCRIBING) { + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; + } + } + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; } -void aws_rr_subscription_manager_on_protocol_adapter_subscription_event(struct aws_rr_subscription_manager *manager, struct aws_protocol_adapter_subscription_event *event) { - (void)manager; - (void)event; +static void s_apply_session_lost(struct aws_rr_subscription_manager *manager) { + aws_hash_table_foreach(&manager->subscriptions, s_apply_session_lost_wrapper, manager); } -void aws_rr_subscription_manager_on_protocol_adapter_connection_event(struct aws_rr_subscription_manager *manager, struct aws_protocol_adapter_connection_event *event) { - (void)manager; - (void)event; +void aws_rr_subscription_manager_on_protocol_adapter_connection_event( + struct aws_rr_subscription_manager *manager, + const struct aws_protocol_adapter_connection_event *event) { + if (event->event_type == AWS_PACET_CONNECTED) { + manager->is_protocol_client_connected = true; + if (!event->joined_session) { + s_apply_session_lost(manager); + } + + s_cull_unused_subscriptions(manager); + s_activate_idle_subscriptions(manager); + } else { + manager->is_protocol_client_connected = false; + } } -int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, struct aws_rr_subscription_manager_options *options) { +int aws_rr_subscription_manager_init( + struct aws_rr_subscription_manager *manager, + struct aws_allocator *allocator, + struct aws_mqtt_protocol_adapter *protocol_adapter, + const struct aws_rr_subscription_manager_options *options) { AWS_ZERO_STRUCT(*manager); if (options == NULL || options->max_subscriptions < 1 || options->operation_timeout_seconds == 0) { @@ -274,10 +449,14 @@ int aws_rr_subscription_manager_init(struct aws_rr_subscription_manager *manager manager->config = *options; manager->protocol_adapter = protocol_adapter; - if (aws_hash_table_init(&manager->subscriptions, allocator, options->max_subscriptions, aws_hash_byte_cursor_ptr, - aws_mqtt_byte_cursor_hash_equality, NULL, s_aws_rr_subscription_record_destroy)) { - return AWS_OP_ERR; - } + aws_hash_table_init( + &manager->subscriptions, + allocator, + options->max_subscriptions, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + s_aws_rr_subscription_record_destroy); manager->is_protocol_client_connected = aws_mqtt_protocol_adapter_is_connected(protocol_adapter); From e7e920aea69031110fbf7516a7c2c879d6dbdcc5 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 26 Feb 2024 08:23:29 -0800 Subject: [PATCH 051/124] Some comments --- .../request-response/subscription_manager.h | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 636cd4a8..b46caacd 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -14,9 +14,24 @@ struct aws_mqtt_protocol_adapter; struct aws_protocol_adapter_connection_event; struct aws_protocol_adapter_subscription_event; +/* + * The kind of subscription event being emitted. + */ enum aws_rr_subscription_event_type { + + /* + * A subscribe succeeded + */ ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + + /* + * A subscribe failed + */ ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + + /* + * A previously successful subscription has ended (generally due to a failure to resume a session) + */ ARRSET_SUBSCRIPTION_ENDED }; @@ -32,13 +47,21 @@ struct aws_rr_subscription_status_event { * callers disrupting things by invoking APIs back on us. */ typedef void( - aws_rr_subscription_status_event_callbacK_fn)(const struct aws_rr_subscription_status_event *event, void *userdata); + aws_rr_subscription_status_event_callback_fn)(const struct aws_rr_subscription_status_event *event, void *userdata); struct aws_rr_subscription_manager_options { + + /* + * Maximum number of concurrent subscriptions allowed + */ size_t max_subscriptions; + + /* + * Ack timeout to use for all subscribe and unsubscribe operations + */ uint32_t operation_timeout_seconds; - aws_rr_subscription_status_event_callbacK_fn *subscription_status_callback; + aws_rr_subscription_status_event_callback_fn *subscription_status_callback; void *userdata; }; @@ -85,10 +108,33 @@ struct aws_rr_release_subscription_options { }; enum aws_acquire_subscription_result_type { + + /* + * The requested subscription already exists and is active. The operation can proceed to the next stage. + */ AASRT_SUBSCRIBED, + + /* + * The requested subscription now exists but is not yet active. The operation must wait for the subscribe + * to complete as success or failure. + */ AASRT_SUBSCRIBING, + + /* + * The subscription does not exist and there is no room for it currently. Room may open up in the future, so + * the operation should wait. + */ AASRT_BLOCKED, + + /* + * The subscription does not exist, there is no room for it, and unless an event stream subscription gets + * closed, no room will be available in the future. The operation should be failed. + */ AASRT_NO_CAPACITY, + + /* + * An internal failure occurred while trying to establish the subscription. The operation should be failed. + */ AASRT_FAILURE }; From 881235110e192f11bcb1c65bd15b0e1e8fd839fd Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 26 Feb 2024 11:05:32 -0800 Subject: [PATCH 052/124] testing framework --- .../request-response/subscription_manager.h | 37 ++ .../subscription_manager_tests.c | 337 ++++++++++++++++++ 2 files changed, 374 insertions(+) create mode 100644 tests/request-response/subscription_manager_tests.c diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index b46caacd..855dcf16 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -140,26 +140,63 @@ enum aws_acquire_subscription_result_type { AWS_EXTERN_C_BEGIN +/* + * Initializes a subscription manager. Every native request-response client owns a single subscription manager. + */ int aws_rr_subscription_manager_init( struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, const struct aws_rr_subscription_manager_options *options); +/* + * Cleans up a subscription manager. This is done early in the native request-response client shutdown process. + * After this API is called, no other subscription manager APIs will be called by the request-response client (during + * the rest of the asynchronous shutdown process). + */ void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager); +/* + * Signals to the subscription manager that the native request-response client is processing an operation that + * needs a subscription to a particular topic. Return value indicates to the request-response client how it should + * proceed with processing the operation. + */ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription( struct aws_rr_subscription_manager *manager, const struct aws_rr_acquire_subscription_options *options); +/* + * Signals to the subscription manager that the native request-response client operation no longer + * needs a subscription to a particular topic. + */ void aws_rr_subscription_manager_release_subscription( struct aws_rr_subscription_manager *manager, const struct aws_rr_release_subscription_options *options); +/* + * Notifies the subscription manager of a subscription status event. Invoked by the native request-response client + * that owns the subscription manager. The native request-response client also owns the protocol adapter that + * the subscription event originates from, so the control flow looks like: + * + * [Subscribe] + * subscription manager -> protocol adapter Subscribe -> protocol client Subscribe -> network... + * + * [Result] + * protocol client Suback/Timeout/Error -> protocol adapter -> native request-response client -> + * subscription manager (this API) + */ void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( struct aws_rr_subscription_manager *manager, const struct aws_protocol_adapter_subscription_event *event); +/* + * Notifies the subscription manager of a connection status event. Invoked by the native request-response client + * that owns the subscription manager. The native request-response client also owns the protocol adapter that + * the connection event originates from. The control flow looks like: + * + * protocol client connect/disconnect -> protocol adapter -> native request-response client -> + * Subscription manager (this API) + */ void aws_rr_subscription_manager_on_protocol_adapter_connection_event( struct aws_rr_subscription_manager *manager, const struct aws_protocol_adapter_connection_event *event); diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c new file mode 100644 index 00000000..782745ac --- /dev/null +++ b/tests/request-response/subscription_manager_tests.c @@ -0,0 +1,337 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include + +#include + +enum aws_protocol_adapter_api_type { + PAAT_SUBSCRIBE, + PAAT_UNSUBSCRIBE, +}; + +struct aws_protocol_adapter_api_record { + enum aws_protocol_adapter_api_type type; + + struct aws_byte_buf topic_filter; + struct aws_byte_cursor topic_filter_cursor; + + uint32_t timeout; +}; + +static void s_aws_protocol_adapter_api_record_init_from_subscribe( + struct aws_protocol_adapter_api_record *record, + struct aws_allocator *allocator, + struct aws_byte_cursor topic_filter, + uint32_t timeout) { + AWS_ZERO_STRUCT(*record); + record->type = PAAT_SUBSCRIBE; + record->timeout = timeout; + + aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); + record->topic_filter_cursor = aws_byte_cursor_from_buf(&record->topic_filter); +} + +static void s_aws_protocol_adapter_api_record_init_from_unsubscribe( + struct aws_protocol_adapter_api_record *record, + struct aws_allocator *allocator, + struct aws_byte_cursor topic_filter, + uint32_t timeout) { + AWS_ZERO_STRUCT(*record); + record->type = PAAT_UNSUBSCRIBE; + record->timeout = timeout; + + aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); + record->topic_filter_cursor = aws_byte_cursor_from_buf(&record->topic_filter); +} + +static void s_aws_protocol_adapter_api_record_clean_up(struct aws_protocol_adapter_api_record *record) { + aws_byte_buf_clean_up(&record->topic_filter); +} + +struct aws_mqtt_protocol_adapter_mock_impl { + struct aws_allocator *allocator; + struct aws_mqtt_protocol_adapter base; + + struct aws_array_list api_records; + bool is_connected; +}; + +static void s_aws_mqtt_protocol_adapter_mock_destroy(void *impl) { + struct aws_mqtt_protocol_adapter_mock_impl *adapter = impl; + + size_t record_count = aws_array_list_length(&adapter->api_records); + for (size_t i = 0; i < record_count; ++i) { + struct aws_protocol_adapter_api_record *record = NULL; + aws_array_list_get_at_ptr(&adapter->api_records, (void **)&record, i); + + s_aws_protocol_adapter_api_record_clean_up(record); + } + + aws_array_list_clean_up(&adapter->api_records); +} + +int s_aws_mqtt_protocol_adapter_mock_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { + struct aws_mqtt_protocol_adapter_mock_impl *adapter = impl; + + struct aws_protocol_adapter_api_record record; + s_aws_protocol_adapter_api_record_init_from_subscribe( + &record, adapter->allocator, options->topic_filter, options->ack_timeout_seconds); + + aws_array_list_push_back(&adapter->api_records, &record); + + return AWS_OP_SUCCESS; +} + +int s_aws_mqtt_protocol_adapter_mock_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { + struct aws_mqtt_protocol_adapter_mock_impl *adapter = impl; + + struct aws_protocol_adapter_api_record record; + s_aws_protocol_adapter_api_record_init_from_unsubscribe( + &record, adapter->allocator, options->topic_filter, options->ack_timeout_seconds); + + aws_array_list_push_back(&adapter->api_records, &record); + + return AWS_OP_SUCCESS; +} + +static bool s_aws_mqtt_protocol_adapter_mqtt_is_connected(void *impl) { + struct aws_mqtt_protocol_adapter_mock_impl *adapter = impl; + + return adapter->is_connected; +} + +static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mock_vtable = { + .aws_mqtt_protocol_adapter_destroy_fn = s_aws_mqtt_protocol_adapter_mock_destroy, + .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_mock_subscribe, + .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_mock_unsubscribe, + .aws_mqtt_protocol_adapter_publish_fn = NULL, + .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_mqtt_is_connected, +}; + +static struct aws_protocol_adapter *s_aws_mqtt_mock_protocol_adapter_new( + struct aws_allocator *allocator, + bool is_connected) { + struct aws_mqtt_protocol_adapter_mock_impl *adapter = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_mock_impl)); + + adapter->allocator = allocator; + adapter->base.impl = adapter; + adapter->base.vtable = &s_protocol_adapter_mock_vtable; + adapter->is_connected = is_connected; + aws_array_list_init_dynamic(&adapter->api_records, allocator, 10, sizeof(struct aws_protocol_adapter_api_record)); + + return &adapter->base; +} + +static bool s_api_records_contains_range( + struct aws_mqtt_protocol_adapter *protocol_adapter, + size_t range_start, + size_t range_end, + struct aws_protocol_adapter_api_record *expected_records) { + struct aws_mqtt_protocol_adapter_mock_impl *adapter = protocol_adapter->impl; + + size_t record_count = aws_array_list_length(&adapter->api_records); + if (record_count < range_end) { + return false; + } + + for (size_t i = range_start; i < range_end; ++i) { + struct aws_protocol_adapter_api_record *expected_record = &expected_records[i - range_start]; + + struct aws_protocol_adapter_api_record *actual_record = NULL; + aws_array_list_get_at_ptr(&adapter->api_records, (void **)&actual_record, i); + + if (expected_record->type != actual_record->type) { + return false; + } + + if (expected_record->timeout != actual_record->timeout) { + return false; + } + + if (!aws_byte_cursor_eq(&expected_record->topic_filter_cursor, &actual_record->topic_filter_cursor)) { + return false; + } + } + + return true; +} + +static bool s_api_records_equals( + struct aws_mqtt_protocol_adapter *protocol_adapter, + size_t record_count, + struct aws_protocol_adapter_api_record *expected_records) { + struct aws_mqtt_protocol_adapter_mock_impl *adapter = protocol_adapter->impl; + + return aws_array_list_length(&adapter->api_records) == record_count && + s_api_records_contains_range(protocol_adapter, 0, record_count, expected_records); +} + +////////////////////////////////////////////////////////////// + +struct aws_subscription_status_record { + enum aws_rr_subscription_event_type type; + struct aws_byte_buf topic_filter; + struct aws_byte_cursor topic_filter_cursor; + uint64_t operation_id; +}; + +static void s_aws_subscription_status_record_init_from_event( + struct aws_subscription_status_record *record, + struct aws_allocator *allocator, + struct aws_rr_subscription_status_event *event) { + AWS_ZERO_STRUCT(*record); + record->type = event->type; + record->operation_id = event->operation_id; + + aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, event->topic_filter); + record->topic_filter_cursor = aws_byte_cursor_from_buf(&record->topic_filter); +} + +static void s_aws_subscription_status_record_clean_up(struct aws_subscription_status_record *record) { + aws_byte_buf_clean_up(&record->topic_filter); +} + +////////////////////////////////////////////////////////////// + +struct aws_subscription_manager_test_fixture { + struct aws_allocator *allocator; + + struct aws_protocol_adapter *mock_protocol_adapter; + struct aws_rr_subscription_manager subscription_manager; + + struct aws_array_list subscription_status_records; +}; + +static void s_aws_rr_subscription_status_event_test_callback_fn( + const struct aws_rr_subscription_status_event *event, + void *userdata) { + struct aws_subscription_manager_test_fixture *fixture = userdata; + + struct aws_subscription_status_record record; + s_aws_subscription_status_record_init_from_event(&record, fixture->allocator, event); + + aws_array_list_push_back(&fixture->subscription_status_records, &record); +} + +struct aws_subscription_manager_test_fixture_options { + uint32_t operation_timeout_seconds; + size_t max_subscriptions; + bool start_connected; +}; + +static int s_aws_subscription_manager_test_fixture_init( + struct aws_subscription_manager_test_fixture *fixture, + struct aws_allocator *allocator, + const struct aws_subscription_manager_test_fixture_options *options) { + AWS_ZERO_STRUCT(*fixture); + + fixture->allocator = allocator; + fixture->mock_protocol_adapter = s_aws_mqtt_mock_protocol_adapter_new(allocator, options->start_connected); + + aws_array_list_init_dynamic( + &fixture->subscription_status_records, allocator, 10, sizeof(struct aws_subscription_status_record)); + + struct aws_rr_subscription_manager_options subscription_manager_options = { + .max_subscriptions = options->max_subscriptions, + .operation_timeout_seconds = options->operation_timeout_seconds, + .subscription_status_callback = s_aws_rr_subscription_status_event_test_callback_fn, + .userdata = fixture}; + ASSERT_SUCCESS(aws_rr_subscription_manager_init( + &fixture->subscription_manager, allocator, fixture->mock_protocol_adapter, &subscription_manager_options)); + + return AWS_OP_SUCCESS; +} + +static void s_aws_subscription_manager_test_fixture_clean_up(struct aws_subscription_manager_test_fixture *fixture) { + + aws_rr_subscription_manager_clean_up(&fixture->subscription_manager); + aws_mqtt_protocol_adapter_destroy(fixture->mock_protocol_adapter); + + size_t record_count = aws_array_list_length(&fixture->subscription_status_records); + for (size_t i = 0; i < record_count; ++i) { + struct aws_subscription_status_record *record = NULL; + aws_array_list_get_at_ptr(&fixture->subscription_status_records, (void **)&record, i); + + s_aws_subscription_status_record_clean_up(record); + } + + aws_array_list_clean_up(&fixture->subscription_status_records); +} + +static bool s_find_subscription_event_record( + struct aws_subscription_manager_test_fixture *fixture, + struct aws_subscription_status_record *expected_record, + size_t *index) { + + size_t record_count = aws_array_list_length(&fixture->subscription_status_records); + for (size_t i = 0; i < record_count; ++i) { + + struct aws_subscription_status_record *actual_record = NULL; + aws_array_list_get_at_ptr(&fixture->subscription_status_records, (void **)&actual_record, i); + + if (expected_record->type != actual_record->type) { + continue; + } + + if (expected_record->operation_id != actual_record->operation_id) { + continue; + } + + if (!aws_byte_cursor_eq(&expected_record->topic_filter_cursor, &actual_record->topic_filter_cursor)) { + continue; + } + + *index = i; + return true; + } + + return false; +} + +static bool s_contains_subscription_event_record( + struct aws_subscription_manager_test_fixture *fixture, + struct aws_subscription_status_record *record) { + size_t index = 0; + return s_find_subscription_event_record(fixture, record, &index); +} + +static bool s_contains_subscription_event_records( + struct aws_subscription_manager_test_fixture *fixture, + size_t record_count, + struct aws_subscription_status_record *records) { + for (size_t i = 0; i < record_count; ++i) { + if (!s_contains_subscription_event_record(fixture, &records[i])) { + return false; + } + } + + return true; +} + +static bool s_contains_subscription_event_sequential_records( + struct aws_subscription_manager_test_fixture *fixture, + size_t record_count, + struct aws_subscription_status_record *records) { + size_t previous_index = 0; + for (size_t i = 0; i < record_count; ++i) { + size_t index = 0; + if (!s_find_subscription_event_record(fixture, &records[i], &index)) { + return false; + } + + if (i > 0) { + if (index <= previous_index) { + return false; + } + } + previous_index = index; + } + + return true; +} From 5000d028bfdfec17d9e4a8932c7f42e9740cb69b Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 26 Feb 2024 14:35:26 -0800 Subject: [PATCH 053/124] Checkpoint --- .../request-response/subscription_manager.c | 10 +- tests/CMakeLists.txt | 5 + .../subscription_manager_tests.c | 130 ++++++++++++++++-- 3 files changed, 131 insertions(+), 14 deletions(-) diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 31887f86..c6870157 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -152,6 +152,7 @@ static int s_rr_subscription_clean_up_foreach_wrap(void *context, struct aws_has struct aws_rr_subscription_record *subscription = elem->value; s_subscription_record_unsubscribe(manager, subscription, true); + s_aws_rr_subscription_record_destroy(subscription); return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; } @@ -233,6 +234,7 @@ static int s_rr_subscription_cull_unused_subscriptions_wrapper(void *context, st } if (record->status == ARRSST_NOT_SUBSCRIBED && record->pending_action == ARRSPAT_NOTHING) { + s_aws_rr_subscription_record_destroy(record); return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; } } @@ -321,7 +323,10 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su } AWS_FATAL_ASSERT(existing_record != NULL); - AWS_FATAL_ASSERT(existing_record->type == options->type); + if (existing_record->type != options->type) { + return AASRT_FAILURE; + } + s_aws_rr_subscription_record_log_invariant_violations(existing_record); // for simplicity, we require unsubscribes to complete before re-subscribing @@ -407,6 +412,7 @@ static int s_apply_session_lost_wrapper(void *context, struct aws_hash_element * s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_ENDED); if (record->pending_action != ARRSPAT_UNSUBSCRIBING) { + s_aws_rr_subscription_record_destroy(record); return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; } } @@ -464,6 +470,6 @@ int aws_rr_subscription_manager_init( } void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager) { - aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_clean_up_foreach_wrap, manager->protocol_adapter); + aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_clean_up_foreach_wrap, manager); aws_hash_table_clean_up(&manager->subscriptions); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6d1992c8..38f140ec 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -481,6 +481,11 @@ add_test_case(mqtt311_listener_connection_events_no_session) add_test_case(mqtt311_listener_connection_events_with_session) add_test_case(mqtt311_listener_publish_event) +# "rrsm" = "request_response_subscription_manager" +# these tests tend to have longer operation sequences and checks in their names +add_test_case(rrsm_acquire_subscribing_rr) +add_test_case(rrsm_acquire_subscribing_eventstream) + generate_test_driver(${PROJECT_NAME}-tests) set(TEST_PAHO_CLIENT_BINARY_NAME ${PROJECT_NAME}-paho-client) diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 782745ac..383d76b3 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -113,7 +113,7 @@ static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mock_vtable = .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_mqtt_is_connected, }; -static struct aws_protocol_adapter *s_aws_mqtt_mock_protocol_adapter_new( +static struct aws_mqtt_protocol_adapter *s_aws_mqtt_mock_protocol_adapter_new( struct aws_allocator *allocator, bool is_connected) { struct aws_mqtt_protocol_adapter_mock_impl *adapter = @@ -128,6 +128,40 @@ static struct aws_protocol_adapter *s_aws_mqtt_mock_protocol_adapter_new( return &adapter->base; } +static bool s_protocol_adapter_api_records_equal(struct aws_protocol_adapter_api_record *record1, struct aws_protocol_adapter_api_record *record2) { + if (record1->type != record2->type) { + return false; + } + + if (record1->timeout != record2->timeout) { + return false; + } + + if (!aws_byte_cursor_eq(&record1->topic_filter_cursor, &record2->topic_filter_cursor)) { + return false; + } + + return true; +} + +static bool s_api_records_contains_record(struct aws_mqtt_protocol_adapter *protocol_adapter, + struct aws_protocol_adapter_api_record *expected_record) { + + struct aws_mqtt_protocol_adapter_mock_impl *adapter = protocol_adapter->impl; + + size_t record_count = aws_array_list_length(&adapter->api_records); + for (size_t i = 0; i < record_count; ++i) { + struct aws_protocol_adapter_api_record *actual_record = NULL; + aws_array_list_get_at_ptr(&adapter->api_records, (void **)&actual_record, i); + + if (s_protocol_adapter_api_records_equal(expected_record, actual_record)) { + return true; + } + } + + return false; +} + static bool s_api_records_contains_range( struct aws_mqtt_protocol_adapter *protocol_adapter, size_t range_start, @@ -146,15 +180,7 @@ static bool s_api_records_contains_range( struct aws_protocol_adapter_api_record *actual_record = NULL; aws_array_list_get_at_ptr(&adapter->api_records, (void **)&actual_record, i); - if (expected_record->type != actual_record->type) { - return false; - } - - if (expected_record->timeout != actual_record->timeout) { - return false; - } - - if (!aws_byte_cursor_eq(&expected_record->topic_filter_cursor, &actual_record->topic_filter_cursor)) { + if (!s_protocol_adapter_api_records_equal(expected_record, actual_record)) { return false; } } @@ -184,7 +210,7 @@ struct aws_subscription_status_record { static void s_aws_subscription_status_record_init_from_event( struct aws_subscription_status_record *record, struct aws_allocator *allocator, - struct aws_rr_subscription_status_event *event) { + const struct aws_rr_subscription_status_event *event) { AWS_ZERO_STRUCT(*record); record->type = event->type; record->operation_id = event->operation_id; @@ -202,7 +228,7 @@ static void s_aws_subscription_status_record_clean_up(struct aws_subscription_st struct aws_subscription_manager_test_fixture { struct aws_allocator *allocator; - struct aws_protocol_adapter *mock_protocol_adapter; + struct aws_mqtt_protocol_adapter *mock_protocol_adapter; struct aws_rr_subscription_manager subscription_manager; struct aws_array_list subscription_status_records; @@ -225,12 +251,24 @@ struct aws_subscription_manager_test_fixture_options { bool start_connected; }; +static const uint32_t DEFAULT_SM_TEST_TIMEOUT = 5; + static int s_aws_subscription_manager_test_fixture_init( struct aws_subscription_manager_test_fixture *fixture, struct aws_allocator *allocator, const struct aws_subscription_manager_test_fixture_options *options) { AWS_ZERO_STRUCT(*fixture); + struct aws_subscription_manager_test_fixture_options default_options = { + .max_subscriptions = 3, + .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, + .start_connected = true, + }; + + if (options == NULL) { + options = &default_options; + } + fixture->allocator = allocator; fixture->mock_protocol_adapter = s_aws_mqtt_mock_protocol_adapter_new(allocator, options->start_connected); @@ -335,3 +373,71 @@ static bool s_contains_subscription_event_sequential_records( return true; } + +static int s_do_acquire_subscribing_test(struct aws_allocator *allocator, enum aws_rr_subscription_type subscription_type) { + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = subscription_type, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + } + }; + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 1, expected_subscribes)); + + /* + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = subscription_type, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes));*/ + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_rrsm_acquire_subscribing_rr_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_do_acquire_subscribing_test(allocator, ARRST_REQUEST_RESPONSE); +} + +AWS_TEST_CASE( + rrsm_acquire_subscribing_rr, + s_rrsm_acquire_subscribing_rr_fn) + +static int s_rrsm_acquire_subscribing_eventstream_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_do_acquire_subscribing_test(allocator, ARRST_REQUEST_RESPONSE); +} + +AWS_TEST_CASE( + rrsm_acquire_subscribing_eventstream, + s_rrsm_acquire_subscribing_eventstream_fn) \ No newline at end of file From baf78c34bd9c3f8a226ba877384f6412e814ec16 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 26 Feb 2024 16:42:41 -0800 Subject: [PATCH 054/124] Test mem leak --- tests/request-response/subscription_manager_tests.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 383d76b3..d34a65d9 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -73,6 +73,8 @@ static void s_aws_mqtt_protocol_adapter_mock_destroy(void *impl) { } aws_array_list_clean_up(&adapter->api_records); + + aws_mem_release(adapter->allocator, adapter); } int s_aws_mqtt_protocol_adapter_mock_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { From 3b16274ea14efc5adac723244ea59cf5bb48dc3d Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 27 Feb 2024 15:07:55 -0800 Subject: [PATCH 055/124] Sync point --- tests/CMakeLists.txt | 16 +- .../subscription_manager_tests.c | 661 +++++++++++++++++- 2 files changed, 649 insertions(+), 28 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 38f140ec..a23af420 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -483,8 +483,20 @@ add_test_case(mqtt311_listener_publish_event) # "rrsm" = "request_response_subscription_manager" # these tests tend to have longer operation sequences and checks in their names -add_test_case(rrsm_acquire_subscribing_rr) -add_test_case(rrsm_acquire_subscribing_eventstream) +add_test_case(rrsm_acquire_subscribing) +add_test_case(rrsm_acquire_existing_subscribing) +add_test_case(rrsm_acquire_existing_subscribed) +add_test_case(rrsm_acquire_blocked_rr) +add_test_case(rrsm_acquire_blocked_eventstream) +add_test_case(rrsm_acquire_no_capacity_max1) +add_test_case(rrsm_acquire_no_capacity_too_many_event_stream) +add_test_case(rrsm_acquire_failure_mixed_subscription_types) +add_test_case(rrsm_acquire_failure_subscribe_sync_failure) +add_test_case(rrsm_release_unsubscribes) +add_test_case(rrsm_release_unsubscribe_success_clears_space) +add_test_case(rrsm_release_unsubscribe_failure_blocked) + +add_test_case(rrsm_acquire_subscribe_failure_event) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index d34a65d9..8ca66d4b 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -117,20 +117,27 @@ static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mock_vtable = static struct aws_mqtt_protocol_adapter *s_aws_mqtt_mock_protocol_adapter_new( struct aws_allocator *allocator, + const struct aws_mqtt_protocol_adapter_vtable *vtable, bool is_connected) { struct aws_mqtt_protocol_adapter_mock_impl *adapter = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_mock_impl)); adapter->allocator = allocator; adapter->base.impl = adapter; - adapter->base.vtable = &s_protocol_adapter_mock_vtable; + if (vtable != NULL) { + adapter->base.vtable = vtable; + } else { + adapter->base.vtable = &s_protocol_adapter_mock_vtable; + } adapter->is_connected = is_connected; aws_array_list_init_dynamic(&adapter->api_records, allocator, 10, sizeof(struct aws_protocol_adapter_api_record)); return &adapter->base; } -static bool s_protocol_adapter_api_records_equal(struct aws_protocol_adapter_api_record *record1, struct aws_protocol_adapter_api_record *record2) { +static bool s_protocol_adapter_api_records_equal( + struct aws_protocol_adapter_api_record *record1, + struct aws_protocol_adapter_api_record *record2) { if (record1->type != record2->type) { return false; } @@ -146,8 +153,9 @@ static bool s_protocol_adapter_api_records_equal(struct aws_protocol_adapter_api return true; } -static bool s_api_records_contains_record(struct aws_mqtt_protocol_adapter *protocol_adapter, - struct aws_protocol_adapter_api_record *expected_record) { +static bool s_api_records_contains_record( + struct aws_mqtt_protocol_adapter *protocol_adapter, + struct aws_protocol_adapter_api_record *expected_record) { struct aws_mqtt_protocol_adapter_mock_impl *adapter = protocol_adapter->impl; @@ -251,6 +259,7 @@ struct aws_subscription_manager_test_fixture_options { uint32_t operation_timeout_seconds; size_t max_subscriptions; bool start_connected; + const struct aws_mqtt_protocol_adapter_vtable *adapter_vtable; }; static const uint32_t DEFAULT_SM_TEST_TIMEOUT = 5; @@ -272,7 +281,8 @@ static int s_aws_subscription_manager_test_fixture_init( } fixture->allocator = allocator; - fixture->mock_protocol_adapter = s_aws_mqtt_mock_protocol_adapter_new(allocator, options->start_connected); + fixture->mock_protocol_adapter = + s_aws_mqtt_mock_protocol_adapter_new(allocator, options->adapter_vtable, options->start_connected); aws_array_list_init_dynamic( &fixture->subscription_status_records, allocator, 10, sizeof(struct aws_subscription_status_record)); @@ -376,43 +386,487 @@ static bool s_contains_subscription_event_sequential_records( return true; } -static int s_do_acquire_subscribing_test(struct aws_allocator *allocator, enum aws_rr_subscription_type subscription_type) { +static int s_rrsm_acquire_subscribing_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture fixture; ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world3"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; struct aws_rr_acquire_subscription_options acquire1_options = { - .type = subscription_type, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 1, expected_subscribes)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + struct aws_rr_acquire_subscription_options acquire3_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world3"), + .operation_id = 3, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 3, expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_subscribing, s_rrsm_acquire_subscribing_fn) + +// duplicate acquires while subscribing should not generate additional subscribe requests +static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); struct aws_protocol_adapter_api_record expected_subscribes[] = { { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), .timeout = DEFAULT_SM_TEST_TIMEOUT, }, { .type = PAAT_SUBSCRIBE, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), .timeout = DEFAULT_SM_TEST_TIMEOUT, - } + }, }; + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 1, expected_subscribes)); - /* + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + struct aws_rr_acquire_subscription_options reacquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .operation_id = 3, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire1_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + struct aws_rr_acquire_subscription_options reacquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 4, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire2_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_existing_subscribing, s_rrsm_acquire_existing_subscribing_fn) + +// calling acquire while subscribed returns subscribed, also checks subscription event emission +static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 1, expected_subscribes)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + struct aws_protocol_adapter_subscription_event successful_subscription1_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription1_event); + + struct aws_subscription_status_record expected_subscription_events[] = { + { + .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), + .operation_id = 1, + }, + { + .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 2, + }}; + ASSERT_TRUE(s_contains_subscription_event_sequential_records(&fixture, 1, expected_subscription_events)); + + struct aws_protocol_adapter_subscription_event successful_subscription2_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription2_event); + + ASSERT_TRUE(s_contains_subscription_event_sequential_records(&fixture, 2, expected_subscription_events)); + + struct aws_rr_acquire_subscription_options reacquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .operation_id = 3, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBED, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire1_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + struct aws_rr_acquire_subscription_options reacquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 4, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBED, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire2_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_existing_subscribed, s_rrsm_acquire_existing_subscribed_fn) + +static int s_do_acquire_blocked_test( + struct aws_subscription_manager_test_fixture *fixture, + enum aws_rr_subscription_type subscription_type) { + struct aws_rr_subscription_manager *manager = &fixture->subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + // no room, but it could potentially free up in the future struct aws_rr_acquire_subscription_options acquire2_options = { .type = subscription_type, .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), .operation_id = 2, }; + ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + + return AWS_OP_SUCCESS; +} + +static int s_rrsm_acquire_blocked_rr_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_subscriptions = 1, + .operation_timeout_seconds = 30, + }; + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + ASSERT_SUCCESS(s_do_acquire_blocked_test(&fixture, ARRST_REQUEST_RESPONSE)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_blocked_rr, s_rrsm_acquire_blocked_rr_fn) + +static int s_rrsm_acquire_blocked_eventstream_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_subscriptions = 2, + .operation_timeout_seconds = 30, + }; + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world3"), + .operation_id = 3, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire1_options)); + + ASSERT_SUCCESS(s_do_acquire_blocked_test(&fixture, ARRST_EVENT_STREAM)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_blocked_eventstream, s_rrsm_acquire_blocked_eventstream_fn) + +static int s_do_acquire_no_capacity_test(struct aws_subscription_manager_test_fixture *fixture) { + struct aws_rr_subscription_manager *manager = &fixture->subscription_manager; + + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_NO_CAPACITY, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + + return AWS_OP_SUCCESS; +} + +static int s_rrsm_acquire_no_capacity_max1_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_subscriptions = 1, + .operation_timeout_seconds = 30, + }; + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + ASSERT_SUCCESS(s_do_acquire_no_capacity_test(&fixture)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_no_capacity_max1, s_rrsm_acquire_no_capacity_max1_fn) + +static int s_rrsm_acquire_no_capacity_too_many_event_stream_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_subscriptions = 5, + .operation_timeout_seconds = 30, + }; + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + // N - 1 event stream subscriptions + for (size_t i = 0; i < 4; ++i) { + char topic_filter_buffer[256]; + sprintf(topic_filter_buffer, "hello/world/%d", (int)(i + 1)); + + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str(topic_filter_buffer), + .operation_id = i + 2, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire_options)); + } + + // 1 more, should fail + ASSERT_SUCCESS(s_do_acquire_no_capacity_test(&fixture)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_no_capacity_too_many_event_stream, s_rrsm_acquire_no_capacity_too_many_event_stream_fn) + +static int s_rrsm_acquire_failure_mixed_subscription_types_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire1_options)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS( + AASRT_FAILURE, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire2_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_failure_mixed_subscription_types, s_rrsm_acquire_failure_mixed_subscription_types_fn) + +static int s_rrsm_release_unsubscribes_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 2, + }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); - ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes));*/ + + struct aws_protocol_adapter_subscription_event successful_subscription_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription_event); + + // verify two success callbacks + struct aws_subscription_status_record expected_subscription_events[] = { + { + .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }, + { + .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 2, + }}; + ASSERT_TRUE(s_contains_subscription_event_records(&fixture, 2, expected_subscription_events)); + + // verify no unsubscribes + struct aws_protocol_adapter_api_record expected_unsubscribe = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // release once, verify no unsubscribe + struct aws_rr_release_subscription_options release1_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + aws_rr_subscription_manager_release_subscription(manager, &release1_options); + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // release second + struct aws_rr_release_subscription_options release2_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 2, + }; + aws_rr_subscription_manager_release_subscription(manager, &release2_options); + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // unsubscribe is lazy, so we need to trigger it by acquiring something else + struct aws_rr_acquire_subscription_options acquire3_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 3, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); + + // now the unsubscribe should be present + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); s_aws_subscription_manager_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -420,26 +874,181 @@ static int s_do_acquire_subscribing_test(struct aws_allocator *allocator, enum a return AWS_OP_SUCCESS; } -static int s_rrsm_acquire_subscribing_rr_fn( - struct aws_allocator *allocator, - void *ctx) { +AWS_TEST_CASE(rrsm_release_unsubscribes, s_rrsm_release_unsubscribes_fn) + +static int s_rrsm_do_unsubscribe_test(struct aws_allocator *allocator, bool should_succeed) { + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_subscriptions = 1, + .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, + .start_connected = true, + }; + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + // budget of 1, so new acquires should be blocked + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + + // complete the subscribe + struct aws_protocol_adapter_subscription_event successful_subscription_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription_event); + + // still blocked + ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + + // release + struct aws_rr_release_subscription_options release1_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + aws_rr_subscription_manager_release_subscription(manager, &release1_options); + + // unsubscribe should be visible, but we're still blocked because it hasn't completed + ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + struct aws_protocol_adapter_api_record expected_unsubscribe = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // complete the unsubscribe + struct aws_protocol_adapter_subscription_event successful_unsubscribe_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .event_type = AWS_PASET_UNSUBSCRIBE, + .error_code = should_succeed ? AWS_ERROR_SUCCESS : AWS_ERROR_MQTT5_UNSUBSCRIBE_OPTIONS_VALIDATION, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_unsubscribe_event); + + // a successful unsubscribe should clear space, a failed one should not + ASSERT_INT_EQUALS( + (should_succeed ? AASRT_SUBSCRIBING : AASRT_BLOCKED), + aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_rrsm_release_unsubscribe_success_clears_space_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_acquire_subscribing_test(allocator, ARRST_REQUEST_RESPONSE); + return s_rrsm_do_unsubscribe_test(allocator, true); } -AWS_TEST_CASE( - rrsm_acquire_subscribing_rr, - s_rrsm_acquire_subscribing_rr_fn) +AWS_TEST_CASE(rrsm_release_unsubscribe_success_clears_space, s_rrsm_release_unsubscribe_success_clears_space_fn) -static int s_rrsm_acquire_subscribing_eventstream_fn( - struct aws_allocator *allocator, - void *ctx) { +static int s_rrsm_release_unsubscribe_failure_blocked_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_rrsm_do_unsubscribe_test(allocator, false); +} + +AWS_TEST_CASE(rrsm_release_unsubscribe_failure_blocked, s_rrsm_release_unsubscribe_failure_blocked_fn) + +static int s_aws_mqtt_protocol_adapter_mock_subscribe_fails( + void *impl, + struct aws_protocol_adapter_subscribe_options *options) { + (void)impl; + (void)options; + + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); +} + +static int s_rrsm_acquire_failure_subscribe_sync_failure_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_mqtt_protocol_adapter_vtable failing_vtable = s_protocol_adapter_mock_vtable; + failing_vtable.aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_mock_subscribe_fails; + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_subscriptions = 3, + .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, + .start_connected = true, + .adapter_vtable = &failing_vtable, + }; + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_FAILURE, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_failure_subscribe_sync_failure, s_rrsm_acquire_failure_subscribe_sync_failure_fn) + +static int s_rrsm_acquire_subscribe_failure_event_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_acquire_subscribing_test(allocator, ARRST_REQUEST_RESPONSE); + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + // complete the subscribe with a failure + struct aws_protocol_adapter_subscription_event failed_subscription_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &failed_subscription_event); + + // verify subscribe failure event emission + struct aws_subscription_status_record expected_subscription_event = { + .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + + ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; } -AWS_TEST_CASE( - rrsm_acquire_subscribing_eventstream, - s_rrsm_acquire_subscribing_eventstream_fn) \ No newline at end of file +AWS_TEST_CASE(rrsm_acquire_subscribe_failure_event, s_rrsm_acquire_subscribe_failure_event_fn) From b639e363321db45aec11a05230481b2e1dea10a6 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 27 Feb 2024 15:28:38 -0800 Subject: [PATCH 056/124] missing visibility --- .../private/request-response/subscription_manager.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 855dcf16..89eb4bff 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -143,7 +143,7 @@ AWS_EXTERN_C_BEGIN /* * Initializes a subscription manager. Every native request-response client owns a single subscription manager. */ -int aws_rr_subscription_manager_init( +AWS_MQTT_API int aws_rr_subscription_manager_init( struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, @@ -154,14 +154,14 @@ int aws_rr_subscription_manager_init( * After this API is called, no other subscription manager APIs will be called by the request-response client (during * the rest of the asynchronous shutdown process). */ -void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager); +AWS_MQTT_API void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager); /* * Signals to the subscription manager that the native request-response client is processing an operation that * needs a subscription to a particular topic. Return value indicates to the request-response client how it should * proceed with processing the operation. */ -enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription( +AWS_MQTT_API enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription( struct aws_rr_subscription_manager *manager, const struct aws_rr_acquire_subscription_options *options); @@ -169,7 +169,7 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su * Signals to the subscription manager that the native request-response client operation no longer * needs a subscription to a particular topic. */ -void aws_rr_subscription_manager_release_subscription( +AWS_MQTT_API void aws_rr_subscription_manager_release_subscription( struct aws_rr_subscription_manager *manager, const struct aws_rr_release_subscription_options *options); @@ -185,7 +185,7 @@ void aws_rr_subscription_manager_release_subscription( * protocol client Suback/Timeout/Error -> protocol adapter -> native request-response client -> * subscription manager (this API) */ -void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( +AWS_MQTT_API void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( struct aws_rr_subscription_manager *manager, const struct aws_protocol_adapter_subscription_event *event); @@ -197,7 +197,7 @@ void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( * protocol client connect/disconnect -> protocol adapter -> native request-response client -> * Subscription manager (this API) */ -void aws_rr_subscription_manager_on_protocol_adapter_connection_event( +AWS_MQTT_API void aws_rr_subscription_manager_on_protocol_adapter_connection_event( struct aws_rr_subscription_manager *manager, const struct aws_protocol_adapter_connection_event *event); From 893105d1bc5fb1586e92805750bf4b59fa1c154d Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 27 Feb 2024 15:31:07 -0800 Subject: [PATCH 057/124] snprintf --- tests/request-response/subscription_manager_tests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 8ca66d4b..a3ae85a9 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -731,7 +731,7 @@ static int s_rrsm_acquire_no_capacity_too_many_event_stream_fn(struct aws_alloca // N - 1 event stream subscriptions for (size_t i = 0; i < 4; ++i) { char topic_filter_buffer[256]; - sprintf(topic_filter_buffer, "hello/world/%d", (int)(i + 1)); + snprintf(topic_filter_buffer, AWS_ARRAY_SIZE(topic_filter_buffer), "hello/world/%d", (int)(i + 1)); struct aws_rr_acquire_subscription_options acquire_options = { .type = ARRST_EVENT_STREAM, From 4247145ca14e4487b7b4430df2ca8221b01c17f1 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 28 Feb 2024 12:31:31 -0800 Subject: [PATCH 058/124] Finish tests --- tests/CMakeLists.txt | 12 +- .../subscription_manager_tests.c | 439 ++++++++++++++++++ 2 files changed, 449 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a23af420..6c26cc77 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -483,6 +483,7 @@ add_test_case(mqtt311_listener_publish_event) # "rrsm" = "request_response_subscription_manager" # these tests tend to have longer operation sequences and checks in their names +# more details about each test can be found at the top of each test function add_test_case(rrsm_acquire_subscribing) add_test_case(rrsm_acquire_existing_subscribing) add_test_case(rrsm_acquire_existing_subscribed) @@ -492,11 +493,18 @@ add_test_case(rrsm_acquire_no_capacity_max1) add_test_case(rrsm_acquire_no_capacity_too_many_event_stream) add_test_case(rrsm_acquire_failure_mixed_subscription_types) add_test_case(rrsm_acquire_failure_subscribe_sync_failure) +add_test_case(rrsm_acquire_subscribe_failure_event) add_test_case(rrsm_release_unsubscribes) add_test_case(rrsm_release_unsubscribe_success_clears_space) add_test_case(rrsm_release_unsubscribe_failure_blocked) - -add_test_case(rrsm_acquire_subscribe_failure_event) +add_test_case(rrsm_offline_acquire_online_success) +add_test_case(rrsm_offline_acquire_online_failure) +add_test_case(rrsm_offline_acquire_release_online) +add_test_case(rrsm_acquire_success_offline_release_acquire2_no_unsubscribe) +add_test_case(rrsm_acquire_success_clean_up_unsubscribe_override) +add_test_case(rrsm_acquire_pending_clean_up_unsubscribe_override) +add_test_case(rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire) +add_test_case(rrsm_subscription_lost_while_unsubscribing) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index a3ae85a9..66bc72ca 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -1052,3 +1052,442 @@ static int s_rrsm_acquire_subscribe_failure_event_fn(struct aws_allocator *alloc } AWS_TEST_CASE(rrsm_acquire_subscribe_failure_event, s_rrsm_acquire_subscribe_failure_event_fn) + +static int s_do_offline_acquire_online_test(struct aws_allocator *allocator, bool success) { + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_subscriptions = 3, + .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, + .start_connected = false, + }; + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + + // nothing should happen while offline + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 0, NULL)); + + // go online, should trigger a subscribe + struct aws_protocol_adapter_connection_event online_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = false, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(&fixture.subscription_manager, &online_event); + + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 1, expected_subscribes)); + + // complete the subscribe, verify a subscription success/failure event + struct aws_protocol_adapter_subscription_event subscription_event = { + .event_type = AWS_PASET_SUBSCRIBE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .error_code = success ? AWS_ERROR_SUCCESS : AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); + + struct aws_subscription_status_record expected_subscription_event = { + .type = success ? ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS : ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_rrsm_offline_acquire_online_success_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_offline_acquire_online_test(allocator, true); +} + +AWS_TEST_CASE(rrsm_offline_acquire_online_success, s_rrsm_offline_acquire_online_success_fn) + +static int s_rrsm_offline_acquire_online_failure_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_offline_acquire_online_test(allocator, false); +} + +AWS_TEST_CASE(rrsm_offline_acquire_online_failure, s_rrsm_offline_acquire_online_failure_fn) + +static int s_rrsm_offline_acquire_release_online_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + // trigger online -> offline + struct aws_protocol_adapter_connection_event offline_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(manager, &offline_event); + + // acquire + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + + // nothing should happen while offline + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 0, NULL)); + + // release while offline, nothing should happen + struct aws_rr_release_subscription_options release_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + aws_rr_subscription_manager_release_subscription(manager, &release_options); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 0, NULL)); + + // go online, nothing should be sent to the protocol adapter + struct aws_protocol_adapter_connection_event online_event = { + .event_type = AWS_PACET_CONNECTED, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(manager, &online_event); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 0, NULL)); + + // trigger a different subscription, verify it's the only thing that has reached the protocol adapter + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 1, expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_offline_acquire_release_online, s_rrsm_offline_acquire_release_online_fn) + +static int s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + // acquire + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + + // successfully complete subscription + struct aws_protocol_adapter_subscription_event subscription_event = { + .event_type = AWS_PASET_SUBSCRIBE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); + + struct aws_subscription_status_record expected_subscription_event = { + .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); + + // trigger online -> offline + struct aws_protocol_adapter_connection_event offline_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(manager, &offline_event); + + // release + struct aws_rr_release_subscription_options release_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + aws_rr_subscription_manager_release_subscription(manager, &release_options); + + // acquire something different, normally that triggers an unsubscribe, but we're offline + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + + // verify no unsubscribe has been sent + struct aws_protocol_adapter_api_record expected_unsubscribe = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // online, verify unsubscribe + struct aws_protocol_adapter_connection_event online_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = true, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(manager, &online_event); + + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrsm_acquire_success_offline_release_acquire2_no_unsubscribe, + s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn) + +static int s_do_rrsm_acquire_clean_up_test(struct aws_allocator *allocator, bool complete_subscribe) { + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + // acquire + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + + // successfully complete subscription if desired + if (complete_subscribe) { + struct aws_protocol_adapter_subscription_event subscription_event = { + .event_type = AWS_PASET_SUBSCRIBE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); + + struct aws_subscription_status_record expected_subscription_event = { + .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); + } + + // clean up subscription manager + aws_rr_subscription_manager_clean_up(manager); + + // verify an unsubscribe was sent even though we are offline + struct aws_protocol_adapter_api_record expected_unsubscribe = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // prevent the fixture cleanup from double freeing the subscription manager that was already cleaned up by + // reinitializing the subscription manager + struct aws_rr_subscription_manager_options subscription_manager_options = { + .max_subscriptions = 3, + .operation_timeout_seconds = 5, + .subscription_status_callback = s_aws_rr_subscription_status_event_test_callback_fn, + .userdata = &fixture, + }; + ASSERT_SUCCESS(aws_rr_subscription_manager_init( + &fixture.subscription_manager, allocator, fixture.mock_protocol_adapter, &subscription_manager_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_rrsm_acquire_success_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_rrsm_acquire_clean_up_test(allocator, true); +} + +AWS_TEST_CASE( + rrsm_acquire_success_clean_up_unsubscribe_override, + s_rrsm_acquire_success_clean_up_unsubscribe_override_fn) + +static int s_rrsm_acquire_pending_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_rrsm_acquire_clean_up_test(allocator, false); +} + +AWS_TEST_CASE( + rrsm_acquire_pending_clean_up_unsubscribe_override, + s_rrsm_acquire_pending_clean_up_unsubscribe_override_fn) + +static int s_rrsm_do_no_session_subscription_lost_test( + struct aws_allocator *allocator, + bool offline_while_unsubscribing) { + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + // acquire + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + + // successfully complete subscription + struct aws_protocol_adapter_subscription_event subscription_event = { + .event_type = AWS_PASET_SUBSCRIBE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); + + struct aws_subscription_status_record expected_subscription_event = { + .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); + + // release if appropriate + if (offline_while_unsubscribing) { + struct aws_rr_release_subscription_options release_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + aws_rr_subscription_manager_release_subscription(manager, &release_options); + + struct aws_protocol_adapter_api_record expected_unsubscribe = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // trigger an event that would cull unused subscriptions, causing an unsubscribe + struct aws_rr_acquire_subscription_options acquire3_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world3"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); + + // now the unsubscribe should be present + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + } + + // online -> offline + struct aws_protocol_adapter_connection_event offline_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(manager, &offline_event); + + // offline -> online (no session) + struct aws_protocol_adapter_connection_event online_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = false, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(manager, &online_event); + + // verify subscription lost emitted + if (!offline_while_unsubscribing) { + struct aws_subscription_status_record expected_subscription_lost_event = { + .type = ARRSET_SUBSCRIPTION_ENDED, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_lost_event)); + } + + // if we were unsubscribing, verify reacquire is blocked and then complete the unsubscribe + struct aws_rr_acquire_subscription_options reacquire_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 3, + }; + + if (offline_while_unsubscribing) { + ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire_options)); + + struct aws_protocol_adapter_subscription_event unsubscribe_event = { + .event_type = AWS_PASET_UNSUBSCRIBE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &unsubscribe_event); + } + + // verify we can reacquire + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_rrsm_do_no_session_subscription_lost_test(allocator, false); +} + +AWS_TEST_CASE( + rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire, + s_rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire_fn) + +static int s_rrsm_subscription_lost_while_unsubscribing_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_rrsm_do_no_session_subscription_lost_test(allocator, true); +} + +AWS_TEST_CASE(rrsm_subscription_lost_while_unsubscribing, s_rrsm_subscription_lost_while_unsubscribing_fn) From c1a22985d2d9d416af08a7e97909b78f7f4934b7 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 28 Feb 2024 13:46:12 -0800 Subject: [PATCH 059/124] Logging, event emission fix --- .../request-response/protocol_adapter.h | 6 + source/request-response/protocol_adapter.c | 25 +++ .../request-response/subscription_manager.c | 191 ++++++++++++++++-- 3 files changed, 201 insertions(+), 21 deletions(-) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index fef91375..f9e35a51 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -196,6 +196,12 @@ AWS_MQTT_API int aws_mqtt_protocol_adapter_publish( */ AWS_MQTT_API bool aws_mqtt_protocol_adapter_is_connected(struct aws_mqtt_protocol_adapter *adapter); +AWS_MQTT_API const char *aws_protocol_adapter_subscription_event_type_to_c_str( + enum aws_protocol_adapter_subscription_event_type type); + +AWS_MQTT_API const char *aws_protocol_adapter_connection_event_type_to_c_str( + enum aws_protocol_adapter_connection_event_type type); + AWS_EXTERN_C_END #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_PROTOCOL_ADAPTER_H */ diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 1890f8df..8066e9eb 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -839,3 +839,28 @@ int aws_mqtt_protocol_adapter_publish( bool aws_mqtt_protocol_adapter_is_connected(struct aws_mqtt_protocol_adapter *adapter) { return (*adapter->vtable->aws_mqtt_protocol_adapter_is_connected_fn)(adapter->impl); } + +const char *aws_protocol_adapter_subscription_event_type_to_c_str( + enum aws_protocol_adapter_subscription_event_type type) { + switch (type) { + case AWS_PASET_SUBSCRIBE: + return "Subscribe"; + + case AWS_PASET_UNSUBSCRIBE: + return "Unsubscribe"; + } + + return "Unknown"; +} + +const char *aws_protocol_adapter_connection_event_type_to_c_str(enum aws_protocol_adapter_connection_event_type type) { + switch (type) { + case AWS_PACET_CONNECTED: + return "Connected"; + + case AWS_PACET_DISCONNECTED: + return "Disconnected"; + } + + return "Unknown"; +} diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index c6870157..05286786 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -9,6 +9,8 @@ #include #include +#include + enum aws_rr_subscription_status_type { ARRSST_SUBSCRIBED, ARRSST_NOT_SUBSCRIBED, @@ -68,7 +70,7 @@ static void s_aws_rr_subscription_record_log_invariant_violations(const struct a if (record->status == ARRSST_SUBSCRIBED && record->pending_action == ARRSPAT_SUBSCRIBING) { AWS_LOGF_ERROR( AWS_LS_MQTT_REQUEST_RESPONSE, - "MQTT request response subscription ('" PRInSTR "') invalid state", + "request-response subscription manager - subscription ('" PRInSTR "') invalid state", AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); } } @@ -128,6 +130,11 @@ static void s_subscription_record_unsubscribe( } if (!should_unsubscribe) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - subscription ('" PRInSTR + "') has no listeners but is not in a state that allows unsubscribe yet", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); return; } @@ -137,13 +144,22 @@ static void s_subscription_record_unsubscribe( }; if (aws_mqtt_protocol_adapter_unsubscribe(manager->protocol_adapter, &unsubscribe_options)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - sync unsubscribe failure for ('" PRInSTR "'), ec %d(%s)", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor), + error_code, + aws_error_debug_str(error_code)); return; } - record->pending_action = ARRSPAT_UNSUBSCRIBING; + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - unsubscribe submitted for ('" PRInSTR "')", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); - // check_invariants may no longer be true now because we might have converted a pending subscribe to a pending - // unsubscribe + record->pending_action = ARRSPAT_UNSUBSCRIBING; } /* Only called when shutting down the client */ @@ -197,6 +213,13 @@ static void s_get_subscription_stats( AWS_ZERO_STRUCT(*stats); aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_count_foreach_wrap, stats); + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager current stats: %d event stream sub records, %d request-response sub " + "records", + (int)stats->event_stream_subscriptions, + (int)stats->request_response_subscriptions); } static void s_remove_listener_from_subscription_record( @@ -213,6 +236,14 @@ static void s_remove_listener_from_subscription_record( }; aws_hash_table_remove(&record->listeners, &listener, NULL, NULL); + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - removed listener %" PRIu64 " from subscription ('" PRInSTR + "'), %zu listeners left", + operation_id, + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor), + aws_hash_table_get_entry_count(&record->listeners)); } static void s_add_listener_to_subscription_record(struct aws_rr_subscription_record *record, uint64_t operation_id) { @@ -222,6 +253,14 @@ static void s_add_listener_to_subscription_record(struct aws_rr_subscription_rec listener->operation_id = operation_id; aws_hash_table_put(&record->listeners, listener, listener, NULL); + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - added listener %" PRIu64 " to subscription ('" PRInSTR + "'), %zu listeners total", + operation_id, + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor), + aws_hash_table_get_entry_count(&record->listeners)); } static int s_rr_subscription_cull_unused_subscriptions_wrapper(void *context, struct aws_hash_element *elem) { @@ -229,11 +268,21 @@ static int s_rr_subscription_cull_unused_subscriptions_wrapper(void *context, st struct aws_rr_subscription_manager *manager = context; if (aws_hash_table_get_entry_count(&record->listeners) == 0) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - checking subscription ('" PRInSTR "') for removal", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); + if (manager->is_protocol_client_connected) { s_subscription_record_unsubscribe(manager, record, false); } if (record->status == ARRSST_NOT_SUBSCRIBED && record->pending_action == ARRSPAT_NOTHING) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - deleting subscription ('" PRInSTR "')", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); + s_aws_rr_subscription_record_destroy(record); return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; } @@ -243,29 +292,24 @@ static int s_rr_subscription_cull_unused_subscriptions_wrapper(void *context, st } static void s_cull_unused_subscriptions(struct aws_rr_subscription_manager *manager) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, "request-response subscription manager - culling unused subscriptions"); aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_cull_unused_subscriptions_wrapper, manager); } -static int s_rr_activate_idle_subscription( - struct aws_rr_subscription_manager *manager, - struct aws_rr_subscription_record *record) { - int result = AWS_OP_SUCCESS; +static const char *s_request_response_subscription_event_type_to_c_str(enum aws_rr_subscription_event_type type) { + switch (type) { + case ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS: + return "SubscriptionSubscribeSuccess"; - if (manager->is_protocol_client_connected && aws_hash_table_get_entry_count(&record->listeners) > 0) { - if (record->status == ARRSST_NOT_SUBSCRIBED && record->pending_action == ARRSPAT_NOTHING) { - struct aws_protocol_adapter_subscribe_options subscribe_options = { - .topic_filter = record->topic_filter_cursor, - .ack_timeout_seconds = manager->config.operation_timeout_seconds, - }; + case ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE: + return "SubscriptionSubscribeFailure"; - result = aws_mqtt_protocol_adapter_subscribe(manager->protocol_adapter, &subscribe_options); - if (result == AWS_OP_SUCCESS) { - record->pending_action = ARRSPAT_SUBSCRIBING; - } - } + case ARRSET_SUBSCRIPTION_ENDED: + return "SubscriptionEnded"; } - return result; + return "Unknown"; } static void s_emit_subscription_event( @@ -284,9 +328,54 @@ static void s_emit_subscription_event( }; (*manager->config.subscription_status_callback)(&event, manager->config.userdata); + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - subscription event for ('" PRInSTR + "'), type: %s, operation: %" PRIu64 "", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor), + s_request_response_subscription_event_type_to_c_str(type), + listener->operation_id); } } +static int s_rr_activate_idle_subscription( + struct aws_rr_subscription_manager *manager, + struct aws_rr_subscription_record *record) { + int result = AWS_OP_SUCCESS; + + if (manager->is_protocol_client_connected && aws_hash_table_get_entry_count(&record->listeners) > 0) { + if (record->status == ARRSST_NOT_SUBSCRIBED && record->pending_action == ARRSPAT_NOTHING) { + struct aws_protocol_adapter_subscribe_options subscribe_options = { + .topic_filter = record->topic_filter_cursor, + .ack_timeout_seconds = manager->config.operation_timeout_seconds, + }; + + result = aws_mqtt_protocol_adapter_subscribe(manager->protocol_adapter, &subscribe_options); + if (result == AWS_OP_SUCCESS) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - initiating subscribe operation for ('" PRInSTR "')", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); + record->pending_action = ARRSPAT_SUBSCRIBING; + } else { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - synchronous failure subscribing to ('" PRInSTR + "'), ec %d(%s)", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor), + error_code, + aws_error_debug_str(error_code)); + + s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE); + } + } + } + + return result; +} + enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription( struct aws_rr_subscription_manager *manager, const struct aws_rr_acquire_subscription_options *options) { @@ -311,8 +400,20 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su if (!space_for_subscription) { // could space eventually free up? if (options->type == ARRST_REQUEST_RESPONSE || stats.request_response_subscriptions > 1) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR + "'), operation %" PRIu64 " blocked - no room currently", + AWS_BYTE_CURSOR_PRI(options->topic_filter), + options->operation_id); return AASRT_BLOCKED; } else { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR + "'), operation %" PRIu64 " failed - no room", + AWS_BYTE_CURSOR_PRI(options->topic_filter), + options->operation_id); return AASRT_NO_CAPACITY; } } @@ -324,6 +425,12 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su AWS_FATAL_ASSERT(existing_record != NULL); if (existing_record->type != options->type) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 + " failed - conflicts with subscription type of existing subscription", + AWS_BYTE_CURSOR_PRI(options->topic_filter), + options->operation_id); return AASRT_FAILURE; } @@ -331,23 +438,48 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su // for simplicity, we require unsubscribes to complete before re-subscribing if (existing_record->pending_action == ARRSPAT_UNSUBSCRIBING) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 + " blocked - existing subscription is unsubscribing", + AWS_BYTE_CURSOR_PRI(options->topic_filter), + options->operation_id); return AASRT_BLOCKED; } // register the operation as a listener s_add_listener_to_subscription_record(existing_record, options->operation_id); if (existing_record->status == ARRSST_SUBSCRIBED) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 + " subscribed - existing subscription is active", + AWS_BYTE_CURSOR_PRI(options->topic_filter), + options->operation_id); return AASRT_SUBSCRIBED; } // do we need to send a subscribe? if (s_rr_activate_idle_subscription(manager, existing_record)) { - s_emit_subscription_event(manager, existing_record, ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE); + // error code was already logged at the point of failure + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 + " failed - synchronous subscribe failure", + AWS_BYTE_CURSOR_PRI(options->topic_filter), + options->operation_id); return AASRT_FAILURE; } s_aws_rr_subscription_record_log_invariant_violations(existing_record); + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 + " subscribing - waiting on existing subscription", + AWS_BYTE_CURSOR_PRI(options->topic_filter), + options->operation_id); + return AASRT_SUBSCRIBING; } @@ -365,6 +497,15 @@ void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( return; } + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - received a protocol adapter subscription event for ('" PRInSTR + "'), type %s, error_code %d(%s)", + AWS_BYTE_CURSOR_PRI(event->topic_filter), + aws_protocol_adapter_subscription_event_type_to_c_str(event->event_type), + event->error_code, + aws_error_debug_str(event->error_code)); + if (event->event_type == AWS_PASET_SUBSCRIBE) { AWS_FATAL_ASSERT(record->pending_action == ARRSPAT_SUBSCRIBING); @@ -427,6 +568,14 @@ static void s_apply_session_lost(struct aws_rr_subscription_manager *manager) { void aws_rr_subscription_manager_on_protocol_adapter_connection_event( struct aws_rr_subscription_manager *manager, const struct aws_protocol_adapter_connection_event *event) { + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - received a protocol adapter connection event, type %s, joined_session " + "%d", + aws_protocol_adapter_connection_event_type_to_c_str(event->event_type), + (int)(event->joined_session ? 1 : 0)); + if (event->event_type == AWS_PACET_CONNECTED) { manager->is_protocol_client_connected = true; if (!event->joined_session) { From 7074a0d80c984585fd6a78d784e423e96eb1a91a Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 29 Feb 2024 09:56:42 -0800 Subject: [PATCH 060/124] Test polish --- tests/CMakeLists.txt | 1 + .../subscription_manager_tests.c | 104 +++++++++++++++++- 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6c26cc77..185cfc5e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -503,6 +503,7 @@ add_test_case(rrsm_offline_acquire_release_online) add_test_case(rrsm_acquire_success_offline_release_acquire2_no_unsubscribe) add_test_case(rrsm_acquire_success_clean_up_unsubscribe_override) add_test_case(rrsm_acquire_pending_clean_up_unsubscribe_override) +add_test_case(rrsm_offline_acquire_pending_clean_up_unsubscribe_override) add_test_case(rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire) add_test_case(rrsm_subscription_lost_while_unsubscribing) diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 66bc72ca..670749fb 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -386,6 +386,9 @@ static bool s_contains_subscription_event_sequential_records( return true; } +/* + * Verify: Acquiring a new subscription triggers a protocol client subscribe and returns SUBSCRIBING + */ static int s_rrsm_acquire_subscribing_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -446,7 +449,10 @@ static int s_rrsm_acquire_subscribing_fn(struct aws_allocator *allocator, void * AWS_TEST_CASE(rrsm_acquire_subscribing, s_rrsm_acquire_subscribing_fn) -// duplicate acquires while subscribing should not generate additional subscribe requests +/* + * Verify: Acquiring an existing, incomplete subscription does not trigger a protocol client subscribe and returns + * SUBSCRIBING + */ static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -512,7 +518,10 @@ static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocato AWS_TEST_CASE(rrsm_acquire_existing_subscribing, s_rrsm_acquire_existing_subscribing_fn) -// calling acquire while subscribed returns subscribed, also checks subscription event emission +/* + * Verify: Acquiring an existing, completed subscription does not trigger a protocol client subscribe and returns + * SUBSCRIBED + */ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -628,6 +637,9 @@ static int s_do_acquire_blocked_test( return AWS_OP_SUCCESS; } +/* + * Verify: Acquiring a new request-response subscription when there is no room returns BLOCKED + */ static int s_rrsm_acquire_blocked_rr_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -650,6 +662,9 @@ static int s_rrsm_acquire_blocked_rr_fn(struct aws_allocator *allocator, void *c AWS_TEST_CASE(rrsm_acquire_blocked_rr, s_rrsm_acquire_blocked_rr_fn) +/* + * Verify: Acquiring a new eventstream subscription when there is no room, but room could free up later, returns BLOCKED + */ static int s_rrsm_acquire_blocked_eventstream_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -694,6 +709,9 @@ static int s_do_acquire_no_capacity_test(struct aws_subscription_manager_test_fi return AWS_OP_SUCCESS; } +/* + * Verify: Acquiring a new eventstream subscription when the budget is 1 returns NO_CAPACITY + */ static int s_rrsm_acquire_no_capacity_max1_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -716,6 +734,10 @@ static int s_rrsm_acquire_no_capacity_max1_fn(struct aws_allocator *allocator, v AWS_TEST_CASE(rrsm_acquire_no_capacity_max1, s_rrsm_acquire_no_capacity_max1_fn) +/* + * Verify: Acquiring a new eventstream subscription when there is no room, and no room could free up later, returns + * NO_CAPACITY + */ static int s_rrsm_acquire_no_capacity_too_many_event_stream_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -754,6 +776,9 @@ static int s_rrsm_acquire_no_capacity_too_many_event_stream_fn(struct aws_alloca AWS_TEST_CASE(rrsm_acquire_no_capacity_too_many_event_stream, s_rrsm_acquire_no_capacity_too_many_event_stream_fn) +/* + * Verify: Acquiring an existing subscription with an unequal subscription type returns FAILURE + */ static int s_rrsm_acquire_failure_mixed_subscription_types_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -788,6 +813,10 @@ static int s_rrsm_acquire_failure_mixed_subscription_types_fn(struct aws_allocat AWS_TEST_CASE(rrsm_acquire_failure_mixed_subscription_types, s_rrsm_acquire_failure_mixed_subscription_types_fn) +/* + * Verify: A subscription that resolves successfully invokes callbacks for every operation listener; releasing + * both references and calling a new acquire will trigger an unsubscribe of the first subscription + */ static int s_rrsm_release_unsubscribes_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -951,6 +980,9 @@ static int s_rrsm_do_unsubscribe_test(struct aws_allocator *allocator, bool shou return AWS_OP_SUCCESS; } +/* + * Verify: Releasing a subscription and successfully unsubscribing frees up space for new acquires + */ static int s_rrsm_release_unsubscribe_success_clears_space_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -959,6 +991,9 @@ static int s_rrsm_release_unsubscribe_success_clears_space_fn(struct aws_allocat AWS_TEST_CASE(rrsm_release_unsubscribe_success_clears_space, s_rrsm_release_unsubscribe_success_clears_space_fn) +/* + * Verify: Releasing a subscription but failing to unsubscribe does not free up space for new acquires + */ static int s_rrsm_release_unsubscribe_failure_blocked_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -976,6 +1011,9 @@ static int s_aws_mqtt_protocol_adapter_mock_subscribe_fails( return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } +/* + * Verify: Acquiring a new subscription but synchronously failing the protocol adapter subscribe returns FAILURE + */ static int s_rrsm_acquire_failure_subscribe_sync_failure_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -1011,6 +1049,9 @@ static int s_rrsm_acquire_failure_subscribe_sync_failure_fn(struct aws_allocator AWS_TEST_CASE(rrsm_acquire_failure_subscribe_sync_failure, s_rrsm_acquire_failure_subscribe_sync_failure_fn) +/* + * Verify: Completing a subscription-acquire with a failing reason code emits a subscription failed event + */ static int s_rrsm_acquire_subscribe_failure_event_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -1114,6 +1155,10 @@ static int s_do_offline_acquire_online_test(struct aws_allocator *allocator, boo return AWS_OP_SUCCESS; } +/* + * Verify: Acquiring a new subscription while offline returns SUBSCRIBING. Going online triggers a protocol adapter + * subscribe. Completing the subscription successfully emits a subscribe success event. + */ static int s_rrsm_offline_acquire_online_success_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -1122,6 +1167,10 @@ static int s_rrsm_offline_acquire_online_success_fn(struct aws_allocator *alloca AWS_TEST_CASE(rrsm_offline_acquire_online_success, s_rrsm_offline_acquire_online_success_fn) +/* + * Verify: Acquiring a new subscription while offline returns SUBSCRIBING. Going online triggers a protocol adapter + * subscribe. Completing the subscription with a failure emits a subscribe failure event. + */ static int s_rrsm_offline_acquire_online_failure_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -1130,6 +1179,10 @@ static int s_rrsm_offline_acquire_online_failure_fn(struct aws_allocator *alloca AWS_TEST_CASE(rrsm_offline_acquire_online_failure, s_rrsm_offline_acquire_online_failure_fn) +/* + * Verify: Acquiring and releasing a subscription while offline and then going online should remove the + * subscription without invoking any protocol adapter APIs. + */ static int s_rrsm_offline_acquire_release_online_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -1197,6 +1250,9 @@ static int s_rrsm_offline_acquire_release_online_fn(struct aws_allocator *alloca AWS_TEST_CASE(rrsm_offline_acquire_release_online, s_rrsm_offline_acquire_release_online_fn) +/* + * Verify: Releasing an active subscription while offline should not invoke an unsubscribe until back online + */ static int s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn( struct aws_allocator *allocator, void *ctx) { @@ -1280,7 +1336,10 @@ AWS_TEST_CASE( rrsm_acquire_success_offline_release_acquire2_no_unsubscribe, s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn) -static int s_do_rrsm_acquire_clean_up_test(struct aws_allocator *allocator, bool complete_subscribe) { +static int s_do_rrsm_acquire_clean_up_test( + struct aws_allocator *allocator, + bool complete_subscribe, + bool clean_up_while_connected) { aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture fixture; @@ -1313,6 +1372,14 @@ static int s_do_rrsm_acquire_clean_up_test(struct aws_allocator *allocator, bool ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); } + if (!clean_up_while_connected) { + // trigger online -> offline + struct aws_protocol_adapter_connection_event offline_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(manager, &offline_event); + } + // clean up subscription manager aws_rr_subscription_manager_clean_up(manager); @@ -1341,26 +1408,45 @@ static int s_do_rrsm_acquire_clean_up_test(struct aws_allocator *allocator, bool return AWS_OP_SUCCESS; } +/* + * Verify: Calling clean up while a subscription is active triggers an immediate unsubscribe + */ static int s_rrsm_acquire_success_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_rrsm_acquire_clean_up_test(allocator, true); + return s_do_rrsm_acquire_clean_up_test(allocator, true, true); } AWS_TEST_CASE( rrsm_acquire_success_clean_up_unsubscribe_override, s_rrsm_acquire_success_clean_up_unsubscribe_override_fn) +/* + * Verify: Calling clean up while a subscription is pending triggers an immediate unsubscribe + */ static int s_rrsm_acquire_pending_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_rrsm_acquire_clean_up_test(allocator, false); + return s_do_rrsm_acquire_clean_up_test(allocator, false, true); } AWS_TEST_CASE( rrsm_acquire_pending_clean_up_unsubscribe_override, s_rrsm_acquire_pending_clean_up_unsubscribe_override_fn) +/* + * Verify: Calling clean up while offline and a subscription is pending triggers an immediate unsubscribe + */ +static int s_rrsm_offline_acquire_pending_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_rrsm_acquire_clean_up_test(allocator, false, false); +} + +AWS_TEST_CASE( + rrsm_offline_acquire_pending_clean_up_unsubscribe_override, + s_rrsm_offline_acquire_pending_clean_up_unsubscribe_override_fn) + static int s_rrsm_do_no_session_subscription_lost_test( struct aws_allocator *allocator, bool offline_while_unsubscribing) { @@ -1472,6 +1558,10 @@ static int s_rrsm_do_no_session_subscription_lost_test( return AWS_OP_SUCCESS; } +/* + * Verify: If the client fails to rejoin a session, a SUBSCRIPTION_ENDED event is emitted for active subscriptions and + * that subscription can successfully be reacquired + */ static int s_rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire_fn( struct aws_allocator *allocator, void *ctx) { @@ -1484,6 +1574,10 @@ AWS_TEST_CASE( rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire, s_rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire_fn) +/* + * Verify: If the client fails to rejoin a session, a SUBSCRIPTION_ENDED event is emitted for unsubscribing + * subscriptions + */ static int s_rrsm_subscription_lost_while_unsubscribing_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; From 0da55e2cbdf43095247b51ffc398c7420888fe8a Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 29 Feb 2024 15:53:30 -0800 Subject: [PATCH 061/124] Checkpoint --- include/aws/mqtt/private/client_impl_shared.h | 5 + .../request-response/protocol_adapter.h | 9 +- .../request_response_client.h | 28 ++ .../request_response_client.h | 33 +++ source/client.c | 11 +- source/client_impl_shared.c | 4 + source/request-response/protocol_adapter.c | 5 +- .../request_response_client.c | 270 ++++++++++++++++++ source/v5/mqtt5_to_mqtt3_adapter.c | 9 +- ...apter_tests.c => protocol_adapter_tests.c} | 6 +- .../request_response_client_tests.c | 99 +++++++ .../subscription_manager_tests.c | 8 +- 12 files changed, 472 insertions(+), 15 deletions(-) create mode 100644 include/aws/mqtt/private/request-response/request_response_client.h create mode 100644 include/aws/mqtt/request-response/request_response_client.h create mode 100644 source/request-response/request_response_client.c rename tests/request-response/{request_response_protocol_adapter_tests.c => protocol_adapter_tests.c} (99%) create mode 100644 tests/request-response/request_response_client_tests.c diff --git a/include/aws/mqtt/private/client_impl_shared.h b/include/aws/mqtt/private/client_impl_shared.h index fa5cbeb3..248b4658 100644 --- a/include/aws/mqtt/private/client_impl_shared.h +++ b/include/aws/mqtt/private/client_impl_shared.h @@ -123,6 +123,8 @@ struct aws_mqtt_client_connection_vtable { int (*get_stats_fn)(void *impl, struct aws_mqtt_connection_operation_statistics *stats); enum aws_mqtt311_impl_type (*get_impl_type)(const void *impl); + + struct aws_event_loop *(*get_event_loop)(const void *impl); }; struct aws_mqtt_client_connection { @@ -139,4 +141,7 @@ 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); +AWS_MQTT_API struct aws_event_loop *aws_mqtt_client_connection_get_event_loop( + const struct aws_mqtt_client_connection *connection); + #endif /* AWS_MQTT_PRIVATE_CLIENT_IMPL_SHARED_H */ diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index f9e35a51..f5b784c2 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -103,13 +103,16 @@ struct aws_protocol_adapter_connection_event { }; typedef void( - aws_protocol_adapter_subscription_event_fn)(struct aws_protocol_adapter_subscription_event *event, void *user_data); + aws_protocol_adapter_subscription_event_fn)(const struct aws_protocol_adapter_subscription_event *event, void *user_data); + typedef void(aws_protocol_adapter_incoming_publish_fn)( - struct aws_protocol_adapter_incoming_publish_event *publish, + const struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data); + typedef void(aws_protocol_adapter_terminate_callback_fn)(void *user_data); + typedef void( - aws_protocol_adapter_connection_event_fn)(struct aws_protocol_adapter_connection_event *event, void *user_data); + aws_protocol_adapter_connection_event_fn)(const struct aws_protocol_adapter_connection_event *event, void *user_data); /* * Set of callbacks invoked by the protocol adapter. These must all be set. diff --git a/include/aws/mqtt/private/request-response/request_response_client.h b/include/aws/mqtt/private/request-response/request_response_client.h new file mode 100644 index 00000000..31f90c9f --- /dev/null +++ b/include/aws/mqtt/private/request-response/request_response_client.h @@ -0,0 +1,28 @@ +#ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H +#define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +struct aws_mqtt_protocol_adapter; +struct aws_mqtt_protocol_adapter_options; +struct aws_mqtt_request_response_client; +struct aws_mqtt_request_response_client_options; + +struct aws_protocol_adapter_factory_options { + struct aws_event_loop *loop; + void *creation_context; + struct aws_mqtt_protocol_adapter * (*mqtt_protocol_adaptor_factory_fn)(struct aws_mqtt_request_response_client *, struct aws_mqtt_protocol_adapter_options *, void *); +}; + +AWS_EXTERN_C_BEGIN + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_adaptor_factory(struct aws_allocator *allocator, const struct aws_protocol_adapter_factory_options *factory_options, const struct aws_mqtt_request_response_client_options *client_options); + +AWS_EXTERN_C_END + +#endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H */ diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h new file mode 100644 index 00000000..4e2e9fb4 --- /dev/null +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -0,0 +1,33 @@ +#ifndef AWS_MQTT_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H +#define AWS_MQTT_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include "aws/mqtt/mqtt.h" + +struct aws_mqtt_request_response_client; +struct aws_mqtt_client_connection; +struct aws_mqtt5_client; + +struct aws_mqtt_request_response_client_options { + size_t max_subscriptions; + uint32_t operation_timeout_seconds; +}; + +AWS_EXTERN_C_BEGIN + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client(struct aws_allocator *allocator, struct aws_mqtt_client_connection *client, const struct aws_mqtt_request_response_client_options *options); + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client(struct aws_allocator *allocator, struct aws_mqtt5_client *client, const struct aws_mqtt_request_response_client_options *options); + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire(struct aws_mqtt_request_response_client *client); + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release(struct aws_mqtt_request_response_client *client); + + +AWS_EXTERN_C_END + +#endif /* AWS_MQTT_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H */ diff --git a/source/client.c b/source/client.c index 5ba4ee5c..36c6d134 100644 --- a/source/client.c +++ b/source/client.c @@ -3322,12 +3322,18 @@ static void s_aws_mqtt_client_connection_311_release(void *impl) { aws_ref_count_release(&connection->ref_count); } -enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_3_get_impl(const void *impl) { +static enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_311_get_impl(const void *impl) { (void)impl; return AWS_MQTT311_IT_311_CONNECTION; } +static struct aws_event_loop *s_aws_mqtt_client_connection_311_get_event_loop(const void *impl) { + const struct aws_mqtt_client_connection_311_impl *connection = impl; + + return connection->loop; +} + 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, @@ -3351,7 +3357,8 @@ static struct aws_mqtt_client_connection_vtable s_aws_mqtt_client_connection_311 .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, - .get_impl_type = s_aws_mqtt_client_connection_3_get_impl, + .get_impl_type = s_aws_mqtt_client_connection_311_get_impl, + .get_event_loop = s_aws_mqtt_client_connection_311_get_event_loop, }; static struct aws_mqtt_client_connection_vtable *s_aws_mqtt_client_connection_311_vtable_ptr = diff --git a/source/client_impl_shared.c b/source/client_impl_shared.c index 525fb7e4..019adc5c 100644 --- a/source/client_impl_shared.c +++ b/source/client_impl_shared.c @@ -223,3 +223,7 @@ bool aws_mqtt_byte_cursor_hash_equality(const void *a, const void *b) { return aws_byte_cursor_eq(a_cursor, b_cursor); } + +struct aws_event_loop *aws_mqtt_client_connection_get_event_loop(const struct aws_mqtt_client_connection *connection) { + return (*connection->vtable->get_event_loop)(connection->impl); +} diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 8066e9eb..a59f09dc 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -428,8 +429,8 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( } if (aws_mqtt_client_connection_get_impl_type(connection) != AWS_MQTT311_IT_311_CONNECTION) { - aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); - return NULL; + struct aws_mqtt_client_connection_5_impl *adapter_impl = connection->impl; + return aws_mqtt_protocol_adapter_new_from_5(allocator, options, adapter_impl->client); } struct aws_mqtt_client_connection_311_impl *impl = connection->impl; diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c new file mode 100644 index 00000000..2d106f29 --- /dev/null +++ b/source/request-response/request_response_client.c @@ -0,0 +1,270 @@ +/** +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +enum aws_request_response_client_state { + AWS_RRCS_ACTIVE, + + // asynchronously shutting down, no more servicing will be done and all protocol adapter callbacks are ignored + AWS_RRCS_SHUTTING_DOWN, +}; + +struct aws_mqtt_request_response_client { + struct aws_allocator *allocator; + + struct aws_ref_count external_ref_count; + struct aws_ref_count internal_ref_count; + + struct aws_mqtt_request_response_client_options config; + + struct aws_mqtt_protocol_adapter *client_adapter; + + struct aws_rr_subscription_manager subscription_manager; + + struct aws_event_loop *loop; + + struct aws_task external_shutdown_task; + struct aws_task internal_shutdown_task; + + enum aws_request_response_client_state state; +}; + +static void s_aws_rr_client_on_zero_internal_ref_count(void *context) { + struct aws_mqtt_request_response_client *client = context; + + aws_event_loop_schedule_task_now(client->loop, &client->internal_shutdown_task); +} + +static void s_aws_rr_client_on_zero_external_ref_count(void *context) { + struct aws_mqtt_request_response_client *client = context; + + aws_event_loop_schedule_task_now(client->loop, &client->external_shutdown_task); +} + +static void s_mqtt_request_response_client_final_destroy(struct aws_mqtt_request_response_client *client) { + aws_mem_release(client->allocator, client); +} + +static void s_mqtt_request_response_client_internal_shutdown_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { + (void)task; + (void)task_status; + + struct aws_mqtt_request_response_client *client = arg; + + /* + * All internal and external refs are gone; it is safe to clean up synchronously. + * + * The subscription manager is cleaned up and the protocol adapter has been shut down. All that's left is to + * free memory. + */ + s_mqtt_request_response_client_final_destroy(client); +} + +static void s_mqtt_request_response_client_external_shutdown_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { + (void)task; + + AWS_FATAL_ASSERT(task_status != AWS_TASK_STATUS_CANCELED); + + struct aws_mqtt_request_response_client *client = arg; + + /* stop handling adapter event callbacks */ + client->state = AWS_RRCS_SHUTTING_DOWN; + + aws_rr_subscription_manager_clean_up(&client->subscription_manager); + + if (client->client_adapter != NULL) { + aws_mqtt_protocol_adapter_destroy(client->client_adapter); + } + + aws_ref_count_release(&client->internal_ref_count); +} + +static void s_aws_rr_client_subscription_status_event_callback(const struct aws_rr_subscription_status_event *event, void *userdata) { + (void)event; + (void)userdata; + + /* + * We must be on the event loop, but it's safer overall to process this event as a top-level event loop task. The subscription + * manager assumes that we won't call APIs on it while iterating subscription records and listeners. + * + * These tasks hold an internal reference while they exist. + */ + + // NYI +} + +static void s_aws_rr_client_protocol_adapter_subscription_event_callback(const struct aws_protocol_adapter_subscription_event *event, void *user_data) { + struct aws_mqtt_request_response_client *rr_client = user_data; + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(rr_client->loop)); + + if (rr_client->state != AWS_RRCS_ACTIVE) { + return; + } + + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(&rr_client->subscription_manager, event); +} + +static void s_aws_rr_client_protocol_adapter_incoming_publish_callback( + const struct aws_protocol_adapter_incoming_publish_event *publish, + void *user_data) { + (void)publish; + + struct aws_mqtt_request_response_client *rr_client = user_data; + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(rr_client->loop)); + + if (rr_client->state != AWS_RRCS_ACTIVE) { + return; + } + + // NYI +} + +static void s_aws_rr_client_protocol_adapter_terminate_callback(void *user_data) { + struct aws_mqtt_request_response_client *rr_client = user_data; + + // release the internal ref count "held" by the protocol adapter's existence + aws_ref_count_release(&rr_client->internal_ref_count); +} + +static void s_aws_rr_client_protocol_adapter_connection_event_callback(const struct aws_protocol_adapter_connection_event *event, void *user_data) { + struct aws_mqtt_request_response_client *rr_client = user_data; + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(rr_client->loop)); + + if (rr_client->state != AWS_RRCS_ACTIVE) { + return; + } + + aws_rr_subscription_manager_on_protocol_adapter_connection_event(&rr_client->subscription_manager, event); +} + +static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_new(struct aws_allocator *allocator, const struct aws_mqtt_request_response_client_options *options, struct aws_event_loop *loop) { + struct aws_mqtt_request_response_client *rr_client = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_request_response_client)); + + rr_client->allocator = allocator; + rr_client->config = *options; + rr_client->loop = loop; + rr_client->state = AWS_RRCS_ACTIVE; + + aws_task_init(&rr_client->external_shutdown_task, s_mqtt_request_response_client_external_shutdown_task_fn, rr_client, "mqtt_rr_client_external_shutdown"); + aws_task_init(&rr_client->internal_shutdown_task, s_mqtt_request_response_client_internal_shutdown_task_fn, rr_client, "mqtt_rr_client_internal_shutdown"); + + // 1 external ref to the caller + aws_ref_count_init(&rr_client->external_ref_count, rr_client, s_aws_rr_client_on_zero_external_ref_count); + + // 1 internal ref count belongs to ourselves (the external ref count shutdown task) + aws_ref_count_init(&rr_client->internal_ref_count, rr_client, s_aws_rr_client_on_zero_internal_ref_count); + + return rr_client; +} + +static int s_aws_rr_client_init_subscription_manager(struct aws_mqtt_request_response_client *rr_client, struct aws_allocator *allocator) { + struct aws_rr_subscription_manager_options subscription_manager_options = { + .operation_timeout_seconds = rr_client->config.operation_timeout_seconds, + .max_subscriptions = rr_client->config.max_subscriptions, + .subscription_status_callback = s_aws_rr_client_subscription_status_event_callback, + .userdata = rr_client, + }; + + return aws_rr_subscription_manager_init(&rr_client->subscription_manager, allocator, rr_client->client_adapter, &subscription_manager_options); +} + +static struct aws_mqtt_protocol_adapter *s_mqtt_protocol_adaptor_factory_from_mqtt311_client(struct aws_mqtt_request_response_client *rr_client, struct aws_mqtt_protocol_adapter_options *adapter_options, void *context) { + + struct aws_mqtt_client_connection *client = context; + + return aws_mqtt_protocol_adapter_new_from_311(rr_client->allocator, adapter_options, client); +} + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client(struct aws_allocator *allocator, struct aws_mqtt_client_connection *client, const struct aws_mqtt_request_response_client_options *options) { + + struct aws_protocol_adapter_factory_options mqtt311_factory_options = { + .loop = aws_mqtt_client_connection_get_event_loop(client), + .creation_context = client, + .mqtt_protocol_adaptor_factory_fn = s_mqtt_protocol_adaptor_factory_from_mqtt311_client, + }; + + return aws_mqtt_request_response_client_new_from_adaptor_factory(allocator, &mqtt311_factory_options, options); +} + +static struct aws_mqtt_protocol_adapter *s_mqtt_protocol_adaptor_factory_from_mqtt5_client(struct aws_mqtt_request_response_client *rr_client, struct aws_mqtt_protocol_adapter_options *adapter_options, void *context) { + + struct aws_mqtt5_client *client = context; + + return aws_mqtt_protocol_adapter_new_from_5(rr_client->allocator, adapter_options, client); +} + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client(struct aws_allocator *allocator, struct aws_mqtt5_client *client, const struct aws_mqtt_request_response_client_options *options) { + + struct aws_protocol_adapter_factory_options mqtt5_factory_options = { + .loop = client->loop, + .creation_context = client, + .mqtt_protocol_adaptor_factory_fn = s_mqtt_protocol_adaptor_factory_from_mqtt5_client, + }; + + return aws_mqtt_request_response_client_new_from_adaptor_factory(allocator, &mqtt5_factory_options, options); +} + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_adaptor_factory(struct aws_allocator *allocator, const struct aws_protocol_adapter_factory_options *factory_options, const struct aws_mqtt_request_response_client_options *client_options) { + struct aws_mqtt_request_response_client *rr_client = s_aws_mqtt_request_response_client_new(allocator, client_options, factory_options->loop); + + struct aws_mqtt_protocol_adapter_options adapter_options = { + .subscription_event_callback = s_aws_rr_client_protocol_adapter_subscription_event_callback, + .incoming_publish_callback = s_aws_rr_client_protocol_adapter_incoming_publish_callback, + .terminate_callback = s_aws_rr_client_protocol_adapter_terminate_callback, + .connection_event_callback = s_aws_rr_client_protocol_adapter_connection_event_callback, + .user_data = rr_client, + }; + + rr_client->client_adapter = (*factory_options->mqtt_protocol_adaptor_factory_fn)(rr_client, &adapter_options, factory_options->creation_context); + if (rr_client->client_adapter == NULL) { + goto error; + } + + // now that it exists, 1 internal ref belongs to protocol adapter termination + aws_ref_count_acquire(&rr_client->internal_ref_count); + + if (s_aws_rr_client_init_subscription_manager(rr_client, allocator)) { + goto error; + } + + return rr_client; + +error: + + // even on construction failures we still need to walk through the async shutdown process + aws_mqtt_request_response_client_release(rr_client); + + return NULL; +} + + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire(struct aws_mqtt_request_response_client *client) { + if (client != NULL) { + aws_ref_count_acquire(&client->external_ref_count); + } + + return client; +} + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release(struct aws_mqtt_request_response_client *client) { + if (client != NULL) { + aws_ref_count_release(&client->external_ref_count); + } + + return NULL; +} diff --git a/source/v5/mqtt5_to_mqtt3_adapter.c b/source/v5/mqtt5_to_mqtt3_adapter.c index 72128a97..6f800431 100644 --- a/source/v5/mqtt5_to_mqtt3_adapter.c +++ b/source/v5/mqtt5_to_mqtt3_adapter.c @@ -2854,12 +2854,18 @@ static uint16_t s_aws_mqtt_5_resubscribe_existing_topics( return 0; } -enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_5_get_impl(const void *impl) { +static enum aws_mqtt311_impl_type s_aws_mqtt_client_connection_5_get_impl(const void *impl) { (void)impl; return AWS_MQTT311_IT_5_ADAPTER; } +static struct aws_event_loop *s_aws_mqtt_client_connection_5_get_event_loop(const void *impl) { + const struct aws_mqtt_client_connection_5_impl *adapter = impl; + + return adapter->client->loop; +} + 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, @@ -2884,6 +2890,7 @@ static struct aws_mqtt_client_connection_vtable s_aws_mqtt_client_connection_5_v .publish_fn = s_aws_mqtt_client_connection_5_publish, .get_stats_fn = s_aws_mqtt_client_connection_5_get_stats, .get_impl_type = s_aws_mqtt_client_connection_5_get_impl, + .get_event_loop = s_aws_mqtt_client_connection_5_get_event_loop, }; static struct aws_mqtt_client_connection_vtable *s_aws_mqtt_client_connection_5_vtable_ptr = diff --git a/tests/request-response/request_response_protocol_adapter_tests.c b/tests/request-response/protocol_adapter_tests.c similarity index 99% rename from tests/request-response/request_response_protocol_adapter_tests.c rename to tests/request-response/protocol_adapter_tests.c index 2e45e065..ec2d6a1c 100644 --- a/tests/request-response/request_response_protocol_adapter_tests.c +++ b/tests/request-response/protocol_adapter_tests.c @@ -95,7 +95,7 @@ struct aws_request_response_protocol_adapter_test_fixture { }; static void s_rr_mqtt_protocol_adapter_test_on_subscription_event( - struct aws_protocol_adapter_subscription_event *event, + const struct aws_protocol_adapter_subscription_event *event, void *user_data) { struct aws_request_response_protocol_adapter_test_fixture *fixture = user_data; @@ -110,7 +110,7 @@ static void s_rr_mqtt_protocol_adapter_test_on_subscription_event( } static void s_rr_mqtt_protocol_adapter_test_on_incoming_publish( - struct aws_protocol_adapter_incoming_publish_event *publish, + const struct aws_protocol_adapter_incoming_publish_event *publish, void *user_data) { struct aws_request_response_protocol_adapter_test_fixture *fixture = user_data; @@ -135,7 +135,7 @@ static void s_rr_mqtt_protocol_adapter_test_on_terminate_callback(void *user_dat } static void s_rr_mqtt_protocol_adapter_test_on_connection_event( - struct aws_protocol_adapter_connection_event *event, + const struct aws_protocol_adapter_connection_event *event, void *user_data) { struct aws_request_response_protocol_adapter_test_fixture *fixture = user_data; diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c new file mode 100644 index 00000000..26580c1b --- /dev/null +++ b/tests/request-response/request_response_client_tests.c @@ -0,0 +1,99 @@ +/** +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0. +*/ + +#include +#include +#include +#include + +#include + +struct aws_mqtt_protocol_adapter_pinned_mock { + struct aws_allocator *allocator; + struct aws_mqtt_protocol_adapter base; + + struct aws_event_loop *loop; + void *test_context; + bool is_connected; +}; + +static void s_aws_mqtt_protocol_adapter_pinned_mock_destroy(void *impl) { + struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; + + aws_mem_release(adapter->allocator, adapter); +} + +static int s_aws_mqtt_protocol_adapter_pinned_mock_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { + (void)options; + + struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; + + return AWS_OP_SUCCESS; +} + +static int s_aws_mqtt_protocol_adapter_pinned_mock_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { + (void)options; + + struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; + + return AWS_OP_SUCCESS; +} + +static int s_aws_mqtt_protocol_adapter_pinned_mock_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { + (void)options; + + struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; + + return AWS_OP_SUCCESS; +} + +static bool s_aws_mqtt_protocol_adapter_pinned_mock_is_connected(void *impl) { + struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; + + return adapter->is_connected; +} + + +static struct aws_mqtt_protocol_adapter_vtable s_default_protocol_adapter_pinned_mock_vtable = { + .aws_mqtt_protocol_adapter_destroy_fn = s_aws_mqtt_protocol_adapter_pinned_mock_destroy, + .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_pinned_mock_subscribe, + .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_pinned_mock_unsubscribe, + .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_pinned_mock_publish, + .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_pinned_mock_is_connected, +}; + +static struct aws_mqtt_protocol_adapter_pinned_mock *s_aws_mqtt_protocol_adapter_new_pinned_mock( + struct aws_allocator *allocator, + const struct aws_mqtt_protocol_adapter_vtable *vtable, + struct aws_event_loop *loop, + void *test_context) { + struct aws_mqtt_protocol_adapter_pinned_mock *adapter = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_pinned_mock)); + + adapter->allocator = allocator; + adapter->test_context = test_context; + adapter->loop = loop; + adapter->base.impl = adapter; + if (vtable != NULL) { + adapter->base.vtable = vtable; + } else { + adapter->base.vtable = &s_default_protocol_adapter_pinned_mock_vtable; + } + + return adapter; +} + +struct aws_rr_client_test_fixture { + struct aws_allocator *allocator; + + struct aws_event_loop_group *elg; + struct aws_event_loop *loop; + + struct aws_request_response_client *client; + + struct aws_mqtt_protocol_adapter_pinned_mock *mock_adapter; + + void *test_context; +}; \ No newline at end of file diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 670749fb..ffba44f2 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -77,7 +77,7 @@ static void s_aws_mqtt_protocol_adapter_mock_destroy(void *impl) { aws_mem_release(adapter->allocator, adapter); } -int s_aws_mqtt_protocol_adapter_mock_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { +static int s_aws_mqtt_protocol_adapter_mock_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { struct aws_mqtt_protocol_adapter_mock_impl *adapter = impl; struct aws_protocol_adapter_api_record record; @@ -89,7 +89,7 @@ int s_aws_mqtt_protocol_adapter_mock_subscribe(void *impl, struct aws_protocol_a return AWS_OP_SUCCESS; } -int s_aws_mqtt_protocol_adapter_mock_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { +static int s_aws_mqtt_protocol_adapter_mock_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { struct aws_mqtt_protocol_adapter_mock_impl *adapter = impl; struct aws_protocol_adapter_api_record record; @@ -101,7 +101,7 @@ int s_aws_mqtt_protocol_adapter_mock_unsubscribe(void *impl, struct aws_protocol return AWS_OP_SUCCESS; } -static bool s_aws_mqtt_protocol_adapter_mqtt_is_connected(void *impl) { +static bool s_aws_mqtt_protocol_adapter_mock_is_connected(void *impl) { struct aws_mqtt_protocol_adapter_mock_impl *adapter = impl; return adapter->is_connected; @@ -112,7 +112,7 @@ static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mock_vtable = .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_mock_subscribe, .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_mock_unsubscribe, .aws_mqtt_protocol_adapter_publish_fn = NULL, - .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_mqtt_is_connected, + .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_mock_is_connected, }; static struct aws_mqtt_protocol_adapter *s_aws_mqtt_mock_protocol_adapter_new( From c7d7cbe3dec7472b29f2bb6487cf6303884870f8 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 1 Mar 2024 15:00:57 -0800 Subject: [PATCH 062/124] create destroy tests --- .../request-response/protocol_adapter.h | 10 +- .../request_response_client.h | 28 -- .../request-response/subscription_manager.h | 8 +- .../request_response_client.h | 26 +- .../request_response_client.c | 110 +++++--- .../request-response/subscription_manager.c | 16 +- tests/CMakeLists.txt | 4 + .../request_response_client_tests.c | 246 ++++++++++++++---- .../subscription_manager_tests.c | 16 +- 9 files changed, 323 insertions(+), 141 deletions(-) delete mode 100644 include/aws/mqtt/private/request-response/request_response_client.h diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index f5b784c2..a4896c68 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -102,8 +102,9 @@ struct aws_protocol_adapter_connection_event { bool joined_session; }; -typedef void( - aws_protocol_adapter_subscription_event_fn)(const struct aws_protocol_adapter_subscription_event *event, void *user_data); +typedef void(aws_protocol_adapter_subscription_event_fn)( + const struct aws_protocol_adapter_subscription_event *event, + void *user_data); typedef void(aws_protocol_adapter_incoming_publish_fn)( const struct aws_protocol_adapter_incoming_publish_event *publish, @@ -111,8 +112,9 @@ typedef void(aws_protocol_adapter_incoming_publish_fn)( typedef void(aws_protocol_adapter_terminate_callback_fn)(void *user_data); -typedef void( - aws_protocol_adapter_connection_event_fn)(const struct aws_protocol_adapter_connection_event *event, void *user_data); +typedef void(aws_protocol_adapter_connection_event_fn)( + const struct aws_protocol_adapter_connection_event *event, + void *user_data); /* * Set of callbacks invoked by the protocol adapter. These must all be set. diff --git a/include/aws/mqtt/private/request-response/request_response_client.h b/include/aws/mqtt/private/request-response/request_response_client.h deleted file mode 100644 index 31f90c9f..00000000 --- a/include/aws/mqtt/private/request-response/request_response_client.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H -#define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H - -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -#include - -struct aws_mqtt_protocol_adapter; -struct aws_mqtt_protocol_adapter_options; -struct aws_mqtt_request_response_client; -struct aws_mqtt_request_response_client_options; - -struct aws_protocol_adapter_factory_options { - struct aws_event_loop *loop; - void *creation_context; - struct aws_mqtt_protocol_adapter * (*mqtt_protocol_adaptor_factory_fn)(struct aws_mqtt_request_response_client *, struct aws_mqtt_protocol_adapter_options *, void *); -}; - -AWS_EXTERN_C_BEGIN - -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_adaptor_factory(struct aws_allocator *allocator, const struct aws_protocol_adapter_factory_options *factory_options, const struct aws_mqtt_request_response_client_options *client_options); - -AWS_EXTERN_C_END - -#endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H */ diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 89eb4bff..605a4994 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -143,7 +143,7 @@ AWS_EXTERN_C_BEGIN /* * Initializes a subscription manager. Every native request-response client owns a single subscription manager. */ -AWS_MQTT_API int aws_rr_subscription_manager_init( +AWS_MQTT_API void aws_rr_subscription_manager_init( struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, @@ -201,6 +201,12 @@ AWS_MQTT_API void aws_rr_subscription_manager_on_protocol_adapter_connection_eve struct aws_rr_subscription_manager *manager, const struct aws_protocol_adapter_connection_event *event); +/* + * Checks subscription manager options for validity. + */ +AWS_MQTT_API bool aws_rr_subscription_manager_are_options_valid( + const struct aws_rr_subscription_manager_options *options); + AWS_EXTERN_C_END #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_SUBSCRIPTION_MANAGER_H */ diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 4e2e9fb4..5e8fa221 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -12,21 +12,37 @@ struct aws_mqtt_request_response_client; struct aws_mqtt_client_connection; struct aws_mqtt5_client; +typedef void(aws_mqtt_request_response_client_initialized_callback_fn)(void *user_data); +typedef void(aws_mqtt_request_response_client_terminated_callback_fn)(void *user_data); + struct aws_mqtt_request_response_client_options { size_t max_subscriptions; uint32_t operation_timeout_seconds; + + // Do not bind the initialized callback; it exists mostly for tests and should not be exposed + aws_mqtt_request_response_client_initialized_callback_fn *initialized_callback; + + aws_mqtt_request_response_client_terminated_callback_fn *terminated_callback; + void *user_data; }; AWS_EXTERN_C_BEGIN -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client(struct aws_allocator *allocator, struct aws_mqtt_client_connection *client, const struct aws_mqtt_request_response_client_options *options); - -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client(struct aws_allocator *allocator, struct aws_mqtt5_client *client, const struct aws_mqtt_request_response_client_options *options); +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection *client, + const struct aws_mqtt_request_response_client_options *options); -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire(struct aws_mqtt_request_response_client *client); +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client( + struct aws_allocator *allocator, + struct aws_mqtt5_client *client, + const struct aws_mqtt_request_response_client_options *options); -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release(struct aws_mqtt_request_response_client *client); +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire( + struct aws_mqtt_request_response_client *client); +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release( + struct aws_mqtt_request_response_client *client); AWS_EXTERN_C_END diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 2d106f29..1d313a2b 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -10,11 +10,13 @@ #include #include #include -#include #include #include enum aws_request_response_client_state { + // cross-thread initialization has not completed and all protocol adapter callbacks are ignored + AWS_RRCS_UNINITIALIZED, + AWS_RRCS_ACTIVE, // asynchronously shutting down, no more servicing will be done and all protocol adapter callbacks are ignored @@ -35,6 +37,7 @@ struct aws_mqtt_request_response_client { struct aws_event_loop *loop; + struct aws_task initialize_task; struct aws_task external_shutdown_task; struct aws_task internal_shutdown_task; @@ -54,7 +57,14 @@ static void s_aws_rr_client_on_zero_external_ref_count(void *context) { } static void s_mqtt_request_response_client_final_destroy(struct aws_mqtt_request_response_client *client) { + aws_mqtt_request_response_client_terminated_callback_fn *terminate_callback = client->config.terminated_callback; + void *user_data = client->config.user_data; + aws_mem_release(client->allocator, client); + + if (terminate_callback != NULL) { + (*terminate_callback)(user_data); + } } static void s_mqtt_request_response_client_internal_shutdown_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { @@ -153,12 +163,23 @@ static void s_aws_rr_client_protocol_adapter_connection_event_callback(const str } static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_new(struct aws_allocator *allocator, const struct aws_mqtt_request_response_client_options *options, struct aws_event_loop *loop) { + struct aws_rr_subscription_manager_options sm_options = { + .max_subscriptions = options->max_subscriptions, + .operation_timeout_seconds = options->operation_timeout_seconds, + }; + + // we can't initialize the subscription manager until we're running on the event loop, so make sure that + // initialize can't fail by checking its options for validity now. + if (!aws_rr_subscription_manager_are_options_valid(&sm_options)) { + return NULL; + } + struct aws_mqtt_request_response_client *rr_client = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_request_response_client)); rr_client->allocator = allocator; rr_client->config = *options; rr_client->loop = loop; - rr_client->state = AWS_RRCS_ACTIVE; + rr_client->state = AWS_RRCS_UNINITIALIZED; aws_task_init(&rr_client->external_shutdown_task, s_mqtt_request_response_client_external_shutdown_task_fn, rr_client, "mqtt_rr_client_external_shutdown"); aws_task_init(&rr_client->internal_shutdown_task, s_mqtt_request_response_client_internal_shutdown_task_fn, rr_client, "mqtt_rr_client_internal_shutdown"); @@ -172,7 +193,7 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie return rr_client; } -static int s_aws_rr_client_init_subscription_manager(struct aws_mqtt_request_response_client *rr_client, struct aws_allocator *allocator) { +static void s_aws_rr_client_init_subscription_manager(struct aws_mqtt_request_response_client *rr_client, struct aws_allocator *allocator) { struct aws_rr_subscription_manager_options subscription_manager_options = { .operation_timeout_seconds = rr_client->config.operation_timeout_seconds, .max_subscriptions = rr_client->config.max_subscriptions, @@ -180,47 +201,72 @@ static int s_aws_rr_client_init_subscription_manager(struct aws_mqtt_request_res .userdata = rr_client, }; - return aws_rr_subscription_manager_init(&rr_client->subscription_manager, allocator, rr_client->client_adapter, &subscription_manager_options); + aws_rr_subscription_manager_init(&rr_client->subscription_manager, allocator, rr_client->client_adapter, &subscription_manager_options); } -static struct aws_mqtt_protocol_adapter *s_mqtt_protocol_adaptor_factory_from_mqtt311_client(struct aws_mqtt_request_response_client *rr_client, struct aws_mqtt_protocol_adapter_options *adapter_options, void *context) { +static void s_mqtt_request_response_client_initialize_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { + (void)task; - struct aws_mqtt_client_connection *client = context; + AWS_FATAL_ASSERT(task_status != AWS_TASK_STATUS_CANCELED); - return aws_mqtt_protocol_adapter_new_from_311(rr_client->allocator, adapter_options, client); -} + struct aws_mqtt_request_response_client *client = arg; -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client(struct aws_allocator *allocator, struct aws_mqtt_client_connection *client, const struct aws_mqtt_request_response_client_options *options) { + if (client->state == AWS_RRCS_UNINITIALIZED) { + s_aws_rr_client_init_subscription_manager(client, client->allocator); - struct aws_protocol_adapter_factory_options mqtt311_factory_options = { - .loop = aws_mqtt_client_connection_get_event_loop(client), - .creation_context = client, - .mqtt_protocol_adaptor_factory_fn = s_mqtt_protocol_adaptor_factory_from_mqtt311_client, - }; + client->state = AWS_RRCS_ACTIVE; + } - return aws_mqtt_request_response_client_new_from_adaptor_factory(allocator, &mqtt311_factory_options, options); -} + if (client->config.initialized_callback != NULL) { + (*client->config.initialized_callback)(client->config.user_data); + } -static struct aws_mqtt_protocol_adapter *s_mqtt_protocol_adaptor_factory_from_mqtt5_client(struct aws_mqtt_request_response_client *rr_client, struct aws_mqtt_protocol_adapter_options *adapter_options, void *context) { + // give up the internal ref we held while the task was pending + aws_ref_count_release(&client->internal_ref_count); +} - struct aws_mqtt5_client *client = context; +static void s_setup_cross_thread_initialization(struct aws_mqtt_request_response_client * rr_client) { + // now that it exists, 1 internal ref belongs to protocol adapter termination + aws_ref_count_acquire(&rr_client->internal_ref_count); - return aws_mqtt_protocol_adapter_new_from_5(rr_client->allocator, adapter_options, client); + // 1 internal ref belongs to the initialize task until it runs + aws_ref_count_acquire(&rr_client->internal_ref_count); + aws_task_init(&rr_client->initialize_task, s_mqtt_request_response_client_initialize_task_fn, rr_client, "mqtt_rr_client_initialize"); + aws_event_loop_schedule_task_now(rr_client->loop, &rr_client->initialize_task); } -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client(struct aws_allocator *allocator, struct aws_mqtt5_client *client, const struct aws_mqtt_request_response_client_options *options) { +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client(struct aws_allocator *allocator, struct aws_mqtt_client_connection *client, const struct aws_mqtt_request_response_client_options *options) { + + struct aws_mqtt_request_response_client *rr_client = s_aws_mqtt_request_response_client_new(allocator, options, aws_mqtt_client_connection_get_event_loop(client)); - struct aws_protocol_adapter_factory_options mqtt5_factory_options = { - .loop = client->loop, - .creation_context = client, - .mqtt_protocol_adaptor_factory_fn = s_mqtt_protocol_adaptor_factory_from_mqtt5_client, + struct aws_mqtt_protocol_adapter_options adapter_options = { + .subscription_event_callback = s_aws_rr_client_protocol_adapter_subscription_event_callback, + .incoming_publish_callback = s_aws_rr_client_protocol_adapter_incoming_publish_callback, + .terminate_callback = s_aws_rr_client_protocol_adapter_terminate_callback, + .connection_event_callback = s_aws_rr_client_protocol_adapter_connection_event_callback, + .user_data = rr_client, }; - return aws_mqtt_request_response_client_new_from_adaptor_factory(allocator, &mqtt5_factory_options, options); + rr_client->client_adapter = aws_mqtt_protocol_adapter_new_from_311(rr_client->allocator, &adapter_options, client); + if (rr_client->client_adapter == NULL) { + goto error; + } + + s_setup_cross_thread_initialization(rr_client); + + return rr_client; + +error: + + // even on construction failures we still need to walk through the async shutdown process + aws_mqtt_request_response_client_release(rr_client); + + return NULL; } -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_adaptor_factory(struct aws_allocator *allocator, const struct aws_protocol_adapter_factory_options *factory_options, const struct aws_mqtt_request_response_client_options *client_options) { - struct aws_mqtt_request_response_client *rr_client = s_aws_mqtt_request_response_client_new(allocator, client_options, factory_options->loop); +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client(struct aws_allocator *allocator, struct aws_mqtt5_client *client, const struct aws_mqtt_request_response_client_options *options) { + + struct aws_mqtt_request_response_client * rr_client = s_aws_mqtt_request_response_client_new(allocator, options, client->loop); struct aws_mqtt_protocol_adapter_options adapter_options = { .subscription_event_callback = s_aws_rr_client_protocol_adapter_subscription_event_callback, @@ -230,17 +276,12 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_fr .user_data = rr_client, }; - rr_client->client_adapter = (*factory_options->mqtt_protocol_adaptor_factory_fn)(rr_client, &adapter_options, factory_options->creation_context); + rr_client->client_adapter = aws_mqtt_protocol_adapter_new_from_5(rr_client->allocator, &adapter_options, client); if (rr_client->client_adapter == NULL) { goto error; } - // now that it exists, 1 internal ref belongs to protocol adapter termination - aws_ref_count_acquire(&rr_client->internal_ref_count); - - if (s_aws_rr_client_init_subscription_manager(rr_client, allocator)) { - goto error; - } + s_setup_cross_thread_initialization(rr_client); return rr_client; @@ -252,7 +293,6 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_fr return NULL; } - struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire(struct aws_mqtt_request_response_client *client) { if (client != NULL) { aws_ref_count_acquire(&client->external_ref_count); diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 05286786..57694ddc 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -589,16 +589,22 @@ void aws_rr_subscription_manager_on_protocol_adapter_connection_event( } } -int aws_rr_subscription_manager_init( +bool aws_rr_subscription_manager_are_options_valid(const struct aws_rr_subscription_manager_options *options) { + if (options == NULL || options->max_subscriptions < 1 || options->operation_timeout_seconds == 0) { + return false; + } + + return true; +} + +void aws_rr_subscription_manager_init( struct aws_rr_subscription_manager *manager, struct aws_allocator *allocator, struct aws_mqtt_protocol_adapter *protocol_adapter, const struct aws_rr_subscription_manager_options *options) { AWS_ZERO_STRUCT(*manager); - if (options == NULL || options->max_subscriptions < 1 || options->operation_timeout_seconds == 0) { - return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); - } + AWS_FATAL_ASSERT(aws_rr_subscription_manager_are_options_valid(options)); manager->allocator = allocator; manager->config = *options; @@ -614,8 +620,6 @@ int aws_rr_subscription_manager_init( s_aws_rr_subscription_record_destroy); manager->is_protocol_client_connected = aws_mqtt_protocol_adapter_is_connected(protocol_adapter); - - return AWS_OP_SUCCESS; } void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 185cfc5e..4b9141b8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -507,6 +507,10 @@ add_test_case(rrsm_offline_acquire_pending_clean_up_unsubscribe_override) add_test_case(rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire) add_test_case(rrsm_subscription_lost_while_unsubscribing) +# "rrc" = request response client +add_test_case(rrc_mqtt5_create_destroy) +add_test_case(rrc_mqtt311_create_destroy) + generate_test_driver(${PROJECT_NAME}-tests) set(TEST_PAHO_CLIENT_BINARY_NAME ${PROJECT_NAME}-paho-client) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 26580c1b..633ee42b 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -1,99 +1,233 @@ /** -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0. -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ #include -#include -#include #include #include -struct aws_mqtt_protocol_adapter_pinned_mock { +#include "../v3/mqtt311_testing_utils.h" +#include "../v5/mqtt5_testing_utils.h" + +enum rr_test_client_protocol { + RRCP_MQTT311, + RRCP_MQTT5, +}; + +struct aws_rr_client_test_fixture { struct aws_allocator *allocator; - struct aws_mqtt_protocol_adapter base; - struct aws_event_loop *loop; + struct aws_mqtt_request_response_client *client; + + enum rr_test_client_protocol test_protocol; + union { + struct aws_mqtt5_client_mock_test_fixture mqtt5_test_fixture; + struct mqtt_connection_state_test mqtt311_test_fixture; + } client_test_fixture; + void *test_context; - bool is_connected; + + struct aws_mutex lock; + struct aws_condition_variable signal; + + bool client_initialized; + bool client_destroyed; }; -static void s_aws_mqtt_protocol_adapter_pinned_mock_destroy(void *impl) { - struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; +static void s_aws_rr_client_test_fixture_on_initialized(void *user_data) { + struct aws_rr_client_test_fixture *fixture = user_data; - aws_mem_release(adapter->allocator, adapter); + aws_mutex_lock(&fixture->lock); + fixture->client_initialized = true; + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); } -static int s_aws_mqtt_protocol_adapter_pinned_mock_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { - (void)options; +static bool s_rr_client_test_fixture_initialized(void *context) { + struct aws_rr_client_test_fixture *fixture = context; - struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; + return fixture->client_initialized; +} - return AWS_OP_SUCCESS; +static void s_aws_rr_client_test_fixture_wait_for_initialized(struct aws_rr_client_test_fixture *fixture) { + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_rr_client_test_fixture_initialized, fixture); + aws_mutex_unlock(&fixture->lock); } -static int s_aws_mqtt_protocol_adapter_pinned_mock_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { - (void)options; +static void s_aws_rr_client_test_fixture_on_terminated(void *user_data) { + struct aws_rr_client_test_fixture *fixture = user_data; + + aws_mutex_lock(&fixture->lock); + fixture->client_destroyed = true; + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static int s_aws_rr_client_test_fixture_init_from_mqtt5( + struct aws_rr_client_test_fixture *fixture, + struct aws_allocator *allocator, + struct aws_mqtt_request_response_client_options *rr_client_options, + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options *client_test_fixture_options, + void *test_context) { + AWS_ZERO_STRUCT(*fixture); + fixture->allocator = allocator; + fixture->test_protocol = RRCP_MQTT5; - struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; + aws_mutex_init(&fixture->lock); + aws_condition_variable_init(&fixture->signal); + fixture->test_context = test_context; + + if (aws_mqtt5_client_mock_test_fixture_init( + &fixture->client_test_fixture.mqtt5_test_fixture, allocator, client_test_fixture_options)) { + return AWS_OP_ERR; + } + + struct aws_mqtt_request_response_client_options client_options = { + .max_subscriptions = 3, + .operation_timeout_seconds = 5, + }; + + if (rr_client_options != NULL) { + client_options = *rr_client_options; + } + + client_options.initialized_callback = s_aws_rr_client_test_fixture_on_initialized; + client_options.terminated_callback = s_aws_rr_client_test_fixture_on_terminated; + client_options.user_data = fixture; + + fixture->client = aws_mqtt_request_response_client_new_from_mqtt5_client( + allocator, fixture->client_test_fixture.mqtt5_test_fixture.client, &client_options); + AWS_FATAL_ASSERT(fixture->client != NULL); + + aws_mqtt5_client_start(fixture->client_test_fixture.mqtt5_test_fixture.client); + + aws_wait_for_connected_lifecycle_event(&fixture->client_test_fixture.mqtt5_test_fixture); + s_aws_rr_client_test_fixture_wait_for_initialized(fixture); return AWS_OP_SUCCESS; } -static int s_aws_mqtt_protocol_adapter_pinned_mock_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { - (void)options; +static int s_aws_rr_client_test_fixture_init_from_mqtt311( + struct aws_rr_client_test_fixture *fixture, + struct aws_allocator *allocator, + struct aws_mqtt_request_response_client_options *rr_client_options, + void *test_context) { + AWS_ZERO_STRUCT(*fixture); + fixture->allocator = allocator; + fixture->test_protocol = RRCP_MQTT311; - struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; + aws_mutex_init(&fixture->lock); + aws_condition_variable_init(&fixture->signal); + fixture->test_context = test_context; + + aws_test311_setup_mqtt_server_fn(allocator, &fixture->client_test_fixture.mqtt311_test_fixture); + + struct aws_mqtt_request_response_client_options client_options = { + .max_subscriptions = 3, + .operation_timeout_seconds = 5, + }; + + if (rr_client_options != NULL) { + client_options = *rr_client_options; + } + + client_options.initialized_callback = s_aws_rr_client_test_fixture_on_initialized; + client_options.terminated_callback = s_aws_rr_client_test_fixture_on_terminated; + client_options.user_data = fixture; + + struct aws_mqtt_client_connection *mqtt_client = fixture->client_test_fixture.mqtt311_test_fixture.mqtt_connection; + + fixture->client = aws_mqtt_request_response_client_new_from_mqtt311_client(allocator, mqtt_client, &client_options); + AWS_FATAL_ASSERT(fixture->client != NULL); + + struct aws_mqtt_connection_options connection_options = { + .user_data = &fixture->client_test_fixture.mqtt311_test_fixture, + .clean_session = false, + .client_id = aws_byte_cursor_from_c_str("client1234"), + .host_name = aws_byte_cursor_from_c_str(fixture->client_test_fixture.mqtt311_test_fixture.endpoint.address), + .socket_options = &fixture->client_test_fixture.mqtt311_test_fixture.socket_options, + .on_connection_complete = aws_test311_on_connection_complete_fn, + .ping_timeout_ms = DEFAULT_TEST_PING_TIMEOUT_MS, + .keep_alive_time_secs = 16960, + }; + + ASSERT_SUCCESS(aws_mqtt_client_connection_connect(mqtt_client, &connection_options)); + aws_test311_wait_for_connection_to_complete(&fixture->client_test_fixture.mqtt311_test_fixture); + + s_aws_rr_client_test_fixture_wait_for_initialized(fixture); return AWS_OP_SUCCESS; } -static bool s_aws_mqtt_protocol_adapter_pinned_mock_is_connected(void *impl) { - struct aws_mqtt_protocol_adapter_pinned_mock *adapter = impl; +static bool s_rr_client_test_fixture_terminated(void *context) { + struct aws_rr_client_test_fixture *fixture = context; - return adapter->is_connected; + return fixture->client_destroyed; } +static void s_aws_rr_client_test_fixture_clean_up(struct aws_rr_client_test_fixture *fixture) { + aws_mqtt_request_response_client_release(fixture->client); -static struct aws_mqtt_protocol_adapter_vtable s_default_protocol_adapter_pinned_mock_vtable = { - .aws_mqtt_protocol_adapter_destroy_fn = s_aws_mqtt_protocol_adapter_pinned_mock_destroy, - .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_pinned_mock_subscribe, - .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_pinned_mock_unsubscribe, - .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_pinned_mock_publish, - .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_pinned_mock_is_connected, -}; + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_rr_client_test_fixture_terminated, fixture); + aws_mutex_unlock(&fixture->lock); -static struct aws_mqtt_protocol_adapter_pinned_mock *s_aws_mqtt_protocol_adapter_new_pinned_mock( - struct aws_allocator *allocator, - const struct aws_mqtt_protocol_adapter_vtable *vtable, - struct aws_event_loop *loop, - void *test_context) { - struct aws_mqtt_protocol_adapter_pinned_mock *adapter = - aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_pinned_mock)); - - adapter->allocator = allocator; - adapter->test_context = test_context; - adapter->loop = loop; - adapter->base.impl = adapter; - if (vtable != NULL) { - adapter->base.vtable = vtable; + if (fixture->test_protocol == RRCP_MQTT5) { + aws_mqtt5_client_mock_test_fixture_clean_up(&fixture->client_test_fixture.mqtt5_test_fixture); } else { - adapter->base.vtable = &s_default_protocol_adapter_pinned_mock_vtable; + struct mqtt_connection_state_test *mqtt311_test_fixture = &fixture->client_test_fixture.mqtt311_test_fixture; + aws_mqtt_client_connection_disconnect( + mqtt311_test_fixture->mqtt_connection, aws_test311_on_disconnect_fn, mqtt311_test_fixture); + aws_test311_clean_up_mqtt_server_fn( + fixture->allocator, AWS_OP_SUCCESS, &fixture->client_test_fixture.mqtt311_test_fixture); } - return adapter; + aws_mutex_clean_up(&fixture->lock); + aws_condition_variable_clean_up(&fixture->signal); } -struct aws_rr_client_test_fixture { - struct aws_allocator *allocator; +static int s_rrc_mqtt5_create_destroy_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; - struct aws_event_loop_group *elg; - struct aws_event_loop *loop; + aws_mqtt_library_init(allocator); - struct aws_request_response_client *client; + struct mqtt5_client_test_options client_test_options; + aws_mqtt5_client_test_init_default_options(&client_test_options); - struct aws_mqtt_protocol_adapter_pinned_mock *mock_adapter; + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { + .client_options = &client_test_options.client_options, + .server_function_table = &client_test_options.server_function_table, + }; - void *test_context; -}; \ No newline at end of file + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_rr_client_test_fixture_init_from_mqtt5(&fixture, allocator, NULL, &client_test_fixture_options, NULL)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_mqtt5_create_destroy, s_rrc_mqtt5_create_destroy_fn) + +static int s_rrc_mqtt311_create_destroy_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_aws_rr_client_test_fixture_init_from_mqtt311(&fixture, allocator, NULL, NULL)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_mqtt311_create_destroy, s_rrc_mqtt311_create_destroy_fn) \ No newline at end of file diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index ffba44f2..2ffd58cf 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -77,7 +77,9 @@ static void s_aws_mqtt_protocol_adapter_mock_destroy(void *impl) { aws_mem_release(adapter->allocator, adapter); } -static int s_aws_mqtt_protocol_adapter_mock_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { +static int s_aws_mqtt_protocol_adapter_mock_subscribe( + void *impl, + struct aws_protocol_adapter_subscribe_options *options) { struct aws_mqtt_protocol_adapter_mock_impl *adapter = impl; struct aws_protocol_adapter_api_record record; @@ -89,7 +91,9 @@ static int s_aws_mqtt_protocol_adapter_mock_subscribe(void *impl, struct aws_pro return AWS_OP_SUCCESS; } -static int s_aws_mqtt_protocol_adapter_mock_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { +static int s_aws_mqtt_protocol_adapter_mock_unsubscribe( + void *impl, + struct aws_protocol_adapter_unsubscribe_options *options) { struct aws_mqtt_protocol_adapter_mock_impl *adapter = impl; struct aws_protocol_adapter_api_record record; @@ -292,8 +296,8 @@ static int s_aws_subscription_manager_test_fixture_init( .operation_timeout_seconds = options->operation_timeout_seconds, .subscription_status_callback = s_aws_rr_subscription_status_event_test_callback_fn, .userdata = fixture}; - ASSERT_SUCCESS(aws_rr_subscription_manager_init( - &fixture->subscription_manager, allocator, fixture->mock_protocol_adapter, &subscription_manager_options)); + aws_rr_subscription_manager_init( + &fixture->subscription_manager, allocator, fixture->mock_protocol_adapter, &subscription_manager_options); return AWS_OP_SUCCESS; } @@ -1399,8 +1403,8 @@ static int s_do_rrsm_acquire_clean_up_test( .subscription_status_callback = s_aws_rr_subscription_status_event_test_callback_fn, .userdata = &fixture, }; - ASSERT_SUCCESS(aws_rr_subscription_manager_init( - &fixture.subscription_manager, allocator, fixture.mock_protocol_adapter, &subscription_manager_options)); + aws_rr_subscription_manager_init( + &fixture.subscription_manager, allocator, fixture.mock_protocol_adapter, &subscription_manager_options); s_aws_subscription_manager_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); From 5d3d2cde579c0ba47606567b548e9c72df7a4574 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 4 Mar 2024 10:38:02 -0800 Subject: [PATCH 063/124] Comments --- .../request_response_client.h | 14 +- .../request_response_client.c | 162 +++++++++++++----- 2 files changed, 133 insertions(+), 43 deletions(-) diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 5e8fa221..5fe38149 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -19,7 +19,7 @@ struct aws_mqtt_request_response_client_options { size_t max_subscriptions; uint32_t operation_timeout_seconds; - // Do not bind the initialized callback; it exists mostly for tests and should not be exposed + /* Do not bind the initialized callback; it exists mostly for tests and should not be exposed */ aws_mqtt_request_response_client_initialized_callback_fn *initialized_callback; aws_mqtt_request_response_client_terminated_callback_fn *terminated_callback; @@ -28,19 +28,31 @@ struct aws_mqtt_request_response_client_options { AWS_EXTERN_C_BEGIN +/* + * Create a new request-response client that uses an MQTT311 client. + */ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client( struct aws_allocator *allocator, struct aws_mqtt_client_connection *client, const struct aws_mqtt_request_response_client_options *options); +/* + * Create a new request-response client that uses an MQTT5 client. + */ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client( struct aws_allocator *allocator, struct aws_mqtt5_client *client, const struct aws_mqtt_request_response_client_options *options); +/* + * Add a reference to a request-response client + */ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire( struct aws_mqtt_request_response_client *client); +/* + * Remove a reference to a request-response client + */ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release( struct aws_mqtt_request_response_client *client); diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 1d313a2b..7b43f452 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -1,7 +1,7 @@ /** -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0. -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ #include @@ -13,16 +13,44 @@ #include #include +/* Tracks the current state of the request-response client */ enum aws_request_response_client_state { - // cross-thread initialization has not completed and all protocol adapter callbacks are ignored + + /* cross-thread initialization has not completed and all protocol adapter callbacks are ignored */ AWS_RRCS_UNINITIALIZED, + /* Normal operating state for the client. */ AWS_RRCS_ACTIVE, - // asynchronously shutting down, no more servicing will be done and all protocol adapter callbacks are ignored + /* asynchronously shutting down, no more servicing will be done and all protocol adapter callbacks are ignored */ AWS_RRCS_SHUTTING_DOWN, }; +/* + * Request-Response Client Notes + * + * Ref-counting/Shutdown + * + * The request-response client uses a double ref-count pattern. + * + * External references represent user references. When the external reference reaches zero, the client's asynchronous + * shutdown process is started. + * + * Internal references block final destruction. Asynchronous shutdown will not complete until all internal references + * are dropped. In addition to one long-lived internal reference (the protocol client adapter's back reference to + * the request-response client), all event loop tasks that target the request-response client hold an internal + * reference between task submission and task completion. This ensures that the task always has a valid reference + * to the client, even if we're trying to shut down at the same time. + * + * + * Initialization + * + * Initialization is complicated by the fact that the subscription manager needs to be initialized from the + * event loop thread that the client/protocol adapter/protocol client are all seated on. To do this safely, + * we add an uninitialized state that ignores all callbacks and we schedule a task on initial construction to do + * the event-loop-only initialization. Once that initialization completes on the event loop thread, we move + * the client into an active state where it will process operations and protocol adapter callbacks. + */ struct aws_mqtt_request_response_client { struct aws_allocator *allocator; @@ -47,12 +75,14 @@ struct aws_mqtt_request_response_client { static void s_aws_rr_client_on_zero_internal_ref_count(void *context) { struct aws_mqtt_request_response_client *client = context; + /* Both ref counts are zero, but it's still safest to schedule final destruction, not invoke it directly */ aws_event_loop_schedule_task_now(client->loop, &client->internal_shutdown_task); } static void s_aws_rr_client_on_zero_external_ref_count(void *context) { struct aws_mqtt_request_response_client *client = context; + /* Start the asynchronous shutdown process */ aws_event_loop_schedule_task_now(client->loop, &client->external_shutdown_task); } @@ -67,7 +97,10 @@ static void s_mqtt_request_response_client_final_destroy(struct aws_mqtt_request } } -static void s_mqtt_request_response_client_internal_shutdown_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { +static void s_mqtt_request_response_client_internal_shutdown_task_fn( + struct aws_task *task, + void *arg, + enum aws_task_status task_status) { (void)task; (void)task_status; @@ -76,13 +109,16 @@ static void s_mqtt_request_response_client_internal_shutdown_task_fn(struct aws_ /* * All internal and external refs are gone; it is safe to clean up synchronously. * - * The subscription manager is cleaned up and the protocol adapter has been shut down. All that's left is to - * free memory. + * The subscription manager is cleaned up and the protocol adapter has been shut down. No tasks targeting the + * client are active (other than this one). All that's left is to free memory. */ s_mqtt_request_response_client_final_destroy(client); } -static void s_mqtt_request_response_client_external_shutdown_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { +static void s_mqtt_request_response_client_external_shutdown_task_fn( + struct aws_task *task, + void *arg, + enum aws_task_status task_status) { (void)task; AWS_FATAL_ASSERT(task_status != AWS_TASK_STATUS_CANCELED); @@ -101,21 +137,25 @@ static void s_mqtt_request_response_client_external_shutdown_task_fn(struct aws_ aws_ref_count_release(&client->internal_ref_count); } -static void s_aws_rr_client_subscription_status_event_callback(const struct aws_rr_subscription_status_event *event, void *userdata) { +static void s_aws_rr_client_subscription_status_event_callback( + const struct aws_rr_subscription_status_event *event, + void *userdata) { (void)event; (void)userdata; /* - * We must be on the event loop, but it's safer overall to process this event as a top-level event loop task. The subscription - * manager assumes that we won't call APIs on it while iterating subscription records and listeners. + * We must be on the event loop, but it's safer overall to process this event as a top-level event loop task. The + * subscription manager assumes that we won't call APIs on it while iterating subscription records and listeners. * * These tasks hold an internal reference while they exist. */ - // NYI + /* NYI */ } -static void s_aws_rr_client_protocol_adapter_subscription_event_callback(const struct aws_protocol_adapter_subscription_event *event, void *user_data) { +static void s_aws_rr_client_protocol_adapter_subscription_event_callback( + const struct aws_protocol_adapter_subscription_event *event, + void *user_data) { struct aws_mqtt_request_response_client *rr_client = user_data; AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(rr_client->loop)); @@ -140,17 +180,19 @@ static void s_aws_rr_client_protocol_adapter_incoming_publish_callback( return; } - // NYI + /* NYI */ } static void s_aws_rr_client_protocol_adapter_terminate_callback(void *user_data) { struct aws_mqtt_request_response_client *rr_client = user_data; - // release the internal ref count "held" by the protocol adapter's existence + /* release the internal ref count "held" by the protocol adapter's existence */ aws_ref_count_release(&rr_client->internal_ref_count); } -static void s_aws_rr_client_protocol_adapter_connection_event_callback(const struct aws_protocol_adapter_connection_event *event, void *user_data) { +static void s_aws_rr_client_protocol_adapter_connection_event_callback( + const struct aws_protocol_adapter_connection_event *event, + void *user_data) { struct aws_mqtt_request_response_client *rr_client = user_data; AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(rr_client->loop)); @@ -162,38 +204,56 @@ static void s_aws_rr_client_protocol_adapter_connection_event_callback(const str aws_rr_subscription_manager_on_protocol_adapter_connection_event(&rr_client->subscription_manager, event); } -static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_new(struct aws_allocator *allocator, const struct aws_mqtt_request_response_client_options *options, struct aws_event_loop *loop) { +static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_new( + struct aws_allocator *allocator, + const struct aws_mqtt_request_response_client_options *options, + struct aws_event_loop *loop) { struct aws_rr_subscription_manager_options sm_options = { .max_subscriptions = options->max_subscriptions, .operation_timeout_seconds = options->operation_timeout_seconds, }; - // we can't initialize the subscription manager until we're running on the event loop, so make sure that - // initialize can't fail by checking its options for validity now. + /* + * We can't initialize the subscription manager until we're running on the event loop, so make sure that + * initialize can't fail by checking its options for validity now. + */ if (!aws_rr_subscription_manager_are_options_valid(&sm_options)) { return NULL; } - struct aws_mqtt_request_response_client *rr_client = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_request_response_client)); + struct aws_mqtt_request_response_client *rr_client = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_request_response_client)); rr_client->allocator = allocator; rr_client->config = *options; rr_client->loop = loop; rr_client->state = AWS_RRCS_UNINITIALIZED; - aws_task_init(&rr_client->external_shutdown_task, s_mqtt_request_response_client_external_shutdown_task_fn, rr_client, "mqtt_rr_client_external_shutdown"); - aws_task_init(&rr_client->internal_shutdown_task, s_mqtt_request_response_client_internal_shutdown_task_fn, rr_client, "mqtt_rr_client_internal_shutdown"); - - // 1 external ref to the caller + aws_task_init( + &rr_client->external_shutdown_task, + s_mqtt_request_response_client_external_shutdown_task_fn, + rr_client, + "mqtt_rr_client_external_shutdown"); + aws_task_init( + &rr_client->internal_shutdown_task, + s_mqtt_request_response_client_internal_shutdown_task_fn, + rr_client, + "mqtt_rr_client_internal_shutdown"); + + /* The initial external ref belongs to the caller */ aws_ref_count_init(&rr_client->external_ref_count, rr_client, s_aws_rr_client_on_zero_external_ref_count); - // 1 internal ref count belongs to ourselves (the external ref count shutdown task) + /* The initial internal ref belongs to ourselves (the external ref count shutdown task) */ aws_ref_count_init(&rr_client->internal_ref_count, rr_client, s_aws_rr_client_on_zero_internal_ref_count); return rr_client; } -static void s_aws_rr_client_init_subscription_manager(struct aws_mqtt_request_response_client *rr_client, struct aws_allocator *allocator) { +static void s_aws_rr_client_init_subscription_manager( + struct aws_mqtt_request_response_client *rr_client, + struct aws_allocator *allocator) { + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(rr_client->loop)); + struct aws_rr_subscription_manager_options subscription_manager_options = { .operation_timeout_seconds = rr_client->config.operation_timeout_seconds, .max_subscriptions = rr_client->config.max_subscriptions, @@ -201,10 +261,14 @@ static void s_aws_rr_client_init_subscription_manager(struct aws_mqtt_request_re .userdata = rr_client, }; - aws_rr_subscription_manager_init(&rr_client->subscription_manager, allocator, rr_client->client_adapter, &subscription_manager_options); + aws_rr_subscription_manager_init( + &rr_client->subscription_manager, allocator, rr_client->client_adapter, &subscription_manager_options); } -static void s_mqtt_request_response_client_initialize_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { +static void s_mqtt_request_response_client_initialize_task_fn( + struct aws_task *task, + void *arg, + enum aws_task_status task_status) { (void)task; AWS_FATAL_ASSERT(task_status != AWS_TASK_STATUS_CANCELED); @@ -221,23 +285,31 @@ static void s_mqtt_request_response_client_initialize_task_fn(struct aws_task *t (*client->config.initialized_callback)(client->config.user_data); } - // give up the internal ref we held while the task was pending + /* give up the internal ref we held while the task was pending */ aws_ref_count_release(&client->internal_ref_count); } -static void s_setup_cross_thread_initialization(struct aws_mqtt_request_response_client * rr_client) { - // now that it exists, 1 internal ref belongs to protocol adapter termination +static void s_setup_cross_thread_initialization(struct aws_mqtt_request_response_client *rr_client) { + /* now that it exists, 1 internal ref belongs to protocol adapter termination */ aws_ref_count_acquire(&rr_client->internal_ref_count); - // 1 internal ref belongs to the initialize task until it runs + /* 1 internal ref belongs to the initialize task until it runs */ aws_ref_count_acquire(&rr_client->internal_ref_count); - aws_task_init(&rr_client->initialize_task, s_mqtt_request_response_client_initialize_task_fn, rr_client, "mqtt_rr_client_initialize"); + aws_task_init( + &rr_client->initialize_task, + s_mqtt_request_response_client_initialize_task_fn, + rr_client, + "mqtt_rr_client_initialize"); aws_event_loop_schedule_task_now(rr_client->loop, &rr_client->initialize_task); } -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client(struct aws_allocator *allocator, struct aws_mqtt_client_connection *client, const struct aws_mqtt_request_response_client_options *options) { +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client( + struct aws_allocator *allocator, + struct aws_mqtt_client_connection *client, + const struct aws_mqtt_request_response_client_options *options) { - struct aws_mqtt_request_response_client *rr_client = s_aws_mqtt_request_response_client_new(allocator, options, aws_mqtt_client_connection_get_event_loop(client)); + struct aws_mqtt_request_response_client *rr_client = + s_aws_mqtt_request_response_client_new(allocator, options, aws_mqtt_client_connection_get_event_loop(client)); struct aws_mqtt_protocol_adapter_options adapter_options = { .subscription_event_callback = s_aws_rr_client_protocol_adapter_subscription_event_callback, @@ -258,15 +330,19 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_fr error: - // even on construction failures we still need to walk through the async shutdown process + /* even on construction failures we still need to walk through the async shutdown process */ aws_mqtt_request_response_client_release(rr_client); return NULL; } -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client(struct aws_allocator *allocator, struct aws_mqtt5_client *client, const struct aws_mqtt_request_response_client_options *options) { +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client( + struct aws_allocator *allocator, + struct aws_mqtt5_client *client, + const struct aws_mqtt_request_response_client_options *options) { - struct aws_mqtt_request_response_client * rr_client = s_aws_mqtt_request_response_client_new(allocator, options, client->loop); + struct aws_mqtt_request_response_client *rr_client = + s_aws_mqtt_request_response_client_new(allocator, options, client->loop); struct aws_mqtt_protocol_adapter_options adapter_options = { .subscription_event_callback = s_aws_rr_client_protocol_adapter_subscription_event_callback, @@ -287,13 +363,14 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_fr error: - // even on construction failures we still need to walk through the async shutdown process + /* even on construction failures we still need to walk through the async shutdown process */ aws_mqtt_request_response_client_release(rr_client); return NULL; } -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire(struct aws_mqtt_request_response_client *client) { +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire( + struct aws_mqtt_request_response_client *client) { if (client != NULL) { aws_ref_count_acquire(&client->external_ref_count); } @@ -301,7 +378,8 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquir return client; } -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release(struct aws_mqtt_request_response_client *client) { +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release( + struct aws_mqtt_request_response_client *client) { if (client != NULL) { aws_ref_count_release(&client->external_ref_count); } From 249ce9f12a4621e086920705ace0ae24ca89f7fc Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 4 Mar 2024 10:58:10 -0800 Subject: [PATCH 064/124] Feedback --- source/mqtt.c | 2 +- source/request-response/subscription_manager.c | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/source/mqtt.c b/source/mqtt.c index 0fbafdb5..2f8c8378 100644 --- a/source/mqtt.c +++ b/source/mqtt.c @@ -254,7 +254,7 @@ static struct aws_error_info_list s_error_list = { 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"), - DEFINE_LOG_SUBJECT_INFO(AWS_LS_MQTT_REQUEST_RESPONSE, "mqtt-request-response-client", "MQTT request-response client logging"), + DEFINE_LOG_SUBJECT_INFO(AWS_LS_MQTT_REQUEST_RESPONSE, "mqtt-request-response-systems", "MQTT request-response systems logging"), }; /* clang-format on */ diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 05286786..a07a5478 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -569,14 +569,13 @@ void aws_rr_subscription_manager_on_protocol_adapter_connection_event( struct aws_rr_subscription_manager *manager, const struct aws_protocol_adapter_connection_event *event) { - AWS_LOGF_DEBUG( - AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - received a protocol adapter connection event, type %s, joined_session " - "%d", - aws_protocol_adapter_connection_event_type_to_c_str(event->event_type), - (int)(event->joined_session ? 1 : 0)); - if (event->event_type == AWS_PACET_CONNECTED) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - received a protocol adapter connection event, joined_session: " + "%d", + (int)(event->joined_session ? 1 : 0)); + manager->is_protocol_client_connected = true; if (!event->joined_session) { s_apply_session_lost(manager); @@ -585,6 +584,10 @@ void aws_rr_subscription_manager_on_protocol_adapter_connection_event( s_cull_unused_subscriptions(manager); s_activate_idle_subscriptions(manager); } else { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - received a protocol adapter disconnection event"); + manager->is_protocol_client_connected = false; } } From 3361455bce7c4c3860e164e9c3a9aa65ca4bf7d1 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 4 Mar 2024 11:02:36 -0800 Subject: [PATCH 065/124] visibility macro --- .../aws/mqtt/request-response/request_response_client.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 5fe38149..001f20e2 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -31,7 +31,7 @@ AWS_EXTERN_C_BEGIN /* * Create a new request-response client that uses an MQTT311 client. */ -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client( +AWS_MQTT_API struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt311_client( struct aws_allocator *allocator, struct aws_mqtt_client_connection *client, const struct aws_mqtt_request_response_client_options *options); @@ -39,7 +39,7 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_fr /* * Create a new request-response client that uses an MQTT5 client. */ -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client( +AWS_MQTT_API struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_from_mqtt5_client( struct aws_allocator *allocator, struct aws_mqtt5_client *client, const struct aws_mqtt_request_response_client_options *options); @@ -47,13 +47,13 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_fr /* * Add a reference to a request-response client */ -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire( +AWS_MQTT_API struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire( struct aws_mqtt_request_response_client *client); /* * Remove a reference to a request-response client */ -struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release( +AWS_MQTT_API struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release( struct aws_mqtt_request_response_client *client); AWS_EXTERN_C_END From 419f5bc359fd9d80977614f554056dde6c8493f0 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 5 Mar 2024 09:13:12 -0800 Subject: [PATCH 066/124] Wording --- source/request-response/subscription_manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index a07a5478..8ea8c3e2 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -162,7 +162,7 @@ static void s_subscription_record_unsubscribe( record->pending_action = ARRSPAT_UNSUBSCRIBING; } -/* Only called when shutting down the client */ +/* Only called when shutting down the request-response client */ static int s_rr_subscription_clean_up_foreach_wrap(void *context, struct aws_hash_element *elem) { struct aws_rr_subscription_manager *manager = context; struct aws_rr_subscription_record *subscription = elem->value; From 545aba0b0397a01b7260e8884ce85d509aa5f0ca Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 6 Mar 2024 15:28:40 -0800 Subject: [PATCH 067/124] Checkpoint --- .../request-response/request_response.h | 33 +++ .../request-response/subscription_manager.h | 22 +- .../request_response_client.h | 71 ++++- .../request_response_client.c | 268 ++++++++++++++++++ 4 files changed, 371 insertions(+), 23 deletions(-) create mode 100644 include/aws/mqtt/private/request-response/request_response.h diff --git a/include/aws/mqtt/private/request-response/request_response.h b/include/aws/mqtt/private/request-response/request_response.h new file mode 100644 index 00000000..ef58ce9d --- /dev/null +++ b/include/aws/mqtt/private/request-response/request_response.h @@ -0,0 +1,33 @@ +#ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_H +#define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +/* + * Describes a change to the state of a request-response client subscription + */ +enum aws_rr_subscription_event_type { + + /* + * A subscribe succeeded + */ + ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + + /* + * A subscribe failed + */ + ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + + /* + * A previously successful subscription has ended (generally due to a failure to resume a session) + */ + ARRSET_SUBSCRIPTION_ENDED +}; + +#endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_H */ + diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 605a4994..61d0e400 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -9,32 +9,12 @@ #include #include +#include struct aws_mqtt_protocol_adapter; struct aws_protocol_adapter_connection_event; struct aws_protocol_adapter_subscription_event; -/* - * The kind of subscription event being emitted. - */ -enum aws_rr_subscription_event_type { - - /* - * A subscribe succeeded - */ - ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, - - /* - * A subscribe failed - */ - ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, - - /* - * A previously successful subscription has ended (generally due to a failure to resume a session) - */ - ARRSET_SUBSCRIPTION_ENDED -}; - struct aws_rr_subscription_status_event { enum aws_rr_subscription_event_type type; struct aws_byte_cursor topic_filter; diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 001f20e2..7c0e570f 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -6,11 +6,66 @@ * SPDX-License-Identifier: Apache-2.0. */ -#include "aws/mqtt/mqtt.h" +#include + +#include struct aws_mqtt_request_response_client; struct aws_mqtt_client_connection; struct aws_mqtt5_client; +struct aws_mqtt_streaming_operation; + +struct aws_mqtt_request_operation_message_path { + struct aws_byte_cursor topic; + + /* potential point of expansion into an abstract "extractor" if we ever need to support non-JSON payloads */ + struct aws_byte_cursor correlation_token_json_path; +}; + +typedef void(aws_mqtt_request_operation_completion_fn)(int error_code, struct aws_byte_cursor payload, void *user_data); + +struct aws_mqtt_request_operation_options { + struct aws_byte_cursor subscription_topic_filter; + + struct aws_mqtt_request_operation_message_path *message_paths; + size_t message_path_count; + + struct aws_byte_cursor publish_topic; + struct aws_byte_cursor serialized_request; + struct aws_byte_cursor correlation_token; + + aws_mqtt_request_operation_completion_fn *completion_callback; + void *user_data; +}; + +struct aws_mqtt_request_operation_storage { + struct aws_mqtt_request_operation_options options; + + struct aws_array_list operation_message_paths; + + struct aws_byte_buf operation_data; +}; + +typedef void(aws_mqtt_streaming_operation_subscription_status_fn)(enum aws_rr_subscription_event_type status, int error_code, void *user_data); +typedef void(aws_mqtt_streaming_operation_incoming_publish_fn)(struct aws_byte_cursor payload, void *user_data); +typedef void(aws_mqtt_streaming_operation_terminated_fn)(void *user_data); + +struct aws_mqtt_streaming_operation_options { + struct aws_byte_cursor topic_filter; + + aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback; + aws_mqtt_streaming_operation_incoming_publish_fn *incoming_publish_callback; + aws_mqtt_streaming_operation_terminated_fn *terminated_callback; + + void *user_data; +}; + +struct aws_mqtt_streaming_operation_storage { + struct aws_mqtt_streaming_operation_options options; + + struct aws_byte_buf operation_data; +}; + typedef void(aws_mqtt_request_response_client_initialized_callback_fn)(void *user_data); typedef void(aws_mqtt_request_response_client_terminated_callback_fn)(void *user_data); @@ -21,8 +76,8 @@ struct aws_mqtt_request_response_client_options { /* Do not bind the initialized callback; it exists mostly for tests and should not be exposed */ aws_mqtt_request_response_client_initialized_callback_fn *initialized_callback; - aws_mqtt_request_response_client_terminated_callback_fn *terminated_callback; + void *user_data; }; @@ -56,6 +111,18 @@ AWS_MQTT_API struct aws_mqtt_request_response_client *aws_mqtt_request_response_ AWS_MQTT_API struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release( struct aws_mqtt_request_response_client *client); +AWS_MQTT_API int aws_mqtt_request_response_client_submit_request( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_request_operation_options *request_options); + +AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_request_response_client_create_streaming_operation( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_streaming_operation_options *streaming_options); + +AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_streaming_operation_acquire(struct aws_mqtt_streaming_operation *operation); + +AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_streaming_operation_release(struct aws_mqtt_streaming_operation *operation); + AWS_EXTERN_C_END #endif /* AWS_MQTT_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H */ diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 7b43f452..7ad5b6db 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -13,6 +13,243 @@ #include #include +enum aws_mqtt_request_response_operation_type { + AWS_MRROT_REQUEST, + AWS_MRROT_STREAMING, +}; + +enum aws_mqtt_request_response_operation_state { + AWS_MRROS_NONE, // creation -> in event loop enqueue + AWS_MRROS_QUEUED, // in event loop queue -> non blocked response from subscription manager + AWS_MRROS_PENDING_SUBSCRIPTION, // subscribing response from sub manager -> subscription success/failure event + AWS_MRROS_AWAITING_RESPONSE, // (request only) subscription success -> (publish failure OR correlated response received) + AWS_MRROS_SUBSCRIBED, // (streaming only) subscription success -> (operation finished OR subscription ended event) + AWS_MRROS_TERMINAL, // (streaming only) (subscription failure OR subscription ended) -> operation close/terminate +}; + +/* + +Tables/Lookups + + (Authoritative operation container) + 1. &operation.id -> &operation // added on in-thread enqueue, removed on operation completion/destruction + + (Response topic -> Correlation token extraction info) + 2. &topic -> &{topic, topic_buffer, correlation token json path buffer} // per-message-path add/replace on in-thread enqueue, removed on client destruction + + (Correlation token -> request operation) + 3. &operation.correlation token -> (request) &operation // added on in-thread request op move to awaiting response state, removed on operation completion/destruction + + (Subscription filter -> all operations using that filter) + 4. &topic_filter -> &{topic_filter, linked_list} // added on in-thread pop from queue, removed from list on operation completion/destruction also checked for empty and removed from table + +*/ + +/* All operations have an internal ref to the client they are a part of */ + +/* + On submit request operation API [Anywhere]: + + Allocate id + Create operation + Submit cross-thread task + + */ + +/* + On submit streaming operation API [Anywhere]: + + Allocate id + Create operation + Submit cross-thread task + Return (ref-counted) operation + + */ + +/* + On receive operation [Event Loop, top-level task]: + + Add to operations table + (Request) Add message paths to path table if no exist or different value + Add to timeout priority queue + Add operation to end of queue list + state <- QUEUED + Wake service task + + */ + +/* + Complete (request) operation [Event Loop]: + + Completion Callback (Success/Failure) + State <- TERMINAL + Decref operation + */ + +/* + On operation ref to zero [Anywhere]: + + Submit cross-thread task to destroy operation (operation terminate callback chains directly to the binding) + */ + +/* + On operation destroy [Event Loop, top-level task]: + + Remove from operations table + Remove from intrusive list + (Request only) Remove from correlation token table + (Streaming only) Check streaming topic table for empty list, remove entry if so + Remove from timeout priority queue + Remove from subscription manager + Wake service task // What if this is the last internal ref? Should service task have an internal reference while scheduled? + (Streaming) Invoke termination callback + Release client internal ref + + */ + +/* + On incoming publish [Event Loop]: + + If topic in streaming routes table + for all streaming operations in list + if operation.state == SUBSCRIBED + invoke publish received callback + + If topic in paths table: + If correlation token extraction success + If entry exists in correlation token table + Complete operation with publish payload + */ + +/* + On Publish completion [Event Loop]: + + If Error + Complete and Fail Operation(id) + + */ + + +/* + On protocol adapter connection event [Event Loop]: + + Notify subscription manager + Wake service task + */ + +/* + On subscription status event [Event Loop, top-level task]: + + For all streaming operations in topic_filter table list: + If Success and state == SUBSCRIBING + state <- SUBSCRIBED + Invoke Success/Failure callback with success + Else If Failure and state == SUBSCRIBING + state <- TERMINAL + Invoke Success/Failure callback with failure + Else if Subscription Ended and state != TERMINAL + state <- TERMINAL + Invoke Ended callback + If Failure or Ended: + sub manager release_subscription(operation id) + + For all request operations in request topic filter list: + If Success and state == SUBSCRIBING + MakeRequest(op) + + If Failure or Ended + Complete operation with failure + + */ + +/* + MakeRequest(op) [Event Loop]: + + state <- AWAITING_RESPONSE + if publish fails synchronously + Complete operation with failure + Decref(op) + */ + +/* + Handle acquire sub result(op, result) [Event Loop, Service Task Loop]: + + If result == {No Capacity, Failure} + If op is streaming + Invoke failure callback + state <- TERMINAL + else + Complete operation with failure + Decref + return + + If streaming + Add operation to topic filter table + State <- {SUBSCRIBING, SUBSCRIBED} + + If request + Add operation to topic filter table + if result == SUBSCRIBING + state <- SUBSCRIBING + else // (SUBSCRIBED) + MakeRequest(op) + + + */ +/* + Service task [Event Loop]: + + For all timed out operations: + Invoke On Operation Timeout + + While OperationQueue is not empty: + op = peek queue + result = subscription manager acquire sub(op) + if result == Blocked + break + pop op + handle acquire sub result (op, result) + + Reschedule Service for next timeout if it exists + */ + +/* + On operation timeout [Event Loop, Service Task Loop]: + + If request + Complete with failure + Decref-op + If streaming and state != {SUBSCRIBED, TERMINAL} + state <- TERMINAL + Invoke failure callback + + */ + +struct aws_mqtt_rr_client_operation { + struct aws_allocator *allocator; + + struct aws_ref_count ref_count; + + struct aws_mqtt_request_response_client *internal_client_ref; + + uint64_t id; + + enum aws_mqtt_request_response_operation_type type; + + union { + struct aws_mqtt_streaming_operation_storage streaming_storage; + struct aws_mqtt_request_operation_storage request_storage; + } storage; + + uint64_t ack_timeout_timepoint_ns; + struct aws_priority_queue_node priority_queue_node; + struct aws_linked_list_node node; + + enum aws_mqtt_request_response_operation_state state; +}; + +/*******************************************************************************************/ + /* Tracks the current state of the request-response client */ enum aws_request_response_client_state { @@ -386,3 +623,34 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_releas return NULL; } + +int aws_mqtt_request_response_client_submit_request( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_request_operation_options *request_options) { + (void)client; + (void)request_options; + + return aws_raise_error(AWS_ERROR_UNIMPLEMENTED); +} + +AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_request_response_client_create_streaming_operation( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_streaming_operation_options *streaming_options) { + + (void)client; + (void)streaming_options; + + return NULL; +} + +AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_streaming_operation_acquire(struct aws_mqtt_streaming_operation *operation) { + (void)operation; + + return NULL; +} + +AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_streaming_operation_release(struct aws_mqtt_streaming_operation *operation) { + (void)operation; + + return NULL; +} From f69b09f0bc69c30dcc67ca0e4c3eff13c23af17c Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 8 Mar 2024 16:37:41 -0800 Subject: [PATCH 068/124] Updates --- include/aws/mqtt/mqtt.h | 1 + .../request_response_client.h | 20 +- source/mqtt.c | 3 + .../request_response_client.c | 686 +++++++++++++++--- 4 files changed, 598 insertions(+), 112 deletions(-) diff --git a/include/aws/mqtt/mqtt.h b/include/aws/mqtt/mqtt.h index d8034640..11232125 100644 --- a/include/aws/mqtt/mqtt.h +++ b/include/aws/mqtt/mqtt.h @@ -82,6 +82,7 @@ enum aws_mqtt_error { AWS_ERROR_MQTT_CONNECTION_RESUBSCRIBE_NO_TOPICS, AWS_ERROR_MQTT_CONNECTION_SUBSCRIBE_FAILURE, AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, + AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, AWS_ERROR_END_MQTT_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_MQTT_PACKAGE_ID), }; diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 7c0e570f..236ac093 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -15,20 +15,20 @@ struct aws_mqtt_client_connection; struct aws_mqtt5_client; struct aws_mqtt_streaming_operation; -struct aws_mqtt_request_operation_message_path { +struct aws_mqtt_request_operation_response_path { struct aws_byte_cursor topic; /* potential point of expansion into an abstract "extractor" if we ever need to support non-JSON payloads */ struct aws_byte_cursor correlation_token_json_path; }; -typedef void(aws_mqtt_request_operation_completion_fn)(int error_code, struct aws_byte_cursor payload, void *user_data); +typedef void(aws_mqtt_request_operation_completion_fn)(struct aws_byte_cursor *payload, int error_code, void *user_data); struct aws_mqtt_request_operation_options { struct aws_byte_cursor subscription_topic_filter; - struct aws_mqtt_request_operation_message_path *message_paths; - size_t message_path_count; + struct aws_mqtt_request_operation_response_path *response_paths; + size_t response_path_count; struct aws_byte_cursor publish_topic; struct aws_byte_cursor serialized_request; @@ -41,7 +41,7 @@ struct aws_mqtt_request_operation_options { struct aws_mqtt_request_operation_storage { struct aws_mqtt_request_operation_options options; - struct aws_array_list operation_message_paths; + struct aws_array_list operation_response_paths; struct aws_byte_buf operation_data; }; @@ -113,15 +113,15 @@ AWS_MQTT_API struct aws_mqtt_request_response_client *aws_mqtt_request_response_ AWS_MQTT_API int aws_mqtt_request_response_client_submit_request( struct aws_mqtt_request_response_client *client, - struct aws_mqtt_request_operation_options *request_options); + const struct aws_mqtt_request_operation_options *request_options); -AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_request_response_client_create_streaming_operation( +AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_streaming_operation( struct aws_mqtt_request_response_client *client, - struct aws_mqtt_streaming_operation_options *streaming_options); + const struct aws_mqtt_streaming_operation_options *streaming_options); -AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_streaming_operation_acquire(struct aws_mqtt_streaming_operation *operation); +AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_acquire(struct aws_mqtt_rr_client_operation *operation); -AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_streaming_operation_release(struct aws_mqtt_streaming_operation *operation); +AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_release(struct aws_mqtt_rr_client_operation *operation); AWS_EXTERN_C_END diff --git a/source/mqtt.c b/source/mqtt.c index 2f8c8378..f1f7c360 100644 --- a/source/mqtt.c +++ b/source/mqtt.c @@ -236,6 +236,9 @@ bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter) AWS_DEFINE_ERROR_INFO_MQTT( AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, "MQTT operation returned a failing reason code"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, + "Request operation failed due to client shut down"), }; /* clang-format on */ #undef AWS_DEFINE_ERROR_INFO_MQTT diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 7ad5b6db..5822bdb8 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -13,6 +13,8 @@ #include #include +#define MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE 50 + enum aws_mqtt_request_response_operation_type { AWS_MRROT_REQUEST, AWS_MRROT_STREAMING, @@ -25,6 +27,7 @@ enum aws_mqtt_request_response_operation_state { AWS_MRROS_AWAITING_RESPONSE, // (request only) subscription success -> (publish failure OR correlated response received) AWS_MRROS_SUBSCRIBED, // (streaming only) subscription success -> (operation finished OR subscription ended event) AWS_MRROS_TERMINAL, // (streaming only) (subscription failure OR subscription ended) -> operation close/terminate + AWS_MRROS_PENDING_DESTROY, // (request only) the request operation's destroy task has been scheduled but not yet executed }; /* @@ -48,189 +51,244 @@ Tables/Lookups /* All operations have an internal ref to the client they are a part of */ /* - On submit request operation API [Anywhere]: + SubmitRequestOperation(options) [Anywhere]: + Validate options Allocate id - Create operation + Create operation with ref count == 2 Submit cross-thread task */ /* - On submit streaming operation API [Anywhere]: + CreateStreamingOperation(options) [Anywhere]: + Validate options Allocate id - Create operation + Create operation with ref count == 2 Submit cross-thread task Return (ref-counted) operation */ /* - On receive operation [Event Loop, top-level task]: + OperationSubmissionThreadTask(operation) [Event Loop, top-level task]: - Add to operations table - (Request) Add message paths to path table if no exist or different value - Add to timeout priority queue - Add operation to end of queue list - state <- QUEUED - Wake service task + Add to client.operations table + (Request) Add message paths to client.paths table if no exist or different value + Add to client's timeout priority queue + Add operation to end of client.operation_queue list + operation.state <- QUEUED + WakeServiceTask + operation.decref (2 -> 1) */ /* - Complete (request) operation [Event Loop]: + CompleteRequestOperation(operation, payload, error_code) [Event Loop]: - Completion Callback (Success/Failure) - State <- TERMINAL - Decref operation + if operation.state != PENDING_DESTROY + CompletionCallback(payload, error_code) + operation.state <- PENDING_DESTROY + operation.decref // schedules destroy task */ /* - On operation ref to zero [Anywhere]: + OnOperationZeroRefCount(operation) [Anywhere]: - Submit cross-thread task to destroy operation (operation terminate callback chains directly to the binding) + Schedule operation's destroy task on client event loop */ /* - On operation destroy [Event Loop, top-level task]: - - Remove from operations table - Remove from intrusive list - (Request only) Remove from correlation token table - (Streaming only) Check streaming topic table for empty list, remove entry if so - Remove from timeout priority queue - Remove from subscription manager - Wake service task // What if this is the last internal ref? Should service task have an internal reference while scheduled? + WakeServiceTask(client) [Event Loop]: + + If client.state != SHUTTING_DOWN && protocol client is connected + RescheduleServiceTask(now) + */ + +/* + OperationDestroyTask(operation) [Event Loop, top-level task]: + + Remove from client.operations + Remove from (client) intrusive list + Remove from client's timeout priority queue + if operation.type == REQUEST + Remove from client's correlation token table + Zero publish completion weak ref wrapper around operation + dec-ref weak-ref-operation-wrapper + Check client's topic filter table entry for empty list, remove entry if so. (intrusive list removal already unlinked it from table) + If client is not shutting down + remove from subscription manager (otherwise it's already been cleaned up) + + WakeServiceTask // queue may now be unblocked, does nothing if shutting down (Streaming) Invoke termination callback Release client internal ref */ /* - On incoming publish [Event Loop]: + OnIncomingPublish(publish) [Event Loop]: + + if client.state != ACTIVE + // If shutting down, request operations are all in PENDING_DESTROY + // If initializing, publish cannot be relevant + return - If topic in streaming routes table + If publish.topic in client's topic filter table for all streaming operations in list if operation.state == SUBSCRIBED invoke publish received callback - If topic in paths table: + If publish.topic in paths table: If correlation token extraction success - If entry exists in correlation token table - Complete operation with publish payload + If operation entry exists in correlation token table + CompleteRequestOperation(operation, payload) // Complete does nothing if the operation is being killed */ /* - On Publish completion [Event Loop]: - - If Error - Complete and Fail Operation(id) + OnProtocolAdapterConnectionEvent(event) [Event Loop]: + client.subscription_manager.notify(event) + WakeServiceTask */ +/* + OnPublishCompletion(result, userdata) [Event Loop, Direct From Protocol Adapter, Operation as UserData]: + + weak-ref-operation-wrapper = userdata + if weak-ref-operation-wrapper can be resolved to an operation: + If result is error + CompleteRequestOperation(operation, error) + + dec-ref weak-ref-operation-wrapper + */ /* - On protocol adapter connection event [Event Loop]: + MakeRequest(operation) [Event Loop]: - Notify subscription manager - Wake service task + // Critical Requirement - the user data for the publish completion callback must be a weak ref that wraps + // the operation. On operation destruction, we zero the weak ref (and dec ref it). + operation.state <- AWAITING_RESPONSE + if publish fails synchronously + CompleteRequestOperation(operation, error) */ /* - On subscription status event [Event Loop, top-level task]: - - For all streaming operations in topic_filter table list: - If Success and state == SUBSCRIBING - state <- SUBSCRIBED - Invoke Success/Failure callback with success - Else If Failure and state == SUBSCRIBING - state <- TERMINAL - Invoke Success/Failure callback with failure - Else if Subscription Ended and state != TERMINAL - state <- TERMINAL - Invoke Ended callback - If Failure or Ended: - sub manager release_subscription(operation id) - - For all request operations in request topic filter list: - If Success and state == SUBSCRIBING - MakeRequest(op) + RequestOperationOnSubscriptionStatusEvent(operation, event) [Event loop, top-level task loop]: - If Failure or Ended - Complete operation with failure + If event.type == SUBSCRIBE_SUCCESS and operation.state == SUBSCRIBING + MakeRequest(operation) + If event.type == {SUBSCRIBE_FAILURE, ENDED} + CompleteRequestOperation(failure) */ /* - MakeRequest(op) [Event Loop]: + StreamingOperationOnSubscriptionStatusEvent(operation, event) [Event loop, top-level task loop]: + + If event.type == Success and operation.state == SUBSCRIBING + operation.state <- SUBSCRIBED + Invoke Success/Failure callback with success + Else If event.type == Failure and operation.state == SUBSCRIBING + operation.state <- TERMINAL + Invoke Success/Failure callback with failure + Else if event.type == Ended and operation.state != TERMINAL + operation.state <- TERMINAL + Invoke Ended callback + + If Failure or Ended: + sub manager release_subscription(operation id) + */ - state <- AWAITING_RESPONSE - if publish fails synchronously - Complete operation with failure - Decref(op) +/* + OnSubscriptionStatusEvent(event) [Event Loop, top-level task from sub manager]: + + For all operations in topic_filter table list: + if operation.type == Streaming + StreamingOperationOnSubscriptionStatusEvent(operation, event) + else + RequestOperationOnSubscriptionStatusEvent(operation, event) */ /* - Handle acquire sub result(op, result) [Event Loop, Service Task Loop]: + HandleAcquireSubscriptionResult(operation, result) [Event Loop, Service Task Loop]: + // invariant, BLOCKED is not possible, it was already handled If result == {No Capacity, Failure} - If op is streaming + If operation is streaming Invoke failure callback - state <- TERMINAL + operation.state <- TERMINAL else - Complete operation with failure - Decref + CompleteRequestOperation(operation, error) return - If streaming + // invariant, must be SUBSCRIBING or SUBSCRIBED at this point + If operation is streaming Add operation to topic filter table - State <- {SUBSCRIBING, SUBSCRIBED} + operation.state <- {SUBSCRIBING, SUBSCRIBED} - If request - Add operation to topic filter table + If operation is request + Add operation to client's topic filter table if result == SUBSCRIBING - state <- SUBSCRIBING + operation.state <- SUBSCRIBING else // (SUBSCRIBED) MakeRequest(op) - - */ + /* Service task [Event Loop]: For all timed out operations: - Invoke On Operation Timeout + OnOperationTimeout(operation) While OperationQueue is not empty: - op = peek queue - result = subscription manager acquire sub(op) + operation = peek queue + result = subscription manager acquire sub(operation) if result == Blocked break - pop op - handle acquire sub result (op, result) + pop operation + HandleAcquireSubscriptionResult(operation, result) Reschedule Service for next timeout if it exists */ /* - On operation timeout [Event Loop, Service Task Loop]: + OnOperationTimeout(operation) [Event Loop, Service Task Loop]: - If request - Complete with failure - Decref-op - If streaming and state != {SUBSCRIBED, TERMINAL} - state <- TERMINAL - Invoke failure callback + If operation.type == request and operation.state != PENDING_DESTROY + CompleteRequestOperation(operation, error) + + If operation.type == streaming and operation.state != {SUBSCRIBED, TERMINAL} + operation.state <- TERMINAL + Invoke operation failure callback */ struct aws_mqtt_rr_client_operation { struct aws_allocator *allocator; + /* + * Operation ref-counting is a bit tricky and un-intuitive because it differs based on the type of operation. + * + * Streaming operations are managed by the user, and so the ref count is their responsibility to drop to zero. + * Dropping a streaming operation's ref count to zero schedules a task on the client event loop to destroy the + * operation. It is expected that the binding client will track (with proper synchronization) all unclosed + * streaming operations and safely close them for the user when close is called on the binding client. + * + * Request operations are managed by the client, and so the ref count is dropped to zero when either the + * operation completes normally (success or failure) or when the client is shutdown due to its external ref + * count dropping to zero. In all cases, this event happens naturally on the client event loop. + * + * So the summary is: + * + * (1) Streaming operation clean up is initiated by the user calling dec ref on the streaming operation + * (2) Request operation clean up is initiated by normal completion or client shutdown invoking dec ref. + * + * The upshot is that client shutdown dec-refs request operations but not streaming operations. + */ struct aws_ref_count ref_count; - struct aws_mqtt_request_response_client *internal_client_ref; + struct aws_mqtt_request_response_client *client_internal_ref; uint64_t id; @@ -243,9 +301,14 @@ struct aws_mqtt_rr_client_operation { uint64_t ack_timeout_timepoint_ns; struct aws_priority_queue_node priority_queue_node; + + /* Sometimes this is client->operation_queue, other times it is an entry in the client's topic_filter table */ struct aws_linked_list_node node; enum aws_mqtt_request_response_operation_state state; + + struct aws_task submit_task; + struct aws_task destroy_task; }; /*******************************************************************************************/ @@ -307,6 +370,16 @@ struct aws_mqtt_request_response_client { struct aws_task internal_shutdown_task; enum aws_request_response_client_state state; + + struct aws_atomic_var next_id; + + struct aws_linked_list operation_queue; + + /* &operation->id -> &operation */ + struct aws_hash_table operations; + + struct aws_task service_task; + uint64_t next_service_scheduled_timepoint; }; static void s_aws_rr_client_on_zero_internal_ref_count(void *context) { @@ -327,6 +400,9 @@ static void s_mqtt_request_response_client_final_destroy(struct aws_mqtt_request aws_mqtt_request_response_client_terminated_callback_fn *terminate_callback = client->config.terminated_callback; void *user_data = client->config.user_data; + AWS_FATAL_ASSERT(aws_hash_table_get_entry_count(&client->operations) == 0); + aws_hash_table_clean_up(&client->operations); + aws_mem_release(client->allocator, client); if (terminate_callback != NULL) { @@ -352,6 +428,83 @@ static void s_mqtt_request_response_client_internal_shutdown_task_fn( s_mqtt_request_response_client_final_destroy(client); } +static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_operation *operation, int error_code) { + AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); + AWS_FATAL_ASSERT(error_code != AWS_ERROR_SUCCESS); + + if (operation->state == AWS_MRROS_PENDING_DESTROY) { + return; + } + + aws_mqtt_request_operation_completion_fn *completion_callback = operation->storage.request_storage.options.completion_callback; + void *user_data = operation->storage.request_storage.options.user_data; + + if (completion_callback != NULL) { + (*completion_callback)(NULL, error_code, user_data); + } + + operation->state = AWS_MRROS_PENDING_DESTROY; + + aws_mqtt_rr_client_operation_release(operation); +} + +static void s_complete_request_operation_with_success(struct aws_mqtt_rr_client_operation *operation, struct aws_byte_cursor payload) { + AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); + + if (operation->state == AWS_MRROS_PENDING_DESTROY) { + return; + } + + aws_mqtt_request_operation_completion_fn *completion_callback = operation->storage.request_storage.options.completion_callback; + void *user_data = operation->storage.request_storage.options.user_data; + + if (completion_callback != NULL) { + (*completion_callback)(&payload, AWS_ERROR_SUCCESS, user_data); + } + + operation->state = AWS_MRROS_PENDING_DESTROY; + + aws_mqtt_rr_client_operation_release(operation); +} + +static void s_streaming_operation_on_client_shutdown(struct aws_mqtt_rr_client_operation *operation, int error_code) { + AWS_FATAL_ASSERT(operation->type == AWS_MRROT_STREAMING); + AWS_FATAL_ASSERT(error_code != AWS_ERROR_SUCCESS); + + switch (operation->state) { + case AWS_MRROS_QUEUED: + case AWS_MRROS_PENDING_SUBSCRIPTION: + case AWS_MRROS_SUBSCRIBED: { + aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback = operation->storage.streaming_storage.options.subscription_status_callback; + void *user_data = operation->storage.streaming_storage.options.user_data; + if (subscription_status_callback != NULL) { + enum aws_rr_subscription_event_type status_type = (operation->state == AWS_MRROS_SUBSCRIBED) ? ARRSET_SUBSCRIPTION_ENDED : ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE; + (*subscription_status_callback)(status_type, error_code, user_data); + } + } + + default: + break; + } + + operation->state = AWS_MRROS_TERMINAL; +} + +static int s_rr_client_clean_up_operation(void *context, struct aws_hash_element *elem) { + (void)context; + struct aws_mqtt_rr_client_operation *operation = elem->value; + + if (operation->type == AWS_MRROT_REQUEST) { + /* Complete the request operation as a failure */ + s_complete_request_operation_with_failure(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN); + } else { + /* Streaming operations that are not subscribed should emit a subscription failure event */ + s_streaming_operation_on_client_shutdown(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN); + } + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; +} + static void s_mqtt_request_response_client_external_shutdown_task_fn( struct aws_task *task, void *arg, @@ -371,6 +524,16 @@ static void s_mqtt_request_response_client_external_shutdown_task_fn( aws_mqtt_protocol_adapter_destroy(client->client_adapter); } + /* + * It is a client invariant that when external shutdown starts, it must be the case that there are no in-flight + * operations with un-executed submit tasks. This means it safe to assume that all tracked operations are + * either in the process of cleaning up already (state == AWS_MRROS_PENDING_DESTROY, requests only) or can be + * completed now (state != ??). Actual operation destruction and ref-count release is done by a scheduled task + * on the operation that is triggered by dec-refing it (assuming streaming operations get closed by the binding + * client). + */ + aws_hash_table_foreach(&client->operations, s_rr_client_clean_up_operation, NULL); + aws_ref_count_release(&client->internal_ref_count); } @@ -441,6 +604,30 @@ static void s_aws_rr_client_protocol_adapter_connection_event_callback( aws_rr_subscription_manager_on_protocol_adapter_connection_event(&rr_client->subscription_manager, event); } +static void s_wake_rr_client_service_task(struct aws_mqtt_request_response_client *rr_client) { + (void)rr_client; + + // NYI +} + +static void s_mqtt_rr_client_service_task_fn( struct aws_task *task, + void *arg, + enum aws_task_status task_status) { + (void)task; + (void)arg; + (void)task_status; + + // NYI +} + +uint64_t aws_mqtt_hash_uint64_t(const void *item) { + return *(uint64_t *)item; +} + +bool aws_mqtt_compare_uint64_t_eq(const void *a, const void *b) { + return *(uint64_t *)a == *(uint64_t *)b; +} + static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_new( struct aws_allocator *allocator, const struct aws_mqtt_request_response_client_options *options, @@ -466,6 +653,14 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie rr_client->loop = loop; rr_client->state = AWS_RRCS_UNINITIALIZED; + aws_hash_table_init(&rr_client->operations, + allocator, + MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE, + aws_mqtt_hash_uint64_t, + aws_mqtt_compare_uint64_t_eq, + NULL, + NULL); + aws_task_init( &rr_client->external_shutdown_task, s_mqtt_request_response_client_external_shutdown_task_fn, @@ -477,12 +672,19 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie rr_client, "mqtt_rr_client_internal_shutdown"); + aws_task_init(&rr_client->service_task, + s_mqtt_rr_client_service_task_fn, + rr_client, + "mqtt_rr_client_service"); + /* The initial external ref belongs to the caller */ aws_ref_count_init(&rr_client->external_ref_count, rr_client, s_aws_rr_client_on_zero_external_ref_count); /* The initial internal ref belongs to ourselves (the external ref count shutdown task) */ aws_ref_count_init(&rr_client->internal_ref_count, rr_client, s_aws_rr_client_on_zero_internal_ref_count); + aws_atomic_store_int(&rr_client->next_id, 1); + return rr_client; } @@ -624,33 +826,313 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_releas return NULL; } +struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_acquire_internal( + struct aws_mqtt_request_response_client *client) { + if (client != NULL) { + aws_ref_count_acquire(&client->internal_ref_count); + } + + return client; +} + +struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_release_internal( + struct aws_mqtt_request_response_client *client) { + if (client != NULL) { + aws_ref_count_release(&client->internal_ref_count); + } + + return NULL; +} + +///////////////////////////////////////////////// + +static bool s_are_request_operation_options_valid(const struct aws_mqtt_request_response_client *client, const struct aws_mqtt_request_operation_options *request_options) { + if (request_options == NULL) { + AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client - NULL request options", (void *)client); + return false; + } + + if (request_options->response_path_count == 0) { + AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - no response paths supplied", (void *)client); + return false; + } + + for (size_t i = 0; i < request_options->response_path_count; ++i) { + const struct aws_mqtt_request_operation_response_path *path = &request_options->response_paths[i]; + if (!aws_mqtt_is_valid_topic(&path->topic)) { + AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - "PRInSTR " is not a valid topic", (void *)client, AWS_BYTE_CURSOR_PRI(path->topic)); + return false; + } + } + + if (request_options->correlation_token.len == 0) { + AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - empty correlation token", (void *)client); + return false; + } + + if (!aws_mqtt_is_valid_topic(&request_options->publish_topic)) { + AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - "PRInSTR " is not a valid topic", (void *)client, AWS_BYTE_CURSOR_PRI(request_options->publish_topic)); + return false; + } + + if (request_options->serialized_request.len == 0) { + AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - empty request payload", (void *)client); + return false; + } + + return true; +} + +static bool s_are_streaming_operation_options_valid(struct aws_mqtt_request_response_client *client, const struct aws_mqtt_streaming_operation_options *streaming_options) { + if (streaming_options == NULL) { + AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client - NULL streaming options", (void *)client); + return false; + } + + if (!aws_mqtt_is_valid_topic_filter(&streaming_options->topic_filter)) { + AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client streaming options - "PRInSTR " is not a valid topic filter", (void *)client, AWS_BYTE_CURSOR_PRI(streaming_options->topic_filter)); + return false; + } + + return true; +} + +static uint64_t s_aws_mqtt_request_response_client_allocate_operation_id(struct aws_mqtt_request_response_client *client) { + return aws_atomic_fetch_add(&client->next_id, 1); +} + +static void s_mqtt_rr_client_submit_operation(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_mqtt_rr_client_operation *operation = arg; + + if (status == AWS_TASK_STATUS_CANCELED) { + goto done; + } + + // add appropriate client table entries + aws_hash_table_put(&operation->client_internal_ref->operations, &operation->id, operation, NULL); + + // NYI other tables + + // NYI set up timeout + + // enqueue + aws_linked_list_push_back(&operation->client_internal_ref->operation_queue, &operation->node); + + operation->state = AWS_MRROS_QUEUED; + + s_wake_rr_client_service_task(operation->client_internal_ref); + +done: + + /* + * We hold a second reference to the operation during submission. This ensures that even if a streaming operation + * is immediately dec-refed by the creator (before submission completes), the operation will not get destroyed. + * + * It is now safe and correct to release that reference. + * + * After this, streaming operation lifetime is completely user-driven, while request operation lifetime is + * completely client-internal. + */ + aws_mqtt_rr_client_operation_release(operation); +} + +static void s_aws_mqtt_streaming_operation_storage_clean_up(struct aws_mqtt_streaming_operation_storage *storage) { + aws_byte_buf_clean_up(&storage->operation_data); +} + +static void s_aws_mqtt_request_operation_storage_clean_up(struct aws_mqtt_request_operation_storage *storage) { + aws_array_list_clean_up(&storage->operation_response_paths); + aws_byte_buf_clean_up(&storage->operation_data); +} + +static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + (void)status; + + struct aws_mqtt_rr_client_operation *operation = arg; + struct aws_mqtt_request_response_client *client = operation->client_internal_ref; + + aws_hash_table_remove(&client->operations, &operation->id, NULL, NULL); + + aws_linked_list_remove(&operation->node); + + if (client->state != AWS_RRCS_SHUTTING_DOWN) { + struct aws_rr_release_subscription_options release_options = { + .topic_filter = ??, + .id = operation->id, + }; + aws_rr_subscription_manager_release_subscription(&client->subscription_manager, &release_options); + } + + /* + NYI: + + Remove from timeout tracking + Remove from topic filter table + Remove from correlation token table + + */ + + s_aws_mqtt_request_response_client_release_internal(operation->client_internal_ref); + + if (operation->type == AWS_MRROT_STREAMING) { + s_aws_mqtt_streaming_operation_storage_clean_up(&operation->storage.streaming_storage); + } else { + s_aws_mqtt_request_operation_storage_clean_up(&operation->storage.request_storage); + } + + aws_mqtt_streaming_operation_terminated_fn *terminated_callback = NULL; + void *terminated_user_data = NULL; + if (operation->type == AWS_MRROT_STREAMING) { + terminated_callback = operation->storage.streaming_storage.options.terminated_callback; + terminated_user_data = operation->storage.streaming_storage.options.user_data; + } + + aws_mem_release(operation->allocator, operation); + + if (terminated_callback != NULL) { + (*terminated_callback)(terminated_user_data); + } +} + +static void s_on_mqtt_rr_client_operation_zero_ref_count(void *context) { + struct aws_mqtt_rr_client_operation *operation = context; + + aws_event_loop_schedule_task_now(operation->client_internal_ref->loop, &operation->destroy_task); +} + +static void s_aws_mqtt_rr_client_operation_init_shared(struct aws_mqtt_rr_client_operation *operation, struct aws_mqtt_request_response_client *client) { + operation->allocator = client->allocator; + aws_ref_count_init(&operation->ref_count, operation, s_on_mqtt_rr_client_operation_zero_ref_count); + + /* + * We hold a second reference to the operation during submission. This ensures that even if a streaming operation + * is immediately dec-refed by the creator (before submission runs), the operation will not get destroyed. + */ + aws_mqtt_rr_client_operation_acquire(operation); + + operation->client_internal_ref = s_aws_mqtt_request_response_client_acquire_internal(client); + operation->id = s_aws_mqtt_request_response_client_allocate_operation_id(client); + operation->state = AWS_MRROS_NONE; + + aws_task_init(&operation->submit_task, s_mqtt_rr_client_submit_operation, operation, "MQTTRequestResponseClientOperationSubmit"); + aws_task_init(&operation->destroy_task, s_mqtt_rr_client_destroy_operation, operation, "MQTTRequestResponseClientOperationDestroy"); +} + +void s_aws_mqtt_request_operation_storage_init_from_options(struct aws_mqtt_request_operation_storage *storage, struct aws_allocator *allocator, const struct aws_mqtt_request_operation_options *request_options) { + + size_t bytes_needed = 0; + bytes_needed += request_options->publish_topic.len; + bytes_needed += request_options->serialized_request.len; + bytes_needed += request_options->correlation_token.len; + bytes_needed += request_options->subscription_topic_filter.len; + + for (size_t i = 0; i < request_options->response_path_count; ++i) { + const struct aws_mqtt_request_operation_response_path *response_path = &request_options->response_paths[i]; + + bytes_needed += response_path->topic.len; + bytes_needed += response_path->correlation_token_json_path.len; + } + + storage->options = *request_options; + + aws_byte_buf_init(&storage->operation_data, allocator, bytes_needed); + aws_array_list_init_dynamic(&storage->operation_response_paths, allocator, request_options->response_path_count, sizeof(struct aws_mqtt_request_operation_response_path)); + + AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.publish_topic) == AWS_OP_SUCCESS); + AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.serialized_request) == AWS_OP_SUCCESS); + AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.correlation_token) == AWS_OP_SUCCESS); + AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.subscription_topic_filter) == AWS_OP_SUCCESS); + + for (size_t i = 0; i < request_options->response_path_count; ++i) { + struct aws_mqtt_request_operation_response_path response_path = request_options->response_paths[i]; + + AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &response_path.topic) == AWS_OP_SUCCESS); + AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &response_path.correlation_token_json_path) == AWS_OP_SUCCESS); + + aws_array_list_push_back(&storage->operation_response_paths, &response_path); + } + + storage->options.response_paths = storage->operation_response_paths.data; +} + int aws_mqtt_request_response_client_submit_request( struct aws_mqtt_request_response_client *client, - struct aws_mqtt_request_operation_options *request_options) { - (void)client; - (void)request_options; + const struct aws_mqtt_request_operation_options *request_options) { + + if (client == NULL) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (!s_are_request_operation_options_valid(client, request_options)) { + /* all failure cases have logged the problem already */ + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_allocator *allocator = client->allocator; + struct aws_mqtt_rr_client_operation *operation = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_rr_client_operation)); + operation->allocator = allocator; + operation->type = AWS_MRROT_REQUEST; + + s_aws_mqtt_request_operation_storage_init_from_options(&operation->storage.request_storage, allocator, request_options); + s_aws_mqtt_rr_client_operation_init_shared(operation, client); + + aws_event_loop_schedule_task_now(client->loop, &operation->submit_task); + + return AWS_OP_SUCCESS; +} + +void s_aws_mqtt_streaming_operation_storage_init_from_options(struct aws_mqtt_streaming_operation_storage *storage, struct aws_allocator *allocator, const struct aws_mqtt_streaming_operation_options *streaming_options) { + size_t bytes_needed = streaming_options->topic_filter.len; - return aws_raise_error(AWS_ERROR_UNIMPLEMENTED); + storage->options = *streaming_options; + aws_byte_buf_init(&storage->operation_data, allocator, bytes_needed); + + AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.topic_filter) == AWS_OP_SUCCESS); } -AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_request_response_client_create_streaming_operation( +struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_streaming_operation( struct aws_mqtt_request_response_client *client, - struct aws_mqtt_streaming_operation_options *streaming_options) { + const struct aws_mqtt_streaming_operation_options *streaming_options) { - (void)client; - (void)streaming_options; + if (client == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } - return NULL; + if (!s_are_streaming_operation_options_valid(client, streaming_options)) { + /* all failure cases have logged the problem already */ + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_allocator *allocator = client->allocator; + struct aws_mqtt_rr_client_operation *operation = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_rr_client_operation)); + operation->allocator = allocator; + operation->type = AWS_MRROT_STREAMING; + + s_aws_mqtt_streaming_operation_storage_init_from_options(&operation->storage.streaming_storage, allocator, streaming_options); + s_aws_mqtt_rr_client_operation_init_shared(operation, client); + + aws_event_loop_schedule_task_now(client->loop, &operation->submit_task); + + return operation; } -AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_streaming_operation_acquire(struct aws_mqtt_streaming_operation *operation) { - (void)operation; +struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_acquire(struct aws_mqtt_rr_client_operation *operation) { + if (operation != NULL) { + aws_ref_count_acquire(&operation->ref_count); + } - return NULL; + return operation; } -AWS_MQTT_API struct aws_mqtt_streaming_operation *aws_mqtt_streaming_operation_release(struct aws_mqtt_streaming_operation *operation) { - (void)operation; +struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_release(struct aws_mqtt_rr_client_operation *operation) { + if (operation != NULL) { + aws_ref_count_release(&operation->ref_count); + } return NULL; } From eeda8381625471ef0873fb3bb5bc62b0570995f8 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sat, 9 Mar 2024 10:02:39 -0800 Subject: [PATCH 069/124] Updates --- .../request_response_client.c | 244 +++++++++++------- 1 file changed, 151 insertions(+), 93 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 5822bdb8..9b937c43 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -21,30 +21,35 @@ enum aws_mqtt_request_response_operation_type { }; enum aws_mqtt_request_response_operation_state { - AWS_MRROS_NONE, // creation -> in event loop enqueue - AWS_MRROS_QUEUED, // in event loop queue -> non blocked response from subscription manager + AWS_MRROS_NONE, // creation -> in event loop enqueue + AWS_MRROS_QUEUED, // in event loop queue -> non blocked response from subscription manager AWS_MRROS_PENDING_SUBSCRIPTION, // subscribing response from sub manager -> subscription success/failure event - AWS_MRROS_AWAITING_RESPONSE, // (request only) subscription success -> (publish failure OR correlated response received) + AWS_MRROS_AWAITING_RESPONSE, // (request only) subscription success -> (publish failure OR correlated response + // received) AWS_MRROS_SUBSCRIBED, // (streaming only) subscription success -> (operation finished OR subscription ended event) - AWS_MRROS_TERMINAL, // (streaming only) (subscription failure OR subscription ended) -> operation close/terminate - AWS_MRROS_PENDING_DESTROY, // (request only) the request operation's destroy task has been scheduled but not yet executed + AWS_MRROS_TERMINAL, // (streaming only) (subscription failure OR subscription ended) -> operation close/terminate + AWS_MRROS_PENDING_DESTROY, // (request only) the request operation's destroy task has been scheduled but not yet + // executed }; /* -Tables/Lookups +Client Tables/Lookups (Authoritative operation container) 1. &operation.id -> &operation // added on in-thread enqueue, removed on operation completion/destruction (Response topic -> Correlation token extraction info) - 2. &topic -> &{topic, topic_buffer, correlation token json path buffer} // per-message-path add/replace on in-thread enqueue, removed on client destruction + 2. &topic -> &{topic, topic_buffer, correlation token json path buffer} // per-message-path add/replace on in-thread +enqueue, removed on client destruction (Correlation token -> request operation) - 3. &operation.correlation token -> (request) &operation // added on in-thread request op move to awaiting response state, removed on operation completion/destruction + 3. &operation.correlation token -> (request) &operation // added on in-thread request op move to awaiting response +state, removed on operation completion/destruction (Subscription filter -> all operations using that filter) - 4. &topic_filter -> &{topic_filter, linked_list} // added on in-thread pop from queue, removed from list on operation completion/destruction also checked for empty and removed from table + 4. &topic_filter -> &{topic_filter, linked_list} // added on in-thread pop from queue, removed from list on +operation completion/destruction also checked for empty and removed from table */ @@ -116,9 +121,8 @@ Tables/Lookups Remove from client's correlation token table Zero publish completion weak ref wrapper around operation dec-ref weak-ref-operation-wrapper - Check client's topic filter table entry for empty list, remove entry if so. (intrusive list removal already unlinked it from table) - If client is not shutting down - remove from subscription manager (otherwise it's already been cleaned up) + Check client's topic filter table entry for empty list, remove entry if so. (intrusive list removal already unlinked it + from table) If client is not shutting down remove from subscription manager (otherwise it's already been cleaned up) WakeServiceTask // queue may now be unblocked, does nothing if shutting down (Streaming) Invoke termination callback @@ -299,9 +303,6 @@ struct aws_mqtt_rr_client_operation { struct aws_mqtt_request_operation_storage request_storage; } storage; - uint64_t ack_timeout_timepoint_ns; - struct aws_priority_queue_node priority_queue_node; - /* Sometimes this is client->operation_queue, other times it is an entry in the client's topic_filter table */ struct aws_linked_list_node node; @@ -377,9 +378,6 @@ struct aws_mqtt_request_response_client { /* &operation->id -> &operation */ struct aws_hash_table operations; - - struct aws_task service_task; - uint64_t next_service_scheduled_timepoint; }; static void s_aws_rr_client_on_zero_internal_ref_count(void *context) { @@ -419,12 +417,7 @@ static void s_mqtt_request_response_client_internal_shutdown_task_fn( struct aws_mqtt_request_response_client *client = arg; - /* - * All internal and external refs are gone; it is safe to clean up synchronously. - * - * The subscription manager is cleaned up and the protocol adapter has been shut down. No tasks targeting the - * client are active (other than this one). All that's left is to free memory. - */ + /* All internal and external refs are gone; it is safe to clean up synchronously. */ s_mqtt_request_response_client_final_destroy(client); } @@ -436,7 +429,8 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ return; } - aws_mqtt_request_operation_completion_fn *completion_callback = operation->storage.request_storage.options.completion_callback; + aws_mqtt_request_operation_completion_fn *completion_callback = + operation->storage.request_storage.options.completion_callback; void *user_data = operation->storage.request_storage.options.user_data; if (completion_callback != NULL) { @@ -448,14 +442,17 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ aws_mqtt_rr_client_operation_release(operation); } -static void s_complete_request_operation_with_success(struct aws_mqtt_rr_client_operation *operation, struct aws_byte_cursor payload) { +static void s_complete_request_operation_with_success( + struct aws_mqtt_rr_client_operation *operation, + struct aws_byte_cursor payload) { AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); if (operation->state == AWS_MRROS_PENDING_DESTROY) { return; } - aws_mqtt_request_operation_completion_fn *completion_callback = operation->storage.request_storage.options.completion_callback; + aws_mqtt_request_operation_completion_fn *completion_callback = + operation->storage.request_storage.options.completion_callback; void *user_data = operation->storage.request_storage.options.user_data; if (completion_callback != NULL) { @@ -475,10 +472,13 @@ static void s_streaming_operation_on_client_shutdown(struct aws_mqtt_rr_client_o case AWS_MRROS_QUEUED: case AWS_MRROS_PENDING_SUBSCRIPTION: case AWS_MRROS_SUBSCRIBED: { - aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback = operation->storage.streaming_storage.options.subscription_status_callback; + aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback = + operation->storage.streaming_storage.options.subscription_status_callback; void *user_data = operation->storage.streaming_storage.options.user_data; if (subscription_status_callback != NULL) { - enum aws_rr_subscription_event_type status_type = (operation->state == AWS_MRROS_SUBSCRIBED) ? ARRSET_SUBSCRIPTION_ENDED : ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE; + enum aws_rr_subscription_event_type status_type = (operation->state == AWS_MRROS_SUBSCRIBED) + ? ARRSET_SUBSCRIPTION_ENDED + : ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE; (*subscription_status_callback)(status_type, error_code, user_data); } } @@ -498,7 +498,7 @@ static int s_rr_client_clean_up_operation(void *context, struct aws_hash_element /* Complete the request operation as a failure */ s_complete_request_operation_with_failure(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN); } else { - /* Streaming operations that are not subscribed should emit a subscription failure event */ + /* Non-terminal streaming operations should a subscription failure or ended event */ s_streaming_operation_on_client_shutdown(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN); } @@ -526,9 +526,12 @@ static void s_mqtt_request_response_client_external_shutdown_task_fn( /* * It is a client invariant that when external shutdown starts, it must be the case that there are no in-flight - * operations with un-executed submit tasks. This means it safe to assume that all tracked operations are - * either in the process of cleaning up already (state == AWS_MRROS_PENDING_DESTROY, requests only) or can be - * completed now (state != ??). Actual operation destruction and ref-count release is done by a scheduled task + * operations with un-executed submit tasks. This means it safe to assume that all tracked request operations are + * either in the process of cleaning up already (state == AWS_MRROS_PENDING_DESTROY) or can be + * completed now (state != AWS_MRROS_PENDING_DESTROY). Non-terminal streaming operations are moved into + * a terminal state and emit an appropriate failure/ended event. + * + * Actual operation destruction and client ref-count release is done by a scheduled task * on the operation that is triggered by dec-refing it (assuming streaming operations get closed by the binding * client). */ @@ -550,6 +553,11 @@ static void s_aws_rr_client_subscription_status_event_callback( * These tasks hold an internal reference while they exist. */ + struct aws_mqtt_request_response_client *rr_client = userdata; + + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(rr_client->loop)); + AWS_FATAL_ASSERT(rr_client->state != AWS_RRCS_SHUTTING_DOWN); + /* NYI */ } @@ -604,22 +612,6 @@ static void s_aws_rr_client_protocol_adapter_connection_event_callback( aws_rr_subscription_manager_on_protocol_adapter_connection_event(&rr_client->subscription_manager, event); } -static void s_wake_rr_client_service_task(struct aws_mqtt_request_response_client *rr_client) { - (void)rr_client; - - // NYI -} - -static void s_mqtt_rr_client_service_task_fn( struct aws_task *task, - void *arg, - enum aws_task_status task_status) { - (void)task; - (void)arg; - (void)task_status; - - // NYI -} - uint64_t aws_mqtt_hash_uint64_t(const void *item) { return *(uint64_t *)item; } @@ -653,30 +645,27 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie rr_client->loop = loop; rr_client->state = AWS_RRCS_UNINITIALIZED; - aws_hash_table_init(&rr_client->operations, - allocator, - MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE, - aws_mqtt_hash_uint64_t, - aws_mqtt_compare_uint64_t_eq, - NULL, - NULL); + aws_hash_table_init( + &rr_client->operations, + allocator, + MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE, + aws_mqtt_hash_uint64_t, + aws_mqtt_compare_uint64_t_eq, + NULL, + NULL); aws_task_init( &rr_client->external_shutdown_task, s_mqtt_request_response_client_external_shutdown_task_fn, rr_client, "mqtt_rr_client_external_shutdown"); + aws_task_init( &rr_client->internal_shutdown_task, s_mqtt_request_response_client_internal_shutdown_task_fn, rr_client, "mqtt_rr_client_internal_shutdown"); - aws_task_init(&rr_client->service_task, - s_mqtt_rr_client_service_task_fn, - rr_client, - "mqtt_rr_client_service"); - /* The initial external ref belongs to the caller */ aws_ref_count_init(&rr_client->external_ref_count, rr_client, s_aws_rr_client_on_zero_external_ref_count); @@ -734,6 +723,7 @@ static void s_setup_cross_thread_initialization(struct aws_mqtt_request_response /* 1 internal ref belongs to the initialize task until it runs */ aws_ref_count_acquire(&rr_client->internal_ref_count); + aws_task_init( &rr_client->initialize_task, s_mqtt_request_response_client_initialize_task_fn, @@ -846,58 +836,80 @@ struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_rele ///////////////////////////////////////////////// -static bool s_are_request_operation_options_valid(const struct aws_mqtt_request_response_client *client, const struct aws_mqtt_request_operation_options *request_options) { +static bool s_are_request_operation_options_valid( + const struct aws_mqtt_request_response_client *client, + const struct aws_mqtt_request_operation_options *request_options) { if (request_options == NULL) { AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client - NULL request options", (void *)client); return false; } if (request_options->response_path_count == 0) { - AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - no response paths supplied", (void *)client); + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "(%p) rr client request options - no response paths supplied", + (void *)client); return false; } for (size_t i = 0; i < request_options->response_path_count; ++i) { const struct aws_mqtt_request_operation_response_path *path = &request_options->response_paths[i]; if (!aws_mqtt_is_valid_topic(&path->topic)) { - AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - "PRInSTR " is not a valid topic", (void *)client, AWS_BYTE_CURSOR_PRI(path->topic)); + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "(%p) rr client request options - " PRInSTR " is not a valid topic", + (void *)client, + AWS_BYTE_CURSOR_PRI(path->topic)); return false; } } if (request_options->correlation_token.len == 0) { - AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - empty correlation token", (void *)client); + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - empty correlation token", (void *)client); return false; } if (!aws_mqtt_is_valid_topic(&request_options->publish_topic)) { - AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - "PRInSTR " is not a valid topic", (void *)client, AWS_BYTE_CURSOR_PRI(request_options->publish_topic)); + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "(%p) rr client request options - " PRInSTR " is not a valid topic", + (void *)client, + AWS_BYTE_CURSOR_PRI(request_options->publish_topic)); return false; } if (request_options->serialized_request.len == 0) { - AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - empty request payload", (void *)client); + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - empty request payload", (void *)client); return false; } return true; } -static bool s_are_streaming_operation_options_valid(struct aws_mqtt_request_response_client *client, const struct aws_mqtt_streaming_operation_options *streaming_options) { +static bool s_are_streaming_operation_options_valid( + struct aws_mqtt_request_response_client *client, + const struct aws_mqtt_streaming_operation_options *streaming_options) { if (streaming_options == NULL) { AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client - NULL streaming options", (void *)client); return false; } if (!aws_mqtt_is_valid_topic_filter(&streaming_options->topic_filter)) { - AWS_LOGF_ERROR(AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client streaming options - "PRInSTR " is not a valid topic filter", (void *)client, AWS_BYTE_CURSOR_PRI(streaming_options->topic_filter)); + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "(%p) rr client streaming options - " PRInSTR " is not a valid topic filter", + (void *)client, + AWS_BYTE_CURSOR_PRI(streaming_options->topic_filter)); return false; } return true; } -static uint64_t s_aws_mqtt_request_response_client_allocate_operation_id(struct aws_mqtt_request_response_client *client) { +static uint64_t s_aws_mqtt_request_response_client_allocate_operation_id( + struct aws_mqtt_request_response_client *client) { return aws_atomic_fetch_add(&client->next_id, 1); } @@ -922,7 +934,7 @@ static void s_mqtt_rr_client_submit_operation(struct aws_task *task, void *arg, operation->state = AWS_MRROS_QUEUED; - s_wake_rr_client_service_task(operation->client_internal_ref); + // NYI wake service done: @@ -947,6 +959,15 @@ static void s_aws_mqtt_request_operation_storage_clean_up(struct aws_mqtt_reques aws_byte_buf_clean_up(&storage->operation_data); } +static struct aws_byte_cursor s_aws_mqtt_rr_operation_get_subscription_topic_filter( + struct aws_mqtt_rr_client_operation *operation) { + if (operation->type == AWS_MRROT_REQUEST) { + return operation->storage.request_storage.options.subscription_topic_filter; + } else { + return operation->storage.streaming_storage.options.topic_filter; + } +} + static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, enum aws_task_status status) { (void)task; (void)status; @@ -960,8 +981,8 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, if (client->state != AWS_RRCS_SHUTTING_DOWN) { struct aws_rr_release_subscription_options release_options = { - .topic_filter = ??, - .id = operation->id, + .topic_filter = s_aws_mqtt_rr_operation_get_subscription_topic_filter(operation), + .operation_id = operation->id, }; aws_rr_subscription_manager_release_subscription(&client->subscription_manager, &release_options); } @@ -1003,7 +1024,9 @@ static void s_on_mqtt_rr_client_operation_zero_ref_count(void *context) { aws_event_loop_schedule_task_now(operation->client_internal_ref->loop, &operation->destroy_task); } -static void s_aws_mqtt_rr_client_operation_init_shared(struct aws_mqtt_rr_client_operation *operation, struct aws_mqtt_request_response_client *client) { +static void s_aws_mqtt_rr_client_operation_init_shared( + struct aws_mqtt_rr_client_operation *operation, + struct aws_mqtt_request_response_client *client) { operation->allocator = client->allocator; aws_ref_count_init(&operation->ref_count, operation, s_on_mqtt_rr_client_operation_zero_ref_count); @@ -1017,11 +1040,22 @@ static void s_aws_mqtt_rr_client_operation_init_shared(struct aws_mqtt_rr_client operation->id = s_aws_mqtt_request_response_client_allocate_operation_id(client); operation->state = AWS_MRROS_NONE; - aws_task_init(&operation->submit_task, s_mqtt_rr_client_submit_operation, operation, "MQTTRequestResponseClientOperationSubmit"); - aws_task_init(&operation->destroy_task, s_mqtt_rr_client_destroy_operation, operation, "MQTTRequestResponseClientOperationDestroy"); + aws_task_init( + &operation->submit_task, + s_mqtt_rr_client_submit_operation, + operation, + "MQTTRequestResponseClientOperationSubmit"); + aws_task_init( + &operation->destroy_task, + s_mqtt_rr_client_destroy_operation, + operation, + "MQTTRequestResponseClientOperationDestroy"); } -void s_aws_mqtt_request_operation_storage_init_from_options(struct aws_mqtt_request_operation_storage *storage, struct aws_allocator *allocator, const struct aws_mqtt_request_operation_options *request_options) { +void s_aws_mqtt_request_operation_storage_init_from_options( + struct aws_mqtt_request_operation_storage *storage, + struct aws_allocator *allocator, + const struct aws_mqtt_request_operation_options *request_options) { size_t bytes_needed = 0; bytes_needed += request_options->publish_topic.len; @@ -1039,18 +1073,32 @@ void s_aws_mqtt_request_operation_storage_init_from_options(struct aws_mqtt_requ storage->options = *request_options; aws_byte_buf_init(&storage->operation_data, allocator, bytes_needed); - aws_array_list_init_dynamic(&storage->operation_response_paths, allocator, request_options->response_path_count, sizeof(struct aws_mqtt_request_operation_response_path)); - - AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.publish_topic) == AWS_OP_SUCCESS); - AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.serialized_request) == AWS_OP_SUCCESS); - AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.correlation_token) == AWS_OP_SUCCESS); - AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.subscription_topic_filter) == AWS_OP_SUCCESS); + aws_array_list_init_dynamic( + &storage->operation_response_paths, + allocator, + request_options->response_path_count, + sizeof(struct aws_mqtt_request_operation_response_path)); + + AWS_FATAL_ASSERT( + aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.publish_topic) == AWS_OP_SUCCESS); + AWS_FATAL_ASSERT( + aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.serialized_request) == + AWS_OP_SUCCESS); + AWS_FATAL_ASSERT( + aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.correlation_token) == + AWS_OP_SUCCESS); + AWS_FATAL_ASSERT( + aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.subscription_topic_filter) == + AWS_OP_SUCCESS); for (size_t i = 0; i < request_options->response_path_count; ++i) { struct aws_mqtt_request_operation_response_path response_path = request_options->response_paths[i]; - AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &response_path.topic) == AWS_OP_SUCCESS); - AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &response_path.correlation_token_json_path) == AWS_OP_SUCCESS); + AWS_FATAL_ASSERT( + aws_byte_buf_append_and_update(&storage->operation_data, &response_path.topic) == AWS_OP_SUCCESS); + AWS_FATAL_ASSERT( + aws_byte_buf_append_and_update(&storage->operation_data, &response_path.correlation_token_json_path) == + AWS_OP_SUCCESS); aws_array_list_push_back(&storage->operation_response_paths, &response_path); } @@ -1072,11 +1120,13 @@ int aws_mqtt_request_response_client_submit_request( } struct aws_allocator *allocator = client->allocator; - struct aws_mqtt_rr_client_operation *operation = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_rr_client_operation)); + struct aws_mqtt_rr_client_operation *operation = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_rr_client_operation)); operation->allocator = allocator; operation->type = AWS_MRROT_REQUEST; - s_aws_mqtt_request_operation_storage_init_from_options(&operation->storage.request_storage, allocator, request_options); + s_aws_mqtt_request_operation_storage_init_from_options( + &operation->storage.request_storage, allocator, request_options); s_aws_mqtt_rr_client_operation_init_shared(operation, client); aws_event_loop_schedule_task_now(client->loop, &operation->submit_task); @@ -1084,13 +1134,17 @@ int aws_mqtt_request_response_client_submit_request( return AWS_OP_SUCCESS; } -void s_aws_mqtt_streaming_operation_storage_init_from_options(struct aws_mqtt_streaming_operation_storage *storage, struct aws_allocator *allocator, const struct aws_mqtt_streaming_operation_options *streaming_options) { +void s_aws_mqtt_streaming_operation_storage_init_from_options( + struct aws_mqtt_streaming_operation_storage *storage, + struct aws_allocator *allocator, + const struct aws_mqtt_streaming_operation_options *streaming_options) { size_t bytes_needed = streaming_options->topic_filter.len; storage->options = *streaming_options; aws_byte_buf_init(&storage->operation_data, allocator, bytes_needed); - AWS_FATAL_ASSERT(aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.topic_filter) == AWS_OP_SUCCESS); + AWS_FATAL_ASSERT( + aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.topic_filter) == AWS_OP_SUCCESS); } struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_streaming_operation( @@ -1109,11 +1163,13 @@ struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_str } struct aws_allocator *allocator = client->allocator; - struct aws_mqtt_rr_client_operation *operation = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_rr_client_operation)); + struct aws_mqtt_rr_client_operation *operation = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_rr_client_operation)); operation->allocator = allocator; operation->type = AWS_MRROT_STREAMING; - s_aws_mqtt_streaming_operation_storage_init_from_options(&operation->storage.streaming_storage, allocator, streaming_options); + s_aws_mqtt_streaming_operation_storage_init_from_options( + &operation->storage.streaming_storage, allocator, streaming_options); s_aws_mqtt_rr_client_operation_init_shared(operation, client); aws_event_loop_schedule_task_now(client->loop, &operation->submit_task); @@ -1121,7 +1177,8 @@ struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_str return operation; } -struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_acquire(struct aws_mqtt_rr_client_operation *operation) { +struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_acquire( + struct aws_mqtt_rr_client_operation *operation) { if (operation != NULL) { aws_ref_count_acquire(&operation->ref_count); } @@ -1129,7 +1186,8 @@ struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_acquire(struct return operation; } -struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_release(struct aws_mqtt_rr_client_operation *operation) { +struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_release( + struct aws_mqtt_rr_client_operation *operation) { if (operation != NULL) { aws_ref_count_release(&operation->ref_count); } From 6cc77239b567741da947a19f9f32970934d90c9d Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sat, 9 Mar 2024 10:28:30 -0800 Subject: [PATCH 070/124] Format, unused function --- .../request-response/request_response.h | 1 - .../request_response_client.h | 15 ++++++++----- .../request_response_client.c | 22 ------------------- 3 files changed, 10 insertions(+), 28 deletions(-) diff --git a/include/aws/mqtt/private/request-response/request_response.h b/include/aws/mqtt/private/request-response/request_response.h index ef58ce9d..5d861457 100644 --- a/include/aws/mqtt/private/request-response/request_response.h +++ b/include/aws/mqtt/private/request-response/request_response.h @@ -30,4 +30,3 @@ enum aws_rr_subscription_event_type { }; #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_H */ - diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 236ac093..ff73cd06 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -22,7 +22,8 @@ struct aws_mqtt_request_operation_response_path { struct aws_byte_cursor correlation_token_json_path; }; -typedef void(aws_mqtt_request_operation_completion_fn)(struct aws_byte_cursor *payload, int error_code, void *user_data); +typedef void( + aws_mqtt_request_operation_completion_fn)(struct aws_byte_cursor *payload, int error_code, void *user_data); struct aws_mqtt_request_operation_options { struct aws_byte_cursor subscription_topic_filter; @@ -46,7 +47,10 @@ struct aws_mqtt_request_operation_storage { struct aws_byte_buf operation_data; }; -typedef void(aws_mqtt_streaming_operation_subscription_status_fn)(enum aws_rr_subscription_event_type status, int error_code, void *user_data); +typedef void(aws_mqtt_streaming_operation_subscription_status_fn)( + enum aws_rr_subscription_event_type status, + int error_code, + void *user_data); typedef void(aws_mqtt_streaming_operation_incoming_publish_fn)(struct aws_byte_cursor payload, void *user_data); typedef void(aws_mqtt_streaming_operation_terminated_fn)(void *user_data); @@ -66,7 +70,6 @@ struct aws_mqtt_streaming_operation_storage { struct aws_byte_buf operation_data; }; - typedef void(aws_mqtt_request_response_client_initialized_callback_fn)(void *user_data); typedef void(aws_mqtt_request_response_client_terminated_callback_fn)(void *user_data); @@ -119,9 +122,11 @@ AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_clie struct aws_mqtt_request_response_client *client, const struct aws_mqtt_streaming_operation_options *streaming_options); -AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_acquire(struct aws_mqtt_rr_client_operation *operation); +AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_acquire( + struct aws_mqtt_rr_client_operation *operation); -AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_release(struct aws_mqtt_rr_client_operation *operation); +AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_release( + struct aws_mqtt_rr_client_operation *operation); AWS_EXTERN_C_END diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 9b937c43..9a7af51a 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -442,28 +442,6 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ aws_mqtt_rr_client_operation_release(operation); } -static void s_complete_request_operation_with_success( - struct aws_mqtt_rr_client_operation *operation, - struct aws_byte_cursor payload) { - AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); - - if (operation->state == AWS_MRROS_PENDING_DESTROY) { - return; - } - - aws_mqtt_request_operation_completion_fn *completion_callback = - operation->storage.request_storage.options.completion_callback; - void *user_data = operation->storage.request_storage.options.user_data; - - if (completion_callback != NULL) { - (*completion_callback)(&payload, AWS_ERROR_SUCCESS, user_data); - } - - operation->state = AWS_MRROS_PENDING_DESTROY; - - aws_mqtt_rr_client_operation_release(operation); -} - static void s_streaming_operation_on_client_shutdown(struct aws_mqtt_rr_client_operation *operation, int error_code) { AWS_FATAL_ASSERT(operation->type == AWS_MRROT_STREAMING); AWS_FATAL_ASSERT(error_code != AWS_ERROR_SUCCESS); From c775de970bb88f1178752a67b817ca629f0c3bad Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 12 Mar 2024 11:23:13 -0700 Subject: [PATCH 071/124] Basic operation submission testing --- .../request_response_client.c | 10 + tests/CMakeLists.txt | 11 + .../request_response_client_tests.c | 693 +++++++++++++++++- 3 files changed, 713 insertions(+), 1 deletion(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 9a7af51a..8ca6d88a 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -632,6 +632,8 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie NULL, NULL); + aws_linked_list_init(&rr_client->operation_queue); + aws_task_init( &rr_client->external_shutdown_task, s_mqtt_request_response_client_external_shutdown_task_fn, @@ -840,6 +842,14 @@ static bool s_are_request_operation_options_valid( AWS_BYTE_CURSOR_PRI(path->topic)); return false; } + + if (path->correlation_token_json_path.len == 0) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "(%p) rr client request options - empty correlation token json path", + (void *)client); + return false; + } } if (request_options->correlation_token.len == 0) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4b9141b8..92e89030 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -511,6 +511,17 @@ add_test_case(rrsm_subscription_lost_while_unsubscribing) add_test_case(rrc_mqtt5_create_destroy) add_test_case(rrc_mqtt311_create_destroy) +add_test_case(rrc_submit_request_operation_failure_no_response_paths) +add_test_case(rrc_submit_request_operation_failure_invalid_response_topic) +add_test_case(rrc_submit_request_operation_failure_invalid_response_correlation_token_path) +add_test_case(rrc_submit_request_operation_failure_no_correlation_token) +add_test_case(rrc_submit_request_operation_failure_invalid_publish_topic) +add_test_case(rrc_submit_request_operation_failure_empty_request) +add_test_case(rrc_submit_streaming_operation_failure_invalid_subscription_topic_filter) + +add_test_case(rrc_submit_request_operation_failure_by_shutdown) +add_test_case(rrc_submit_streaming_operation_and_shutdown) + generate_test_driver(${PROJECT_NAME}-tests) set(TEST_PAHO_CLIENT_BINARY_NAME ${PROJECT_NAME}-paho-client) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 633ee42b..99a67e7e 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0. */ +#include #include #include @@ -34,8 +35,333 @@ struct aws_rr_client_test_fixture { bool client_initialized; bool client_destroyed; + + struct aws_hash_table request_response_records; + struct aws_hash_table streaming_records; }; +struct aws_rr_client_fixture_request_response_record { + struct aws_allocator *allocator; + + struct aws_rr_client_test_fixture *fixture; + + struct aws_byte_cursor payload_cursor; + + struct aws_byte_buf payload; + + bool completed; + int error_code; + struct aws_byte_buf response; +}; + +struct aws_rr_client_fixture_request_response_record *s_aws_rr_client_fixture_request_response_record_new( + struct aws_allocator *allocator, + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor request_payload) { + struct aws_rr_client_fixture_request_response_record *record = + aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_client_fixture_request_response_record)); + + record->allocator = allocator; + record->fixture = fixture; + + aws_byte_buf_init_copy_from_cursor(&record->payload, allocator, request_payload); + record->payload_cursor = aws_byte_cursor_from_buf(&record->payload); + + return record; +} + +void s_aws_rr_client_fixture_request_response_record_delete( + struct aws_rr_client_fixture_request_response_record *record) { + aws_byte_buf_clean_up(&record->payload); + aws_byte_buf_clean_up(&record->response); + + aws_mem_release(record->allocator, record); +} + +static void s_aws_rr_client_fixture_request_response_record_hash_destroy(void *element) { + struct aws_rr_client_fixture_request_response_record *record = element; + + s_aws_rr_client_fixture_request_response_record_delete(record); +} + +static void s_rrc_fixture_request_completion_callback( + struct aws_byte_cursor *payload, + int error_code, + void *user_data) { + struct aws_rr_client_fixture_request_response_record *record = user_data; + struct aws_rr_client_test_fixture *fixture = record->fixture; + + aws_mutex_lock(&fixture->lock); + + if (payload != NULL) { + AWS_FATAL_ASSERT(error_code == AWS_ERROR_SUCCESS); + + aws_byte_buf_init_copy_from_cursor(&record->response, fixture->allocator, *payload); + } else { + AWS_FATAL_ASSERT(error_code != AWS_ERROR_SUCCESS); + record->error_code = error_code; + } + + record->completed = true; + + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static struct aws_rr_client_fixture_request_response_record *s_rrc_fixture_add_request_record( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor request_payload) { + struct aws_rr_client_fixture_request_response_record *record = + s_aws_rr_client_fixture_request_response_record_new(fixture->allocator, fixture, request_payload); + + aws_hash_table_put(&fixture->request_response_records, &record->payload_cursor, record, NULL); + + return record; +} + +struct rrc_operation_completion_context { + struct aws_byte_cursor key; + struct aws_rr_client_test_fixture *fixture; +}; + +static bool s_is_request_complete(void *context) { + struct rrc_operation_completion_context *completion_context = context; + + struct aws_hash_element *element = NULL; + aws_hash_table_find(&completion_context->fixture->request_response_records, &completion_context->key, &element); + + AWS_FATAL_ASSERT(element != NULL && element->value != NULL); + + struct aws_rr_client_fixture_request_response_record *record = element->value; + + return record->completed; +} + +static void s_rrc_wait_on_request_completion( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor request_payload) { + struct rrc_operation_completion_context context = { + .key = request_payload, + .fixture = fixture, + }; + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_is_request_complete, &context); + aws_mutex_unlock(&fixture->lock); +} + +static int s_rrc_verify_request_completion( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor request_payload, + int expected_error_code, + struct aws_byte_cursor *expected_response) { + aws_mutex_lock(&fixture->lock); + + struct aws_hash_element *element = NULL; + aws_hash_table_find(&fixture->request_response_records, &request_payload, &element); + + AWS_FATAL_ASSERT(element != NULL && element->value != NULL); + + struct aws_rr_client_fixture_request_response_record *record = element->value; + + ASSERT_INT_EQUALS(expected_error_code, record->error_code); + + if (expected_response != NULL) { + struct aws_byte_cursor actual_payload = aws_byte_cursor_from_buf(&record->response); + ASSERT_TRUE(aws_byte_cursor_eq(expected_response, &actual_payload)); + } else { + ASSERT_INT_EQUALS(0, record->response.len); + } + + aws_mutex_unlock(&fixture->lock); + + return AWS_OP_SUCCESS; +} + +struct aws_rr_client_fixture_streaming_record { + struct aws_allocator *allocator; + + struct aws_rr_client_test_fixture *fixture; + + struct aws_byte_cursor record_key_cursor; + struct aws_byte_buf record_key; + + struct aws_array_list publishes; + struct aws_array_list subscription_events; + + bool terminated; +}; + +struct aws_rr_client_fixture_streaming_record_subscription_event { + enum aws_rr_subscription_event_type status; + int error_code; +}; + +struct aws_rr_client_fixture_streaming_record *s_aws_rr_client_fixture_streaming_record_new( + struct aws_allocator *allocator, + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor record_key) { + struct aws_rr_client_fixture_streaming_record *record = + aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_client_fixture_streaming_record)); + + record->allocator = allocator; + record->fixture = fixture; + + aws_byte_buf_init_copy_from_cursor(&record->record_key, allocator, record_key); + record->record_key_cursor = aws_byte_cursor_from_buf(&record->record_key); + + aws_array_list_init_dynamic(&record->publishes, allocator, 10, sizeof(struct aws_byte_buf)); + aws_array_list_init_dynamic( + &record->subscription_events, + allocator, + 10, + sizeof(struct aws_rr_client_fixture_streaming_record_subscription_event)); + + return record; +} + +void s_aws_rr_client_fixture_streaming_record_delete(struct aws_rr_client_fixture_streaming_record *record) { + aws_byte_buf_clean_up(&record->record_key); + + size_t publish_count = aws_array_list_length(&record->publishes); + for (size_t i = 0; i < publish_count; ++i) { + struct aws_byte_buf publish_payload; + aws_array_list_get_at(&record->publishes, &publish_payload, i); + + aws_byte_buf_clean_up(&publish_payload); + } + + aws_array_list_clean_up(&record->publishes); + aws_array_list_clean_up(&record->subscription_events); + + aws_mem_release(record->allocator, record); +} + +static void s_aws_rr_client_fixture_streaming_record_hash_destroy(void *element) { + struct aws_rr_client_fixture_streaming_record *record = element; + + s_aws_rr_client_fixture_streaming_record_delete(record); +} + +static void s_rrc_fixture_streaming_operation_subscription_status_callback( + enum aws_rr_subscription_event_type status, + int error_code, + void *user_data) { + + struct aws_rr_client_fixture_streaming_record *record = user_data; + struct aws_rr_client_test_fixture *fixture = record->fixture; + + aws_mutex_lock(&fixture->lock); + + struct aws_rr_client_fixture_streaming_record_subscription_event event = { + .status = status, + .error_code = error_code, + }; + aws_array_list_push_back(&record->subscription_events, &event); + + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static void s_rrc_fixture_streaming_operation_incoming_publish_callback( + struct aws_byte_cursor payload, + void *user_data) { + struct aws_rr_client_fixture_streaming_record *record = user_data; + struct aws_rr_client_test_fixture *fixture = record->fixture; + + aws_mutex_lock(&fixture->lock); + + struct aws_byte_buf payload_buffer; + aws_byte_buf_init_copy_from_cursor(&payload_buffer, fixture->allocator, payload); + + aws_array_list_push_back(&record->publishes, &payload_buffer); + + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static void s_rrc_fixture_streaming_operation_terminated_callback(void *user_data) { + struct aws_rr_client_fixture_streaming_record *record = user_data; + struct aws_rr_client_test_fixture *fixture = record->fixture; + + aws_mutex_lock(&fixture->lock); + + record->terminated = true; + + aws_mutex_unlock(&fixture->lock); + aws_condition_variable_notify_all(&fixture->signal); +} + +static struct aws_rr_client_fixture_streaming_record *s_rrc_fixture_add_streaming_record( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor key) { + struct aws_rr_client_fixture_streaming_record *record = + s_aws_rr_client_fixture_streaming_record_new(fixture->allocator, fixture, key); + + aws_hash_table_put(&fixture->streaming_records, &record->record_key, record, NULL); + + return record; +} + +static bool s_is_stream_terminated(void *context) { + struct rrc_operation_completion_context *completion_context = context; + + struct aws_hash_element *element = NULL; + aws_hash_table_find(&completion_context->fixture->streaming_records, &completion_context->key, &element); + + AWS_FATAL_ASSERT(element != NULL && element->value != NULL); + + struct aws_rr_client_fixture_streaming_record *record = element->value; + + return record->terminated; +} + +static void s_rrc_wait_on_streaming_termination( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor key) { + struct rrc_operation_completion_context context = { + .key = key, + .fixture = fixture, + }; + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_is_stream_terminated, &context); + aws_mutex_unlock(&fixture->lock); +} + +static int s_rrc_verify_streaming_record_subscription_events( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor key, + size_t expected_subscription_event_count, + struct aws_rr_client_fixture_streaming_record_subscription_event *expected_subscription_events) { + aws_mutex_lock(&fixture->lock); + + struct aws_hash_element *element = NULL; + aws_hash_table_find(&fixture->streaming_records, &key, &element); + + AWS_FATAL_ASSERT(element != NULL && element->value != NULL); + + struct aws_rr_client_fixture_streaming_record *record = element->value; + + size_t actual_subscription_event_count = aws_array_list_length(&record->subscription_events); + ASSERT_INT_EQUALS(expected_subscription_event_count, actual_subscription_event_count); + + for (size_t i = 0; i < actual_subscription_event_count; ++i) { + struct aws_rr_client_fixture_streaming_record_subscription_event actual_event; + aws_array_list_get_at(&record->subscription_events, &actual_event, i); + + struct aws_rr_client_fixture_streaming_record_subscription_event *expected_event = + &expected_subscription_events[i]; + + ASSERT_INT_EQUALS(expected_event->status, actual_event.status); + ASSERT_INT_EQUALS(expected_event->error_code, actual_event.error_code); + } + + aws_mutex_unlock(&fixture->lock); + + return AWS_OP_SUCCESS; +} + static void s_aws_rr_client_test_fixture_on_initialized(void *user_data) { struct aws_rr_client_test_fixture *fixture = user_data; @@ -80,6 +406,24 @@ static int s_aws_rr_client_test_fixture_init_from_mqtt5( aws_condition_variable_init(&fixture->signal); fixture->test_context = test_context; + aws_hash_table_init( + &fixture->request_response_records, + allocator, + 10, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + s_aws_rr_client_fixture_request_response_record_hash_destroy); + + aws_hash_table_init( + &fixture->streaming_records, + allocator, + 10, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + s_aws_rr_client_fixture_streaming_record_hash_destroy); + if (aws_mqtt5_client_mock_test_fixture_init( &fixture->client_test_fixture.mqtt5_test_fixture, allocator, client_test_fixture_options)) { return AWS_OP_ERR; @@ -123,6 +467,24 @@ static int s_aws_rr_client_test_fixture_init_from_mqtt311( aws_condition_variable_init(&fixture->signal); fixture->test_context = test_context; + aws_hash_table_init( + &fixture->request_response_records, + allocator, + 10, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + s_aws_rr_client_fixture_request_response_record_hash_destroy); + + aws_hash_table_init( + &fixture->streaming_records, + allocator, + 10, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + s_aws_rr_client_fixture_streaming_record_hash_destroy); + aws_test311_setup_mqtt_server_fn(allocator, &fixture->client_test_fixture.mqtt311_test_fixture); struct aws_mqtt_request_response_client_options client_options = { @@ -187,6 +549,9 @@ static void s_aws_rr_client_test_fixture_clean_up(struct aws_rr_client_test_fixt aws_mutex_clean_up(&fixture->lock); aws_condition_variable_clean_up(&fixture->signal); + + aws_hash_table_clean_up(&fixture->request_response_records); + aws_hash_table_clean_up(&fixture->streaming_records); } static int s_rrc_mqtt5_create_destroy_fn(struct aws_allocator *allocator, void *ctx) { @@ -230,4 +595,330 @@ static int s_rrc_mqtt311_create_destroy_fn(struct aws_allocator *allocator, void return AWS_OP_SUCCESS; } -AWS_TEST_CASE(rrc_mqtt311_create_destroy, s_rrc_mqtt311_create_destroy_fn) \ No newline at end of file +AWS_TEST_CASE(rrc_mqtt311_create_destroy, s_rrc_mqtt311_create_destroy_fn) + +static int s_rrc_do_submit_request_operation_failure_test( + struct aws_allocator *allocator, + void (*request_mutator_fn)(struct aws_mqtt_request_operation_options *)) { + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + aws_mqtt5_client_test_init_default_options(&client_test_options); + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { + .client_options = &client_test_options.client_options, + .server_function_table = &client_test_options.server_function_table, + }; + + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_rr_client_test_fixture_init_from_mqtt5(&fixture, allocator, NULL, &client_test_fixture_options, NULL)); + + struct aws_mqtt_request_operation_response_path response_paths[] = { + { + .topic = aws_byte_cursor_from_c_str("response/filter/accepted"), + .correlation_token_json_path = aws_byte_cursor_from_c_str("client_token"), + }, + { + .topic = aws_byte_cursor_from_c_str("response/filter/rejected"), + .correlation_token_json_path = aws_byte_cursor_from_c_str("client_token"), + }, + }; + struct aws_mqtt_request_operation_options good_request = { + .subscription_topic_filter = aws_byte_cursor_from_c_str("response/filter/+"), + .response_paths = response_paths, + .response_path_count = AWS_ARRAY_SIZE(response_paths), + .publish_topic = aws_byte_cursor_from_c_str("get/shadow"), + .serialized_request = aws_byte_cursor_from_c_str("{}"), + .correlation_token = aws_byte_cursor_from_c_str("MyRequest#1"), + }; + ASSERT_SUCCESS(aws_mqtt_request_response_client_submit_request(fixture.client, &good_request)); + + struct aws_mqtt_request_operation_options bad_request = good_request; + (*request_mutator_fn)(&bad_request); + + ASSERT_FAILS(aws_mqtt_request_response_client_submit_request(fixture.client, &bad_request)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static void s_no_response_paths_mutator(struct aws_mqtt_request_operation_options *request_options) { + request_options->response_path_count = 0; + request_options->response_paths = NULL; +} + +static int s_rrc_submit_request_operation_failure_no_response_paths_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_rrc_do_submit_request_operation_failure_test(allocator, s_no_response_paths_mutator); +} + +AWS_TEST_CASE( + rrc_submit_request_operation_failure_no_response_paths, + s_rrc_submit_request_operation_failure_no_response_paths_fn) + +static void s_invalid_response_topic_mutator(struct aws_mqtt_request_operation_options *request_options) { + request_options->response_paths[0].topic = aws_byte_cursor_from_c_str("a/b/#"); +} + +static int s_rrc_submit_request_operation_failure_invalid_response_topic_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_rrc_do_submit_request_operation_failure_test(allocator, s_invalid_response_topic_mutator); +} + +AWS_TEST_CASE( + rrc_submit_request_operation_failure_invalid_response_topic, + s_rrc_submit_request_operation_failure_invalid_response_topic_fn) + +static void s_invalid_response_correlation_token_path_mutator( + struct aws_mqtt_request_operation_options *request_options) { + request_options->response_paths[0].correlation_token_json_path = aws_byte_cursor_from_c_str(""); +} + +static int s_rrc_submit_request_operation_failure_invalid_response_correlation_token_path_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_rrc_do_submit_request_operation_failure_test(allocator, s_invalid_response_correlation_token_path_mutator); +} + +AWS_TEST_CASE( + rrc_submit_request_operation_failure_invalid_response_correlation_token_path, + s_rrc_submit_request_operation_failure_invalid_response_correlation_token_path_fn) + +static void s_no_correlation_token_mutator(struct aws_mqtt_request_operation_options *request_options) { + request_options->correlation_token = aws_byte_cursor_from_c_str(""); +} + +static int s_rrc_submit_request_operation_failure_no_correlation_token_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_rrc_do_submit_request_operation_failure_test(allocator, s_no_correlation_token_mutator); +} + +AWS_TEST_CASE( + rrc_submit_request_operation_failure_no_correlation_token, + s_rrc_submit_request_operation_failure_no_correlation_token_fn) + +static void s_invalid_publish_topic_mutator(struct aws_mqtt_request_operation_options *request_options) { + request_options->publish_topic = aws_byte_cursor_from_c_str("a/b/#"); +} + +static int s_rrc_submit_request_operation_failure_invalid_publish_topic_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_rrc_do_submit_request_operation_failure_test(allocator, s_invalid_publish_topic_mutator); +} + +AWS_TEST_CASE( + rrc_submit_request_operation_failure_invalid_publish_topic, + s_rrc_submit_request_operation_failure_invalid_publish_topic_fn) + +static void s_empty_request_mutator(struct aws_mqtt_request_operation_options *request_options) { + request_options->serialized_request = aws_byte_cursor_from_c_str(""); +} + +static int s_rrc_submit_request_operation_failure_empty_request_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_rrc_do_submit_request_operation_failure_test(allocator, s_empty_request_mutator); +} + +AWS_TEST_CASE( + rrc_submit_request_operation_failure_empty_request, + s_rrc_submit_request_operation_failure_empty_request_fn) + +static int s_rrc_submit_streaming_operation_failure_invalid_subscription_topic_filter_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + aws_mqtt5_client_test_init_default_options(&client_test_options); + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { + .client_options = &client_test_options.client_options, + .server_function_table = &client_test_options.server_function_table, + }; + + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_rr_client_test_fixture_init_from_mqtt5(&fixture, allocator, NULL, &client_test_fixture_options, NULL)); + + struct aws_mqtt_streaming_operation_options good_options = { + .topic_filter = aws_byte_cursor_from_c_str("a/b"), + }; + + struct aws_mqtt_rr_client_operation *good_operation = + aws_mqtt_request_response_client_create_streaming_operation(fixture.client, &good_options); + ASSERT_NOT_NULL(good_operation); + + aws_mqtt_rr_client_operation_release(good_operation); + + struct aws_mqtt_streaming_operation_options bad_options = good_options; + bad_options.topic_filter = aws_byte_cursor_from_c_str(""); + + struct aws_mqtt_rr_client_operation *bad_operation = + aws_mqtt_request_response_client_create_streaming_operation(fixture.client, &bad_options); + ASSERT_NULL(bad_operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_submit_streaming_operation_failure_invalid_subscription_topic_filter, + s_rrc_submit_streaming_operation_failure_invalid_subscription_topic_filter_fn) + +static int s_do_rrc_single_request_operation_test_fn( + struct aws_allocator *allocator, + struct aws_mqtt_request_operation_options *request_options, + int expected_error_code, + struct aws_byte_cursor *expected_payload, + bool shutdown_after_submit) { + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + aws_mqtt5_client_test_init_default_options(&client_test_options); + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { + .client_options = &client_test_options.client_options, + .server_function_table = &client_test_options.server_function_table, + }; + + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_rr_client_test_fixture_init_from_mqtt5(&fixture, allocator, NULL, &client_test_fixture_options, NULL)); + + struct aws_rr_client_fixture_request_response_record *record = + s_rrc_fixture_add_request_record(&fixture, request_options->serialized_request); + + request_options->completion_callback = s_rrc_fixture_request_completion_callback; + request_options->user_data = record; + + ASSERT_SUCCESS(aws_mqtt_request_response_client_submit_request(fixture.client, request_options)); + + if (shutdown_after_submit) { + aws_mqtt_request_response_client_release(fixture.client); + fixture.client = NULL; + } + + s_rrc_wait_on_request_completion(&fixture, request_options->serialized_request); + + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, request_options->serialized_request, expected_error_code, expected_payload)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_rrc_submit_request_operation_failure_by_shutdown_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_mqtt_request_operation_response_path response_paths[] = { + { + .topic = aws_byte_cursor_from_c_str("response/filter/accepted"), + .correlation_token_json_path = aws_byte_cursor_from_c_str("client_token"), + }, + }; + + struct aws_mqtt_request_operation_options request = { + .subscription_topic_filter = aws_byte_cursor_from_c_str("response/filter/+"), + .response_paths = response_paths, + .response_path_count = AWS_ARRAY_SIZE(response_paths), + .publish_topic = aws_byte_cursor_from_c_str("get/shadow"), + .serialized_request = aws_byte_cursor_from_c_str("request1"), + .correlation_token = aws_byte_cursor_from_c_str("MyRequest#1"), + }; + + return s_do_rrc_single_request_operation_test_fn( + allocator, &request, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, NULL, true); +} + +AWS_TEST_CASE(rrc_submit_request_operation_failure_by_shutdown, s_rrc_submit_request_operation_failure_by_shutdown_fn) + +static int s_do_rrc_single_streaming_operation_test_fn( + struct aws_allocator *allocator, + struct aws_mqtt_streaming_operation_options *streaming_options, + size_t expected_subscription_event_count, + struct aws_rr_client_fixture_streaming_record_subscription_event *expected_subscription_events, + bool shutdown_after_submit) { + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + aws_mqtt5_client_test_init_default_options(&client_test_options); + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { + .client_options = &client_test_options.client_options, + .server_function_table = &client_test_options.server_function_table, + }; + + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS( + s_aws_rr_client_test_fixture_init_from_mqtt5(&fixture, allocator, NULL, &client_test_fixture_options, NULL)); + + struct aws_byte_cursor streaming_id = aws_byte_cursor_from_c_str("streaming1"); + struct aws_rr_client_fixture_streaming_record *record = s_rrc_fixture_add_streaming_record(&fixture, streaming_id); + + streaming_options->incoming_publish_callback = s_rrc_fixture_streaming_operation_incoming_publish_callback; + streaming_options->subscription_status_callback = s_rrc_fixture_streaming_operation_subscription_status_callback; + streaming_options->terminated_callback = s_rrc_fixture_streaming_operation_terminated_callback; + streaming_options->user_data = record; + + struct aws_mqtt_rr_client_operation *streaming_operation = + aws_mqtt_request_response_client_create_streaming_operation(fixture.client, streaming_options); + ASSERT_NOT_NULL(streaming_operation); + + if (shutdown_after_submit) { + aws_mqtt_request_response_client_release(fixture.client); + fixture.client = NULL; + aws_mqtt_rr_client_operation_release(streaming_operation); + } + + s_rrc_wait_on_streaming_termination(&fixture, streaming_id); + + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, streaming_id, expected_subscription_event_count, expected_subscription_events)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_rrc_submit_streaming_operation_and_shutdown_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + .error_code = AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, + }, + }; + + struct aws_mqtt_streaming_operation_options streaming_options = { + .topic_filter = aws_byte_cursor_from_c_str("derp/filter"), + }; + + return s_do_rrc_single_streaming_operation_test_fn( + allocator, &streaming_options, AWS_ARRAY_SIZE(expected_events), expected_events, true); +} + +AWS_TEST_CASE(rrc_submit_streaming_operation_and_shutdown, s_rrc_submit_streaming_operation_and_shutdown_fn) From bd04ad5fa01c3d30d56bbf665635c3bb7a89f2cb Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 12 Mar 2024 12:01:12 -0700 Subject: [PATCH 072/124] PR feedback --- source/request-response/request_response_client.c | 11 +++++++++++ .../request_response_client_tests.c | 13 +++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 7b43f452..a692e813 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -218,6 +218,9 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie * initialize can't fail by checking its options for validity now. */ if (!aws_rr_subscription_manager_are_options_valid(&sm_options)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, "(static) request response client creation failed - invalid client options"); + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); return NULL; } @@ -311,6 +314,10 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_fr struct aws_mqtt_request_response_client *rr_client = s_aws_mqtt_request_response_client_new(allocator, options, aws_mqtt_client_connection_get_event_loop(client)); + if (rr_client == NULL) { + return NULL; + } + struct aws_mqtt_protocol_adapter_options adapter_options = { .subscription_event_callback = s_aws_rr_client_protocol_adapter_subscription_event_callback, .incoming_publish_callback = s_aws_rr_client_protocol_adapter_incoming_publish_callback, @@ -344,6 +351,10 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_new_fr struct aws_mqtt_request_response_client *rr_client = s_aws_mqtt_request_response_client_new(allocator, options, client->loop); + if (rr_client == NULL) { + return NULL; + } + struct aws_mqtt_protocol_adapter_options adapter_options = { .subscription_event_callback = s_aws_rr_client_protocol_adapter_subscription_event_callback, .incoming_publish_callback = s_aws_rr_client_protocol_adapter_incoming_publish_callback, diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 633ee42b..226efdf0 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -19,7 +19,7 @@ enum rr_test_client_protocol { struct aws_rr_client_test_fixture { struct aws_allocator *allocator; - struct aws_mqtt_request_response_client *client; + struct aws_mqtt_request_response_client *rr_client; enum rr_test_client_protocol test_protocol; union { @@ -98,9 +98,9 @@ static int s_aws_rr_client_test_fixture_init_from_mqtt5( client_options.terminated_callback = s_aws_rr_client_test_fixture_on_terminated; client_options.user_data = fixture; - fixture->client = aws_mqtt_request_response_client_new_from_mqtt5_client( + fixture->rr_client = aws_mqtt_request_response_client_new_from_mqtt5_client( allocator, fixture->client_test_fixture.mqtt5_test_fixture.client, &client_options); - AWS_FATAL_ASSERT(fixture->client != NULL); + AWS_FATAL_ASSERT(fixture->rr_client != NULL); aws_mqtt5_client_start(fixture->client_test_fixture.mqtt5_test_fixture.client); @@ -140,8 +140,9 @@ static int s_aws_rr_client_test_fixture_init_from_mqtt311( struct aws_mqtt_client_connection *mqtt_client = fixture->client_test_fixture.mqtt311_test_fixture.mqtt_connection; - fixture->client = aws_mqtt_request_response_client_new_from_mqtt311_client(allocator, mqtt_client, &client_options); - AWS_FATAL_ASSERT(fixture->client != NULL); + fixture->rr_client = + aws_mqtt_request_response_client_new_from_mqtt311_client(allocator, mqtt_client, &client_options); + AWS_FATAL_ASSERT(fixture->rr_client != NULL); struct aws_mqtt_connection_options connection_options = { .user_data = &fixture->client_test_fixture.mqtt311_test_fixture, @@ -169,7 +170,7 @@ static bool s_rr_client_test_fixture_terminated(void *context) { } static void s_aws_rr_client_test_fixture_clean_up(struct aws_rr_client_test_fixture *fixture) { - aws_mqtt_request_response_client_release(fixture->client); + aws_mqtt_request_response_client_release(fixture->rr_client); aws_mutex_lock(&fixture->lock); aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_rr_client_test_fixture_terminated, fixture); From 15e8a32374b2f2e5b2535e09f3f33f38d7b8a822 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 12 Mar 2024 12:13:44 -0700 Subject: [PATCH 073/124] Merge from previous PR --- .../request_response_client_tests.c | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 96c4b346..4b2ca361 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -633,12 +633,12 @@ static int s_rrc_do_submit_request_operation_failure_test( .serialized_request = aws_byte_cursor_from_c_str("{}"), .correlation_token = aws_byte_cursor_from_c_str("MyRequest#1"), }; - ASSERT_SUCCESS(aws_mqtt_request_response_client_submit_request(fixture.client, &good_request)); + ASSERT_SUCCESS(aws_mqtt_request_response_client_submit_request(fixture.rr_client, &good_request)); struct aws_mqtt_request_operation_options bad_request = good_request; (*request_mutator_fn)(&bad_request); - ASSERT_FAILS(aws_mqtt_request_response_client_submit_request(fixture.client, &bad_request)); + ASSERT_FAILS(aws_mqtt_request_response_client_submit_request(fixture.rr_client, &bad_request)); s_aws_rr_client_test_fixture_clean_up(&fixture); @@ -761,7 +761,7 @@ static int s_rrc_submit_streaming_operation_failure_invalid_subscription_topic_f }; struct aws_mqtt_rr_client_operation *good_operation = - aws_mqtt_request_response_client_create_streaming_operation(fixture.client, &good_options); + aws_mqtt_request_response_client_create_streaming_operation(fixture.rr_client, &good_options); ASSERT_NOT_NULL(good_operation); aws_mqtt_rr_client_operation_release(good_operation); @@ -770,7 +770,7 @@ static int s_rrc_submit_streaming_operation_failure_invalid_subscription_topic_f bad_options.topic_filter = aws_byte_cursor_from_c_str(""); struct aws_mqtt_rr_client_operation *bad_operation = - aws_mqtt_request_response_client_create_streaming_operation(fixture.client, &bad_options); + aws_mqtt_request_response_client_create_streaming_operation(fixture.rr_client, &bad_options); ASSERT_NULL(bad_operation); s_aws_rr_client_test_fixture_clean_up(&fixture); @@ -810,11 +810,11 @@ static int s_do_rrc_single_request_operation_test_fn( request_options->completion_callback = s_rrc_fixture_request_completion_callback; request_options->user_data = record; - ASSERT_SUCCESS(aws_mqtt_request_response_client_submit_request(fixture.client, request_options)); + ASSERT_SUCCESS(aws_mqtt_request_response_client_submit_request(fixture.rr_client, request_options)); if (shutdown_after_submit) { - aws_mqtt_request_response_client_release(fixture.client); - fixture.client = NULL; + aws_mqtt_request_response_client_release(fixture.rr_client); + fixture.rr_client = NULL; } s_rrc_wait_on_request_completion(&fixture, request_options->serialized_request); @@ -883,12 +883,12 @@ static int s_do_rrc_single_streaming_operation_test_fn( streaming_options->user_data = record; struct aws_mqtt_rr_client_operation *streaming_operation = - aws_mqtt_request_response_client_create_streaming_operation(fixture.client, streaming_options); + aws_mqtt_request_response_client_create_streaming_operation(fixture.rr_client, streaming_options); ASSERT_NOT_NULL(streaming_operation); if (shutdown_after_submit) { - aws_mqtt_request_response_client_release(fixture.client); - fixture.client = NULL; + aws_mqtt_request_response_client_release(fixture.rr_client); + fixture.rr_client = NULL; aws_mqtt_rr_client_operation_release(streaming_operation); } From 8de5a0060e593ef04af615b3036d46a950768c84 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 13 Mar 2024 15:46:16 -0700 Subject: [PATCH 074/124] Update subscription manager and associated systems to support durable/indefined streaming operations --- .../request-response/request_response.h | 39 +- source/request-response/protocol_adapter.c | 30 + .../request_response_client.c | 5 +- .../request-response/subscription_manager.c | 181 ++++- tests/CMakeLists.txt | 36 +- .../request-response/protocol_adapter_tests.c | 4 +- .../request_response_client_tests.c | 2 +- .../subscription_manager_tests.c | 638 +++++++++++++++--- 8 files changed, 798 insertions(+), 137 deletions(-) diff --git a/include/aws/mqtt/private/request-response/request_response.h b/include/aws/mqtt/private/request-response/request_response.h index 5d861457..78e7819d 100644 --- a/include/aws/mqtt/private/request-response/request_response.h +++ b/include/aws/mqtt/private/request-response/request_response.h @@ -9,24 +9,49 @@ #include /* - * Describes a change to the state of a request-response client subscription + * Describes a change to the state of a request operation subscription */ enum aws_rr_subscription_event_type { /* - * A subscribe succeeded + * A request subscription subscribe succeeded */ - ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, /* - * A subscribe failed + * A request subscription subscribe failed */ - ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE, /* - * A previously successful subscription has ended (generally due to a failure to resume a session) + * A previously successful request subscription has ended. + * + * Under normal circumstances this can happen when + * + * (1) failure to rejoin a session + * (2) a successful unsubscribe when the subscription is no longer needed */ - ARRSET_SUBSCRIPTION_ENDED + ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED, + + /* + * A streaming subscription subscribe succeeded + */ + ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, + + /* + * The protocol client failed to rejoin a session containing a previously-established streaming subscription + */ + ARRSET_STREAMING_SUBSCRIPTION_LOST, + + /* + * A streaming subscription subscribe attempt resulted in an error or reason code that the client has determined + * will result in indefinite failures to subscribe. In this case, we stop attempting to resubscribe. + * + * Situations that can lead to this: + * (1) Permission failures + * (2) Invalid topic filter + */ + ARRSET_STREAMING_SUBSCRIPTION_HALTED }; #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_H */ diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index a59f09dc..fefa7000 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -145,6 +145,7 @@ static void s_protocol_adapter_311_subscribe_completion( .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), .event_type = AWS_PASET_SUBSCRIBE, .error_code = error_code, + .retryable = true, }; (*adapter->config.subscription_event_callback)(&subscribe_event, adapter->config.user_data); @@ -487,6 +488,28 @@ static void s_aws_mqtt_protocol_adapter_5_destroy(void *impl) { /* Subscribe */ +static bool s_is_retryable_subscribe(enum aws_mqtt5_suback_reason_code reason_code, int error_code) { + if (error_code == AWS_ERROR_MQTT5_PACKET_VALIDATION || error_code == AWS_ERROR_MQTT5_SUBSCRIBE_OPTIONS_VALIDATION) { + return false; + } else if (error_code != AWS_ERROR_SUCCESS) { + return true; + } + + switch (reason_code) { + case AWS_MQTT5_SARC_GRANTED_QOS_0: + case AWS_MQTT5_SARC_GRANTED_QOS_1: + case AWS_MQTT5_SARC_GRANTED_QOS_2: + case AWS_MQTT5_SARC_UNSPECIFIED_ERROR: + case AWS_MQTT5_SARC_PACKET_IDENTIFIER_IN_USE: + case AWS_MQTT5_SARC_IMPLEMENTATION_SPECIFIC_ERROR: + case AWS_MQTT5_SARC_QUOTA_EXCEEDED: + return true; + + default: + return false; + } +} + static void s_protocol_adapter_5_subscribe_completion( const struct aws_mqtt5_packet_suback_view *suback, int error_code, @@ -498,6 +521,12 @@ static void s_protocol_adapter_5_subscribe_completion( goto done; } + enum aws_mqtt5_suback_reason_code reason_code = AWS_MQTT5_SARC_GRANTED_QOS_0; + if (suback != NULL && suback->reason_code_count > 0) { + reason_code = suback->reason_codes[0]; + } + bool is_retryable = s_is_retryable_subscribe(reason_code, error_code); + if (error_code == AWS_ERROR_SUCCESS) { if (suback == NULL || suback->reason_code_count != 1 || suback->reason_codes[0] >= 128) { error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE; @@ -508,6 +537,7 @@ static void s_protocol_adapter_5_subscribe_completion( .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), .event_type = AWS_PASET_SUBSCRIBE, .error_code = error_code, + .retryable = is_retryable, }; (*adapter->config.subscription_event_callback)(&subscribe_event, adapter->config.user_data); diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 1f6b255b..110199d6 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -454,10 +454,7 @@ static void s_streaming_operation_on_client_shutdown(struct aws_mqtt_rr_client_o operation->storage.streaming_storage.options.subscription_status_callback; void *user_data = operation->storage.streaming_storage.options.user_data; if (subscription_status_callback != NULL) { - enum aws_rr_subscription_event_type status_type = (operation->state == AWS_MRROS_SUBSCRIBED) - ? ARRSET_SUBSCRIPTION_ENDED - : ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE; - (*subscription_status_callback)(status_type, error_code, user_data); + (*subscription_status_callback)(ARRSET_STREAMING_SUBSCRIPTION_HALTED, error_code, user_data); } } diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 402814c3..98a7206c 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -64,6 +64,16 @@ struct aws_rr_subscription_record { enum aws_rr_subscription_pending_action_type pending_action; enum aws_rr_subscription_type type; + + /* + * A poisoned record represents a subscription that we will never try to subscribe to because a previous + * attempt resulted in a failure that we judge to be "terminal." Terminal failures include permission failures + * and validation failures. To remove a poisoned record, all listeners must be removed. For request-response + * operations this will happen naturally. For streaming operations, the operation must be closed by the user (in + * response to the user-facing event we emit on the streaming operation when the failure that poisons the + * record occurs). + */ + bool poisoned; }; static void s_aws_rr_subscription_record_log_invariant_violations(const struct aws_rr_subscription_record *record) { @@ -162,7 +172,7 @@ static void s_subscription_record_unsubscribe( record->pending_action = ARRSPAT_UNSUBSCRIBING; } -/* Only called when shutting down the request-response client */ +/* Only called by the request-response client when shutting down */ static int s_rr_subscription_clean_up_foreach_wrap(void *context, struct aws_hash_element *elem) { struct aws_rr_subscription_manager *manager = context; struct aws_rr_subscription_record *subscription = elem->value; @@ -297,26 +307,55 @@ static void s_cull_unused_subscriptions(struct aws_rr_subscription_manager *mana aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_cull_unused_subscriptions_wrapper, manager); } -static const char *s_request_response_subscription_event_type_to_c_str(enum aws_rr_subscription_event_type type) { +static const char *s_rr_subscription_event_type_to_c_str(enum aws_rr_subscription_event_type type) { switch (type) { - case ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS: - return "SubscriptionSubscribeSuccess"; + case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS: + return "RequestSubscribeSuccess"; + + case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE: + return "RequestSubscribeFailure"; + + case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED: + return "RequestSubscriptionEnded"; + + case ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED: + return "StreamingSubscriptionEstablished"; - case ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE: - return "SubscriptionSubscribeFailure"; + case ARRSET_STREAMING_SUBSCRIPTION_LOST: + return "StreamingSubscriptionLost"; - case ARRSET_SUBSCRIPTION_ENDED: - return "SubscriptionEnded"; + case ARRSET_STREAMING_SUBSCRIPTION_HALTED: + return "StreamingSubscriptionHalted"; } return "Unknown"; } +static bool s_subscription_type_matches_event_type( + enum aws_rr_subscription_type subscription_type, + enum aws_rr_subscription_event_type event_type) { + switch (event_type) { + case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS: + case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE: + case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED: + return subscription_type == ARRST_REQUEST_RESPONSE; + + case ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED: + case ARRSET_STREAMING_SUBSCRIPTION_LOST: + case ARRSET_STREAMING_SUBSCRIPTION_HALTED: + return subscription_type == ARRST_EVENT_STREAM; + } + + return false; +} + static void s_emit_subscription_event( const struct aws_rr_subscription_manager *manager, const struct aws_rr_subscription_record *record, enum aws_rr_subscription_event_type type) { + AWS_FATAL_ASSERT(s_subscription_type_matches_event_type(record->type, type)); + for (struct aws_hash_iter iter = aws_hash_iter_begin(&record->listeners); !aws_hash_iter_done(&iter); aws_hash_iter_next(&iter)) { @@ -334,7 +373,7 @@ static void s_emit_subscription_event( "request-response subscription manager - subscription event for ('" PRInSTR "'), type: %s, operation: %" PRIu64 "", AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor), - s_request_response_subscription_event_type_to_c_str(type), + s_rr_subscription_event_type_to_c_str(type), listener->operation_id); } } @@ -344,6 +383,10 @@ static int s_rr_activate_idle_subscription( struct aws_rr_subscription_record *record) { int result = AWS_OP_SUCCESS; + if (record->poisoned) { + return AWS_OP_SUCCESS; + } + if (manager->is_protocol_client_connected && aws_hash_table_get_entry_count(&record->listeners) > 0) { if (record->status == ARRSST_NOT_SUBSCRIBED && record->pending_action == ARRSPAT_NOTHING) { struct aws_protocol_adapter_subscribe_options subscribe_options = { @@ -368,7 +411,12 @@ static int s_rr_activate_idle_subscription( error_code, aws_error_debug_str(error_code)); - s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE); + if (record->type == ARRST_REQUEST_RESPONSE) { + s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE); + } else { + record->poisoned = true; + s_emit_subscription_event(manager, record, ARRSET_STREAMING_SUBSCRIPTION_HALTED); + } } } } @@ -434,6 +482,16 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su return AASRT_FAILURE; } + if (existing_record->poisoned) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 + " failed - existing subscription is poisoned and has not been released", + AWS_BYTE_CURSOR_PRI(options->topic_filter), + options->operation_id); + return AASRT_FAILURE; + } + s_aws_rr_subscription_record_log_invariant_violations(existing_record); // for simplicity, we require unsubscribes to complete before re-subscribing @@ -489,42 +547,84 @@ void aws_rr_subscription_manager_release_subscription( s_remove_listener_from_subscription_record(manager, options->topic_filter, options->operation_id); } -void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( +static void s_handle_protocol_adapter_request_subscription_event( struct aws_rr_subscription_manager *manager, + struct aws_rr_subscription_record *record, const struct aws_protocol_adapter_subscription_event *event) { - struct aws_rr_subscription_record *record = s_get_subscription_record(manager, event->topic_filter); - if (record == NULL) { - return; - } + if (event->event_type == AWS_PASET_SUBSCRIBE) { + AWS_FATAL_ASSERT(record->pending_action == ARRSPAT_SUBSCRIBING); + record->pending_action = ARRSPAT_NOTHING; - AWS_LOGF_DEBUG( - AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - received a protocol adapter subscription event for ('" PRInSTR - "'), type %s, error_code %d(%s)", - AWS_BYTE_CURSOR_PRI(event->topic_filter), - aws_protocol_adapter_subscription_event_type_to_c_str(event->event_type), - event->error_code, - aws_error_debug_str(event->error_code)); + if (event->error_code == AWS_ERROR_SUCCESS) { + record->status = ARRSST_SUBSCRIBED; + s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS); + } else { + s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE); + } + } else { + AWS_FATAL_ASSERT(event->event_type == AWS_PASET_UNSUBSCRIBE); + AWS_FATAL_ASSERT(record->pending_action == ARRSPAT_UNSUBSCRIBING); + record->pending_action = ARRSPAT_NOTHING; + if (event->error_code == AWS_ERROR_SUCCESS) { + record->status = ARRSST_NOT_SUBSCRIBED; + s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED); + } + } +} + +static void s_handle_protocol_adapter_streaming_subscription_event( + struct aws_rr_subscription_manager *manager, + struct aws_rr_subscription_record *record, + const struct aws_protocol_adapter_subscription_event *event) { if (event->event_type == AWS_PASET_SUBSCRIBE) { AWS_FATAL_ASSERT(record->pending_action == ARRSPAT_SUBSCRIBING); + record->pending_action = ARRSPAT_NOTHING; if (event->error_code == AWS_ERROR_SUCCESS) { record->status = ARRSST_SUBSCRIBED; - s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS); + s_emit_subscription_event(manager, record, ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED); } else { - s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE); + if (event->retryable) { + s_rr_activate_idle_subscription(manager, record); + } else { + record->poisoned = true; + s_emit_subscription_event(manager, record, ARRSET_STREAMING_SUBSCRIPTION_HALTED); + } } - } else if (event->event_type == AWS_PASET_UNSUBSCRIBE) { + } else { + AWS_FATAL_ASSERT(event->event_type == AWS_PASET_UNSUBSCRIBE); AWS_FATAL_ASSERT(record->pending_action == ARRSPAT_UNSUBSCRIBING); + record->pending_action = ARRSPAT_NOTHING; if (event->error_code == AWS_ERROR_SUCCESS) { record->status = ARRSST_NOT_SUBSCRIBED; - s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_ENDED); } } +} + +void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( + struct aws_rr_subscription_manager *manager, + const struct aws_protocol_adapter_subscription_event *event) { + struct aws_rr_subscription_record *record = s_get_subscription_record(manager, event->topic_filter); + if (record == NULL) { + return; + } - record->pending_action = ARRSPAT_NOTHING; + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - received a protocol adapter subscription event for ('" PRInSTR + "'), type %s, error_code %d(%s)", + AWS_BYTE_CURSOR_PRI(event->topic_filter), + aws_protocol_adapter_subscription_event_type_to_c_str(event->event_type), + event->error_code, + aws_error_debug_str(event->error_code)); + + if (record->type == ARRST_REQUEST_RESPONSE) { + s_handle_protocol_adapter_request_subscription_event(manager, record, event); + } else { + s_handle_protocol_adapter_streaming_subscription_event(manager, record, event); + } s_aws_rr_subscription_record_log_invariant_violations(record); } @@ -550,19 +650,36 @@ static int s_apply_session_lost_wrapper(void *context, struct aws_hash_element * if (record->status == ARRSST_SUBSCRIBED) { record->status = ARRSST_NOT_SUBSCRIBED; - s_emit_subscription_event(manager, record, ARRSET_SUBSCRIPTION_ENDED); - if (record->pending_action != ARRSPAT_UNSUBSCRIBING) { - s_aws_rr_subscription_record_destroy(record); - return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; + if (record->type == ARRST_REQUEST_RESPONSE) { + s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED); + + if (record->pending_action != ARRSPAT_UNSUBSCRIBING) { + s_aws_rr_subscription_record_destroy(record); + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE | AWS_COMMON_HASH_TABLE_ITER_DELETE; + } + } else { + s_emit_subscription_event(manager, record, ARRSET_STREAMING_SUBSCRIPTION_LOST); } } return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; } +static int s_apply_streaming_resubscribe_wrapper(void *context, struct aws_hash_element *elem) { + struct aws_rr_subscription_record *record = elem->value; + struct aws_rr_subscription_manager *manager = context; + + if (record->type == ARRST_EVENT_STREAM) { + s_rr_activate_idle_subscription(manager, record); + } + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; +} + static void s_apply_session_lost(struct aws_rr_subscription_manager *manager) { aws_hash_table_foreach(&manager->subscriptions, s_apply_session_lost_wrapper, manager); + aws_hash_table_foreach(&manager->subscriptions, s_apply_streaming_resubscribe_wrapper, manager); } void aws_rr_subscription_manager_on_protocol_adapter_connection_event( diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 92e89030..3ba0bb96 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -492,20 +492,32 @@ add_test_case(rrsm_acquire_blocked_eventstream) add_test_case(rrsm_acquire_no_capacity_max1) add_test_case(rrsm_acquire_no_capacity_too_many_event_stream) add_test_case(rrsm_acquire_failure_mixed_subscription_types) -add_test_case(rrsm_acquire_failure_subscribe_sync_failure) -add_test_case(rrsm_acquire_subscribe_failure_event) -add_test_case(rrsm_release_unsubscribes) +add_test_case(rrsm_acquire_failure_poisoned) +add_test_case(rrsm_release_unsubscribes_request) +add_test_case(rrsm_release_unsubscribes_streaming) add_test_case(rrsm_release_unsubscribe_success_clears_space) add_test_case(rrsm_release_unsubscribe_failure_blocked) -add_test_case(rrsm_offline_acquire_online_success) -add_test_case(rrsm_offline_acquire_online_failure) -add_test_case(rrsm_offline_acquire_release_online) -add_test_case(rrsm_acquire_success_offline_release_acquire2_no_unsubscribe) -add_test_case(rrsm_acquire_success_clean_up_unsubscribe_override) -add_test_case(rrsm_acquire_pending_clean_up_unsubscribe_override) -add_test_case(rrsm_offline_acquire_pending_clean_up_unsubscribe_override) -add_test_case(rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire) -add_test_case(rrsm_subscription_lost_while_unsubscribing) +add_test_case(rrsm_acquire_failure_subscribe_sync_failure_request) +add_test_case(rrsm_acquire_failure_subscribe_sync_failure_streaming) +add_test_case(rrsm_acquire_request_subscribe_failure_event) +add_test_case(rrsm_acquire_streaming_subscribe_failure_retryable_resubscribe) +add_test_case(rrsm_offline_acquire_request_online_success) +add_test_case(rrsm_offline_acquire_request_online_failure) +add_test_case(rrsm_offline_acquire_streaming_online_success) +add_test_case(rrsm_offline_acquire_streaming_online_failure) +add_test_case(rrsm_offline_acquire_release_request_online) +add_test_case(rrsm_offline_acquire_release_streaming_online) +add_test_case(rrsm_acquire_request_success_offline_release_acquire2_no_unsubscribe) +add_test_case(rrsm_acquire_streaming_success_offline_release_acquire2_no_unsubscribe) +add_test_case(rrsm_acquire_request_success_clean_up_unsubscribe_override) +add_test_case(rrsm_acquire_streaming_success_clean_up_unsubscribe_override) +add_test_case(rrsm_acquire_request_pending_clean_up_unsubscribe_override) +add_test_case(rrsm_acquire_streaming_pending_clean_up_unsubscribe_override) +add_test_case(rrsm_offline_acquire_request_pending_clean_up_unsubscribe_override) +add_test_case(rrsm_offline_acquire_streaming_pending_clean_up_unsubscribe_override) +add_test_case(rrsm_acquire_request_success_offline_online_no_session_subscription_ended_can_reacquire) +add_test_case(rrsm_request_subscription_ended_while_unsubscribing) +add_test_case(rrsm_streaming_subscription_lost_resubscribe_on_no_session) # "rrc" = request response client add_test_case(rrc_mqtt5_create_destroy) diff --git a/tests/request-response/protocol_adapter_tests.c b/tests/request-response/protocol_adapter_tests.c index ec2d6a1c..5b5bce58 100644 --- a/tests/request-response/protocol_adapter_tests.c +++ b/tests/request-response/protocol_adapter_tests.c @@ -585,7 +585,7 @@ static int s_do_request_response_mqtt5_protocol_adapter_subscribe_test( AWS_PASET_SUBSCRIBE, aws_byte_cursor_from_c_str("hello/world"), expected_error_code, - false); + test_type != PAOTT_FAILURE_REASON_CODE_NOT_RETRYABLE); struct aws_protocol_adapter_subscribe_options subscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), @@ -1255,7 +1255,7 @@ static int s_do_request_response_mqtt311_protocol_adapter_subscribe_test( AWS_PASET_SUBSCRIBE, aws_byte_cursor_from_c_str("hello/world"), expected_error_code, - false); + true); struct aws_protocol_adapter_subscribe_options subscribe_options = { .topic_filter = aws_byte_cursor_from_buf(&expected_outcome.topic_filter), diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 4b2ca361..a7586df1 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -909,7 +909,7 @@ static int s_rrc_submit_streaming_operation_and_shutdown_fn(struct aws_allocator struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { { - .status = ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + .status = ARRSET_STREAMING_SUBSCRIPTION_HALTED, .error_code = AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, }, }; diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 2ffd58cf..4abd726c 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -59,6 +59,7 @@ struct aws_mqtt_protocol_adapter_mock_impl { struct aws_array_list api_records; bool is_connected; + size_t subscribe_count; }; static void s_aws_mqtt_protocol_adapter_mock_destroy(void *impl) { @@ -523,8 +524,8 @@ static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocato AWS_TEST_CASE(rrsm_acquire_existing_subscribing, s_rrsm_acquire_existing_subscribing_fn) /* - * Verify: Acquiring an existing, completed subscription does not trigger a protocol client subscribe and returns - * SUBSCRIBED + * Verify: Acquiring an existing, completed request subscription does not trigger a protocol client subscribe and + * returns SUBSCRIBED. Verify request and streaming subscription events are emitted. */ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -574,12 +575,12 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator struct aws_subscription_status_record expected_subscription_events[] = { { - .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), .operation_id = 1, }, { - .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), .operation_id = 2, }}; @@ -818,10 +819,61 @@ static int s_rrsm_acquire_failure_mixed_subscription_types_fn(struct aws_allocat AWS_TEST_CASE(rrsm_acquire_failure_mixed_subscription_types, s_rrsm_acquire_failure_mixed_subscription_types_fn) /* - * Verify: A subscription that resolves successfully invokes callbacks for every operation listener; releasing + * Verify: Acquiring an existing, completed request subscription does not trigger a protocol client subscribe and + * returns SUBSCRIBED. Verify request and streaming subscription events are emitted. + */ +static int s_rrsm_acquire_failure_poisoned_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + struct aws_protocol_adapter_subscription_event unretryable_failure_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, + .retryable = false, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &unretryable_failure_event); + + struct aws_subscription_status_record expected_subscription_events[] = {{ + .type = ARRSET_STREAMING_SUBSCRIPTION_HALTED, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), + .operation_id = 1, + }}; + ASSERT_TRUE(s_contains_subscription_event_sequential_records(&fixture, 1, expected_subscription_events)); + + struct aws_rr_acquire_subscription_options reacquire1_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .operation_id = 3, + }; + ASSERT_INT_EQUALS(AASRT_FAILURE, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire1_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_failure_poisoned, s_rrsm_acquire_failure_poisoned_fn) + +/* + * Verify: A request subscription that resolves successfully invokes callbacks for every operation listener; releasing * both references and calling a new acquire will trigger an unsubscribe of the first subscription */ -static int s_rrsm_release_unsubscribes_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_release_unsubscribes_request_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_mqtt_library_init(allocator); @@ -855,12 +907,12 @@ static int s_rrsm_release_unsubscribes_fn(struct aws_allocator *allocator, void // verify two success callbacks struct aws_subscription_status_record expected_subscription_events[] = { { - .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }, { - .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 2, }}; @@ -907,7 +959,99 @@ static int s_rrsm_release_unsubscribes_fn(struct aws_allocator *allocator, void return AWS_OP_SUCCESS; } -AWS_TEST_CASE(rrsm_release_unsubscribes, s_rrsm_release_unsubscribes_fn) +AWS_TEST_CASE(rrsm_release_unsubscribes_request, s_rrsm_release_unsubscribes_request_fn) + +/* + * Verify: A streaming subscription that resolves successfully invokes callbacks for every operation listener; releasing + * both references and calling a new acquire will trigger an unsubscribe of the first subscription + */ +static int s_rrsm_release_unsubscribes_streaming_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + + struct aws_protocol_adapter_subscription_event successful_subscription_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription_event); + + // verify two success callbacks + struct aws_subscription_status_record expected_subscription_events[] = { + { + .type = ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }, + { + .type = ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 2, + }}; + ASSERT_TRUE(s_contains_subscription_event_records(&fixture, 2, expected_subscription_events)); + + // verify no unsubscribes + struct aws_protocol_adapter_api_record expected_unsubscribe = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // release once, verify no unsubscribe + struct aws_rr_release_subscription_options release1_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + aws_rr_subscription_manager_release_subscription(manager, &release1_options); + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // release second + struct aws_rr_release_subscription_options release2_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 2, + }; + aws_rr_subscription_manager_release_subscription(manager, &release2_options); + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // unsubscribe is lazy, so we need to trigger it by acquiring something else + struct aws_rr_acquire_subscription_options acquire3_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 3, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); + + // now the unsubscribe should be present + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_release_unsubscribes_streaming, s_rrsm_release_unsubscribes_streaming_fn) static int s_rrsm_do_unsubscribe_test(struct aws_allocator *allocator, bool should_succeed) { aws_mqtt_library_init(allocator); @@ -1006,25 +1150,31 @@ static int s_rrsm_release_unsubscribe_failure_blocked_fn(struct aws_allocator *a AWS_TEST_CASE(rrsm_release_unsubscribe_failure_blocked, s_rrsm_release_unsubscribe_failure_blocked_fn) -static int s_aws_mqtt_protocol_adapter_mock_subscribe_fails( +static int s_aws_mqtt_protocol_adapter_mock_subscribe_fails_first_time( void *impl, struct aws_protocol_adapter_subscribe_options *options) { (void)impl; (void)options; - return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + struct aws_mqtt_protocol_adapter_mock_impl *mock_impl = impl; + + if (mock_impl->subscribe_count++ == 0) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } else { + return AWS_OP_SUCCESS; + } } /* - * Verify: Acquiring a new subscription but synchronously failing the protocol adapter subscribe returns FAILURE + * Verify: Acquiring a new request subscription but synchronously failing the protocol adapter subscribe returns FAILURE */ -static int s_rrsm_acquire_failure_subscribe_sync_failure_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_acquire_failure_subscribe_sync_failure_request_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_mqtt_library_init(allocator); struct aws_mqtt_protocol_adapter_vtable failing_vtable = s_protocol_adapter_mock_vtable; - failing_vtable.aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_mock_subscribe_fails; + failing_vtable.aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_mock_subscribe_fails_first_time; struct aws_subscription_manager_test_fixture_options fixture_config = { .max_subscriptions = 3, @@ -1051,12 +1201,62 @@ static int s_rrsm_acquire_failure_subscribe_sync_failure_fn(struct aws_allocator return AWS_OP_SUCCESS; } -AWS_TEST_CASE(rrsm_acquire_failure_subscribe_sync_failure, s_rrsm_acquire_failure_subscribe_sync_failure_fn) +AWS_TEST_CASE( + rrsm_acquire_failure_subscribe_sync_failure_request, + s_rrsm_acquire_failure_subscribe_sync_failure_request_fn) + +/* + * Verify: Acquiring a new streaming subscription but synchronously failing the protocol adapter subscribe returns + * FAILURE Trying again also fails even though the mock subscribe succeeds after the first try. + */ +static int s_rrsm_acquire_failure_subscribe_sync_failure_streaming_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_mqtt_protocol_adapter_vtable failing_vtable = s_protocol_adapter_mock_vtable; + failing_vtable.aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_mock_subscribe_fails_first_time; + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_subscriptions = 3, + .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, + .start_connected = true, + .adapter_vtable = &failing_vtable, + }; + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_FAILURE, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + + struct aws_rr_acquire_subscription_options acquire_options2 = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_FAILURE, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options2)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrsm_acquire_failure_subscribe_sync_failure_streaming, + s_rrsm_acquire_failure_subscribe_sync_failure_streaming_fn) /* - * Verify: Completing a subscription-acquire with a failing reason code emits a subscription failed event + * Verify: Completing a request subscription-acquire with a failing reason code emits a subscription failed event */ -static int s_rrsm_acquire_subscribe_failure_event_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_acquire_request_subscribe_failure_event_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_mqtt_library_init(allocator); @@ -1083,7 +1283,7 @@ static int s_rrsm_acquire_subscribe_failure_event_fn(struct aws_allocator *alloc // verify subscribe failure event emission struct aws_subscription_status_record expected_subscription_event = { - .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1096,9 +1296,87 @@ static int s_rrsm_acquire_subscribe_failure_event_fn(struct aws_allocator *alloc return AWS_OP_SUCCESS; } -AWS_TEST_CASE(rrsm_acquire_subscribe_failure_event, s_rrsm_acquire_subscribe_failure_event_fn) +AWS_TEST_CASE(rrsm_acquire_request_subscribe_failure_event, s_rrsm_acquire_request_subscribe_failure_event_fn) + +/* + * Verify: Completing a streaming subscription-acquire with a retryable failing failure triggers a resubscribe attempt + */ +static int s_rrsm_acquire_streaming_subscribe_failure_retryable_resubscribe_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; -static int s_do_offline_acquire_online_test(struct aws_allocator *allocator, bool success) { + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + // complete the subscribe with a retryable failure + struct aws_protocol_adapter_subscription_event failed_subscription_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, + .retryable = true, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &failed_subscription_event); + + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + + ASSERT_TRUE( + s_api_records_equals(fixture.mock_protocol_adapter, AWS_ARRAY_SIZE(expected_subscribes), expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrsm_acquire_streaming_subscribe_failure_retryable_resubscribe, + s_rrsm_acquire_streaming_subscribe_failure_retryable_resubscribe_fn) + +static enum aws_rr_subscription_event_type s_compute_expected_subscription_event_offline_acquire_online( + enum aws_rr_subscription_type subscription_type, + bool success) { + if (subscription_type == ARRST_REQUEST_RESPONSE) { + if (success) { + return ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS; + } else { + return ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE; + } + } else { + if (success) { + return ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED; + } else { + return ARRSET_STREAMING_SUBSCRIPTION_HALTED; + } + } +} + +static int s_do_offline_acquire_online_test( + struct aws_allocator *allocator, + enum aws_rr_subscription_type subscription_type, + bool success) { aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture_options fixture_config = { @@ -1113,7 +1391,7 @@ static int s_do_offline_acquire_online_test(struct aws_allocator *allocator, boo struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; struct aws_rr_acquire_subscription_options acquire_options = { - .type = ARRST_REQUEST_RESPONSE, + .type = subscription_type, .topic_filter = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1147,7 +1425,7 @@ static int s_do_offline_acquire_online_test(struct aws_allocator *allocator, boo aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { - .type = success ? ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS : ARRSET_SUBSCRIPTION_SUBSCRIBE_FAILURE, + .type = s_compute_expected_subscription_event_offline_acquire_online(subscription_type, success), .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1160,36 +1438,56 @@ static int s_do_offline_acquire_online_test(struct aws_allocator *allocator, boo } /* - * Verify: Acquiring a new subscription while offline returns SUBSCRIBING. Going online triggers a protocol adapter - * subscribe. Completing the subscription successfully emits a subscribe success event. + * Verify: Acquiring a new request subscription while offline returns SUBSCRIBING. Going online triggers a protocol + * adapter subscribe. Completing the subscription successfully emits a request subscribe success event. */ -static int s_rrsm_offline_acquire_online_success_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_offline_acquire_request_online_success_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_offline_acquire_online_test(allocator, true); + return s_do_offline_acquire_online_test(allocator, ARRST_REQUEST_RESPONSE, true); } -AWS_TEST_CASE(rrsm_offline_acquire_online_success, s_rrsm_offline_acquire_online_success_fn) +AWS_TEST_CASE(rrsm_offline_acquire_request_online_success, s_rrsm_offline_acquire_request_online_success_fn) /* - * Verify: Acquiring a new subscription while offline returns SUBSCRIBING. Going online triggers a protocol adapter - * subscribe. Completing the subscription with a failure emits a subscribe failure event. + * Verify: Acquiring a new request subscription while offline returns SUBSCRIBING. Going online triggers a protocol + * adapter subscribe. Completing the subscription with a failure emits a request subscribe failure event. */ -static int s_rrsm_offline_acquire_online_failure_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_offline_acquire_request_online_failure_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_offline_acquire_online_test(allocator, false); + return s_do_offline_acquire_online_test(allocator, ARRST_REQUEST_RESPONSE, false); } -AWS_TEST_CASE(rrsm_offline_acquire_online_failure, s_rrsm_offline_acquire_online_failure_fn) +AWS_TEST_CASE(rrsm_offline_acquire_request_online_failure, s_rrsm_offline_acquire_request_online_failure_fn) /* - * Verify: Acquiring and releasing a subscription while offline and then going online should remove the - * subscription without invoking any protocol adapter APIs. + * Verify: Acquiring a new steaming subscription while offline returns SUBSCRIBING. Going online triggers a protocol + * adapter subscribe. Completing the subscription successfully emits a streaming subscription established event. + */ +static int s_rrsm_offline_acquire_streaming_online_success_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_offline_acquire_online_test(allocator, ARRST_EVENT_STREAM, true); +} + +AWS_TEST_CASE(rrsm_offline_acquire_streaming_online_success, s_rrsm_offline_acquire_streaming_online_success_fn) + +/* + * Verify: Acquiring a new request subscription while offline returns SUBSCRIBING. Going online triggers a protocol + * adapter subscribe. Completing the subscription with a failure emits a streaming subscription halted event. */ -static int s_rrsm_offline_acquire_release_online_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_offline_acquire_streaming_online_failure_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; + return s_do_offline_acquire_online_test(allocator, ARRST_EVENT_STREAM, false); +} + +AWS_TEST_CASE(rrsm_offline_acquire_streaming_online_failure, s_rrsm_offline_acquire_streaming_online_failure_fn) + +static int s_do_offline_acquire_release_online_test( + struct aws_allocator *allocator, + enum aws_rr_subscription_type subscription_type) { aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture fixture; @@ -1205,7 +1503,7 @@ static int s_rrsm_offline_acquire_release_online_fn(struct aws_allocator *alloca // acquire struct aws_rr_acquire_subscription_options acquire_options = { - .type = ARRST_REQUEST_RESPONSE, + .type = subscription_type, .topic_filter = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1231,7 +1529,7 @@ static int s_rrsm_offline_acquire_release_online_fn(struct aws_allocator *alloca // trigger a different subscription, verify it's the only thing that has reached the protocol adapter struct aws_rr_acquire_subscription_options acquire2_options = { - .type = ARRST_REQUEST_RESPONSE, + .type = subscription_type, .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), .operation_id = 2, }; @@ -1252,16 +1550,34 @@ static int s_rrsm_offline_acquire_release_online_fn(struct aws_allocator *alloca return AWS_OP_SUCCESS; } -AWS_TEST_CASE(rrsm_offline_acquire_release_online, s_rrsm_offline_acquire_release_online_fn) +/* + * Verify: Acquiring and releasing a subscription while offline and then going online should remove the + * subscription without invoking any protocol adapter APIs. + */ +static int s_rrsm_offline_acquire_release_request_online_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_offline_acquire_release_online_test(allocator, ARRST_REQUEST_RESPONSE); +} + +AWS_TEST_CASE(rrsm_offline_acquire_release_request_online, s_rrsm_offline_acquire_release_request_online_fn) /* - * Verify: Releasing an active subscription while offline should not invoke an unsubscribe until back online + * Verify: Acquiring and releasing a subscription while offline and then going online should remove the + * subscription without invoking any protocol adapter APIs. */ -static int s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn( - struct aws_allocator *allocator, - void *ctx) { +static int s_rrsm_offline_acquire_release_streaming_online_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; + return s_do_offline_acquire_release_online_test(allocator, ARRST_EVENT_STREAM); +} + +AWS_TEST_CASE(rrsm_offline_acquire_release_streaming_online, s_rrsm_offline_acquire_release_streaming_online_fn) + +static int s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test( + struct aws_allocator *allocator, + enum aws_rr_subscription_type subscription_type) { + aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture fixture; @@ -1271,7 +1587,7 @@ static int s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn( // acquire struct aws_rr_acquire_subscription_options acquire_options = { - .type = ARRST_REQUEST_RESPONSE, + .type = subscription_type, .topic_filter = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1286,7 +1602,8 @@ static int s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn( aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { - .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = subscription_type == ARRST_REQUEST_RESPONSE ? ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS + : ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1307,7 +1624,7 @@ static int s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn( // acquire something different, normally that triggers an unsubscribe, but we're offline struct aws_rr_acquire_subscription_options acquire2_options = { - .type = ARRST_REQUEST_RESPONSE, + .type = subscription_type, .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), .operation_id = 2, }; @@ -1336,14 +1653,42 @@ static int s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn( return AWS_OP_SUCCESS; } +/* + * Verify: Releasing an active request subscription while offline should not invoke an unsubscribe until back online + */ +static int s_rrsm_acquire_request_success_offline_release_acquire2_no_unsubscribe_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test(allocator, ARRST_REQUEST_RESPONSE); +} + +AWS_TEST_CASE( + rrsm_acquire_request_success_offline_release_acquire2_no_unsubscribe, + s_rrsm_acquire_request_success_offline_release_acquire2_no_unsubscribe_fn) + +/* + * Verify: Releasing an active streaming subscription while offline should not invoke an unsubscribe until back online + */ +static int s_rrsm_acquire_streaming_success_offline_release_acquire2_no_unsubscribe_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test(allocator, ARRST_REQUEST_RESPONSE); +} + AWS_TEST_CASE( - rrsm_acquire_success_offline_release_acquire2_no_unsubscribe, - s_rrsm_acquire_success_offline_release_acquire2_no_unsubscribe_fn) + rrsm_acquire_streaming_success_offline_release_acquire2_no_unsubscribe, + s_rrsm_acquire_streaming_success_offline_release_acquire2_no_unsubscribe_fn) static int s_do_rrsm_acquire_clean_up_test( struct aws_allocator *allocator, + enum aws_rr_subscription_type subscription_type, bool complete_subscribe, bool clean_up_while_connected) { + aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture fixture; @@ -1353,7 +1698,7 @@ static int s_do_rrsm_acquire_clean_up_test( // acquire struct aws_rr_acquire_subscription_options acquire_options = { - .type = ARRST_REQUEST_RESPONSE, + .type = subscription_type, .topic_filter = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1369,7 +1714,8 @@ static int s_do_rrsm_acquire_clean_up_test( aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { - .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = subscription_type == ARRST_REQUEST_RESPONSE ? ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS + : ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1413,45 +1759,92 @@ static int s_do_rrsm_acquire_clean_up_test( } /* - * Verify: Calling clean up while a subscription is active triggers an immediate unsubscribe + * Verify: Calling clean up while a request subscription is active triggers an immediate unsubscribe + */ +static int s_rrsm_acquire_request_success_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_rrsm_acquire_clean_up_test(allocator, ARRST_REQUEST_RESPONSE, true, true); +} + +AWS_TEST_CASE( + rrsm_acquire_request_success_clean_up_unsubscribe_override, + s_rrsm_acquire_request_success_clean_up_unsubscribe_override_fn) + +/* + * Verify: Calling clean up while a streaming subscription is active triggers an immediate unsubscribe + */ +static int s_rrsm_acquire_streaming_success_clean_up_unsubscribe_override_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_do_rrsm_acquire_clean_up_test(allocator, ARRST_EVENT_STREAM, true, true); +} + +AWS_TEST_CASE( + rrsm_acquire_streaming_success_clean_up_unsubscribe_override, + s_rrsm_acquire_streaming_success_clean_up_unsubscribe_override_fn) + +/* + * Verify: Calling clean up while a request subscription is pending triggers an immediate unsubscribe */ -static int s_rrsm_acquire_success_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_acquire_request_pending_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_do_rrsm_acquire_clean_up_test(allocator, true, true); + return s_do_rrsm_acquire_clean_up_test(allocator, ARRST_REQUEST_RESPONSE, false, true); } AWS_TEST_CASE( - rrsm_acquire_success_clean_up_unsubscribe_override, - s_rrsm_acquire_success_clean_up_unsubscribe_override_fn) + rrsm_acquire_request_pending_clean_up_unsubscribe_override, + s_rrsm_acquire_request_pending_clean_up_unsubscribe_override_fn) /* - * Verify: Calling clean up while a subscription is pending triggers an immediate unsubscribe + * Verify: Calling clean up while a streaming subscription is pending triggers an immediate unsubscribe */ -static int s_rrsm_acquire_pending_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_acquire_streaming_pending_clean_up_unsubscribe_override_fn( + struct aws_allocator *allocator, + void *ctx) { (void)ctx; - return s_do_rrsm_acquire_clean_up_test(allocator, false, true); + return s_do_rrsm_acquire_clean_up_test(allocator, ARRST_EVENT_STREAM, false, true); } AWS_TEST_CASE( - rrsm_acquire_pending_clean_up_unsubscribe_override, - s_rrsm_acquire_pending_clean_up_unsubscribe_override_fn) + rrsm_acquire_streaming_pending_clean_up_unsubscribe_override, + s_rrsm_acquire_streaming_pending_clean_up_unsubscribe_override_fn) /* - * Verify: Calling clean up while offline and a subscription is pending triggers an immediate unsubscribe + * Verify: Calling clean up while offline and a request subscription is pending triggers an immediate unsubscribe */ -static int s_rrsm_offline_acquire_pending_clean_up_unsubscribe_override_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_offline_acquire_request_pending_clean_up_unsubscribe_override_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_do_rrsm_acquire_clean_up_test(allocator, ARRST_REQUEST_RESPONSE, false, false); +} + +AWS_TEST_CASE( + rrsm_offline_acquire_request_pending_clean_up_unsubscribe_override, + s_rrsm_offline_acquire_request_pending_clean_up_unsubscribe_override_fn) + +/* + * Verify: Calling clean up while offline and a streaming subscription is pending triggers an immediate unsubscribe + */ +static int s_rrsm_offline_acquire_streaming_pending_clean_up_unsubscribe_override_fn( + struct aws_allocator *allocator, + void *ctx) { (void)ctx; - return s_do_rrsm_acquire_clean_up_test(allocator, false, false); + return s_do_rrsm_acquire_clean_up_test(allocator, ARRST_EVENT_STREAM, false, false); } AWS_TEST_CASE( - rrsm_offline_acquire_pending_clean_up_unsubscribe_override, - s_rrsm_offline_acquire_pending_clean_up_unsubscribe_override_fn) + rrsm_offline_acquire_streaming_pending_clean_up_unsubscribe_override, + s_rrsm_offline_acquire_streaming_pending_clean_up_unsubscribe_override_fn) -static int s_rrsm_do_no_session_subscription_lost_test( +static int s_rrsm_do_no_session_subscription_ended_test( struct aws_allocator *allocator, bool offline_while_unsubscribing) { aws_mqtt_library_init(allocator); @@ -1478,7 +1871,7 @@ static int s_rrsm_do_no_session_subscription_lost_test( aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { - .type = ARRSET_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1527,12 +1920,12 @@ static int s_rrsm_do_no_session_subscription_lost_test( // verify subscription lost emitted if (!offline_while_unsubscribing) { - struct aws_subscription_status_record expected_subscription_lost_event = { - .type = ARRSET_SUBSCRIPTION_ENDED, + struct aws_subscription_status_record expected_subscription_ended_event = { + .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; - ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_lost_event)); + ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_ended_event)); } // if we were unsubscribing, verify reacquire is blocked and then complete the unsubscribe @@ -1563,29 +1956,116 @@ static int s_rrsm_do_no_session_subscription_lost_test( } /* - * Verify: If the client fails to rejoin a session, a SUBSCRIPTION_ENDED event is emitted for active subscriptions and - * that subscription can successfully be reacquired + * Verify: If the client fails to rejoin a session, a SUBSCRIPTION_ENDED event is emitted for active request + * subscriptions and that subscription can successfully be reacquired */ -static int s_rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire_fn( +static int s_rrsm_acquire_request_success_offline_online_no_session_subscription_ended_can_reacquire_fn( struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_rrsm_do_no_session_subscription_lost_test(allocator, false); + return s_rrsm_do_no_session_subscription_ended_test(allocator, false); } AWS_TEST_CASE( - rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire, - s_rrsm_acquire_success_offline_online_no_session_subscription_lost_can_reacquire_fn) + rrsm_acquire_request_success_offline_online_no_session_subscription_ended_can_reacquire, + s_rrsm_acquire_request_success_offline_online_no_session_subscription_ended_can_reacquire_fn) /* * Verify: If the client fails to rejoin a session, a SUBSCRIPTION_ENDED event is emitted for unsubscribing - * subscriptions + * request subscriptions */ -static int s_rrsm_subscription_lost_while_unsubscribing_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrsm_request_subscription_ended_while_unsubscribing_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_rrsm_do_no_session_subscription_lost_test(allocator, true); + return s_rrsm_do_no_session_subscription_ended_test(allocator, true); +} + +AWS_TEST_CASE( + rrsm_request_subscription_ended_while_unsubscribing, + s_rrsm_request_subscription_ended_while_unsubscribing_fn) + +/* + * Verify: If the client fails to rejoin a session, a SUBSCRIPTION_LOST event is emitted for streaming subscriptions, + * and a resubscribe is triggered + */ +static int s_rrsm_streaming_subscription_lost_resubscribe_on_no_session_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + // acquire + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_EVENT_STREAM, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + + // successfully complete subscription + struct aws_protocol_adapter_subscription_event subscription_event = { + .event_type = AWS_PASET_SUBSCRIBE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); + + struct aws_subscription_status_record expected_subscription_event = { + .type = ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); + + // online -> offline + struct aws_protocol_adapter_connection_event offline_event = { + .event_type = AWS_PACET_DISCONNECTED, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(manager, &offline_event); + + // offline -> online (no session) + struct aws_protocol_adapter_connection_event online_event = { + .event_type = AWS_PACET_CONNECTED, + .joined_session = false, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(manager, &online_event); + + // verify subscription lost on rejoin + struct aws_subscription_status_record expected_subscription_ended_event = { + .type = ARRSET_STREAMING_SUBSCRIPTION_LOST, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_ended_event)); + + // verify resubscribe submitted to the protocol adapter + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + + ASSERT_TRUE( + s_api_records_equals(fixture.mock_protocol_adapter, AWS_ARRAY_SIZE(expected_subscribes), expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; } -AWS_TEST_CASE(rrsm_subscription_lost_while_unsubscribing, s_rrsm_subscription_lost_while_unsubscribing_fn) +AWS_TEST_CASE( + rrsm_streaming_subscription_lost_resubscribe_on_no_session, + s_rrsm_streaming_subscription_lost_resubscribe_on_no_session_fn) From 3d503740df5f0af2673c23fd04fa113c3ae5ff7e Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 13 Mar 2024 17:53:50 -0700 Subject: [PATCH 075/124] Add a sleep to guarantee task execution order which guarantees event emission that we wanted to check in the test --- .../request_response_client_tests.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index a7586df1..9448dd81 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0. */ +#include #include #include #include @@ -889,6 +890,21 @@ static int s_do_rrc_single_streaming_operation_test_fn( if (shutdown_after_submit) { aws_mqtt_request_response_client_release(fixture.rr_client); fixture.rr_client = NULL; + + /* + * Extremely awkward sleep: + * + * We've submitted the operation and we've decref'd the client to zero. When the operation submit task + * is processed, if the release in the succeeding line has happened-before the client external destroy task + * has run, then the operation's destory will be scheduled in-thread and run ahead of the client external + * destroy. This doesn't break correctness, but it does prevent the client from emitting a HALTED event + * on the subscription because the subscription/operation will be gone before the client external destroy + * task runs. + * + * So we add a nice, fat sleep to guarantee that the client external destroy task runs before the operation + * destroy task. + */ + aws_thread_current_sleep(aws_timestamp_convert(1, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); aws_mqtt_rr_client_operation_release(streaming_operation); } From ccf6ac8e8db3c0d9adc4150397ffea50e2be641e Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 14 Mar 2024 14:14:14 -0700 Subject: [PATCH 076/124] Add timeout and service task support to the request-response client --- include/aws/mqtt/mqtt.h | 1 + source/mqtt.c | 4 + .../request_response_client.c | 163 +++++++++++++++++- tests/CMakeLists.txt | 1 + .../request_response_client_tests.c | 44 ++++- 5 files changed, 203 insertions(+), 10 deletions(-) diff --git a/include/aws/mqtt/mqtt.h b/include/aws/mqtt/mqtt.h index 11232125..72e4df48 100644 --- a/include/aws/mqtt/mqtt.h +++ b/include/aws/mqtt/mqtt.h @@ -83,6 +83,7 @@ enum aws_mqtt_error { AWS_ERROR_MQTT_CONNECTION_SUBSCRIBE_FAILURE, AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, + AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, AWS_ERROR_END_MQTT_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_MQTT_PACKAGE_ID), }; diff --git a/source/mqtt.c b/source/mqtt.c index f1f7c360..68bd9bf0 100644 --- a/source/mqtt.c +++ b/source/mqtt.c @@ -239,6 +239,10 @@ bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter) AWS_DEFINE_ERROR_INFO_MQTT( AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, "Request operation failed due to client shut down"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, + "Request operation failed due to timeout"), + }; /* clang-format on */ #undef AWS_DEFINE_ERROR_INFO_MQTT diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 110199d6..22c1c94a 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -5,6 +5,7 @@ #include +#include #include #include #include @@ -13,6 +14,8 @@ #include #include +#include + #define MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE 50 enum aws_mqtt_request_response_operation_type { @@ -303,6 +306,9 @@ struct aws_mqtt_rr_client_operation { struct aws_mqtt_request_operation_storage request_storage; } storage; + uint64_t timeout_timepoint_ns; + struct aws_priority_queue_node priority_queue_node; + /* Sometimes this is client->operation_queue, other times it is an entry in the client's topic_filter table */ struct aws_linked_list_node node; @@ -370,6 +376,9 @@ struct aws_mqtt_request_response_client { struct aws_task external_shutdown_task; struct aws_task internal_shutdown_task; + uint64_t scheduled_service_timepoint_ns; + struct aws_task service_task; + enum aws_request_response_client_state state; struct aws_atomic_var next_id; @@ -378,6 +387,13 @@ struct aws_mqtt_request_response_client { /* &operation->id -> &operation */ struct aws_hash_table operations; + + /* + * heap of operation pointers where the timeout is the sort value. Elements are added to this on operation + * submission and removed on operation timeout/completion/termination. Request-response operations have actual + * timeouts, while streaming operations have UINT64_MAX timeouts. + */ + struct aws_priority_queue operations_by_timeout; }; static void s_aws_rr_client_on_zero_internal_ref_count(void *context) { @@ -401,6 +417,8 @@ static void s_mqtt_request_response_client_final_destroy(struct aws_mqtt_request AWS_FATAL_ASSERT(aws_hash_table_get_entry_count(&client->operations) == 0); aws_hash_table_clean_up(&client->operations); + aws_priority_queue_clean_up(&client->operations_by_timeout); + aws_mem_release(client->allocator, client); if (terminate_callback != NULL) { @@ -421,6 +439,15 @@ static void s_mqtt_request_response_client_internal_shutdown_task_fn( s_mqtt_request_response_client_final_destroy(client); } +static void s_remove_operation_from_timeout_queue(struct aws_mqtt_rr_client_operation *operation) { + struct aws_mqtt_request_response_client *client = operation->client_internal_ref; + + if (aws_priority_queue_node_is_in_queue(&operation->priority_queue_node)) { + struct aws_mqtt_rr_client_operation *queued_operation = NULL; + aws_priority_queue_remove(&client->operations_by_timeout, &queued_operation, &operation->priority_queue_node); + } +} + static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_operation *operation, int error_code) { AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); AWS_FATAL_ASSERT(error_code != AWS_ERROR_SUCCESS); @@ -515,6 +542,20 @@ static void s_mqtt_request_response_client_external_shutdown_task_fn( aws_ref_count_release(&client->internal_ref_count); } +static void s_mqtt_request_response_client_wake_service(struct aws_mqtt_request_response_client *client) { + uint64_t now = 0; + aws_high_res_clock_get_ticks(&now); + + if (client->scheduled_service_timepoint_ns == 0 || now < client->scheduled_service_timepoint_ns) { + if (now < client->scheduled_service_timepoint_ns) { + aws_event_loop_cancel_task(client->loop, &client->service_task); + } + + client->scheduled_service_timepoint_ns = now; + aws_event_loop_schedule_task_now(client->loop, &client->service_task); + } +} + static void s_aws_rr_client_subscription_status_event_callback( const struct aws_rr_subscription_status_event *event, void *userdata) { @@ -595,6 +636,22 @@ bool aws_mqtt_compare_uint64_t_eq(const void *a, const void *b) { return *(uint64_t *)a == *(uint64_t *)b; } +static int s_compare_rr_operation_timeouts(const void *a, const void *b) { + const struct aws_mqtt_rr_client_operation **operation_a_ptr = (void *)a; + const struct aws_mqtt_rr_client_operation *operation_a = *operation_a_ptr; + + const struct aws_mqtt_rr_client_operation **operation_b_ptr = (void *)b; + const struct aws_mqtt_rr_client_operation *operation_b = *operation_b_ptr; + + if (operation_a->timeout_timepoint_ns < operation_b->timeout_timepoint_ns) { + return -1; + } else if (operation_a->timeout_timepoint_ns > operation_b->timeout_timepoint_ns) { + return 1; + } else { + return 0; + } +} + static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_new( struct aws_allocator *allocator, const struct aws_mqtt_request_response_client_options *options, @@ -632,6 +689,13 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie NULL, NULL); + aws_priority_queue_init_dynamic( + &rr_client->operations_by_timeout, + allocator, + 100, + sizeof(struct aws_mqtt_rr_client_operation *), + s_compare_rr_operation_timeouts); + aws_linked_list_init(&rr_client->operation_queue); aws_task_init( @@ -673,6 +737,81 @@ static void s_aws_rr_client_init_subscription_manager( &rr_client->subscription_manager, allocator, rr_client->client_adapter, &subscription_manager_options); } +static void s_check_for_operation_timeouts(struct aws_mqtt_request_response_client *client) { + uint64_t now = 0; + aws_high_res_clock_get_ticks(&now); + + struct aws_priority_queue *timeout_queue = &client->operations_by_timeout; + + bool done = aws_priority_queue_size(timeout_queue) == 0; + while (!done) { + struct aws_mqtt_rr_client_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_mqtt_rr_client_operation *next_operation_by_timeout = *next_operation_by_timeout_ptr; + AWS_FATAL_ASSERT(next_operation_by_timeout != NULL); + + // If the current top of the heap hasn't timed out than nothing has + if (next_operation_by_timeout->timeout_timepoint_ns > now) { + break; + } + + /* Ack timeout for this operation has been reached */ + aws_priority_queue_pop(timeout_queue, &next_operation_by_timeout); + + AWS_LOGF_INFO( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request operation with id:%" PRIu64 " has timed out", + (void *)client, + next_operation_by_timeout->id); + + s_complete_request_operation_with_failure(next_operation_by_timeout, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT); + + done = aws_priority_queue_size(timeout_queue) == 0; + } +} + +static uint64_t s_mqtt_request_response_client_get_next_service_time(struct aws_mqtt_request_response_client *client) { + if (aws_priority_queue_size(&client->operations_by_timeout) > 0) { + struct aws_mqtt_rr_client_operation **next_operation_by_timeout_ptr = NULL; + aws_priority_queue_top(&client->operations_by_timeout, (void **)&next_operation_by_timeout_ptr); + AWS_FATAL_ASSERT(next_operation_by_timeout_ptr != NULL); + struct aws_mqtt_rr_client_operation *next_operation_by_timeout = *next_operation_by_timeout_ptr; + AWS_FATAL_ASSERT(next_operation_by_timeout != NULL); + + return next_operation_by_timeout->timeout_timepoint_ns; + } + + return UINT64_MAX; +} + +static void s_mqtt_request_response_service_task_fn( + struct aws_task *task, + void *arg, + enum aws_task_status task_status) { + (void)task; + + if (task_status == AWS_TASK_STATUS_CANCELED) { + return; + } + + struct aws_mqtt_request_response_client *client = arg; + client->scheduled_service_timepoint_ns = 0; + + if (client->state == AWS_RRCS_ACTIVE) { + + // timeouts + s_check_for_operation_timeouts(client); + + // TODO: operation intake and service + + // schedule next service + client->scheduled_service_timepoint_ns = s_mqtt_request_response_client_get_next_service_time(client); + aws_event_loop_schedule_task_future( + client->loop, &client->service_task, client->scheduled_service_timepoint_ns); + } +} + static void s_mqtt_request_response_client_initialize_task_fn( struct aws_task *task, void *arg, @@ -687,6 +826,11 @@ static void s_mqtt_request_response_client_initialize_task_fn( s_aws_rr_client_init_subscription_manager(client, client->allocator); client->state = AWS_RRCS_ACTIVE; + + aws_task_init(&client->service_task, s_mqtt_request_response_service_task_fn, client, "mqtt_rr_client_service"); + + aws_event_loop_schedule_task_future(client->loop, &client->service_task, UINT64_MAX); + client->scheduled_service_timepoint_ns = UINT64_MAX; } if (client->config.initialized_callback != NULL) { @@ -913,24 +1057,26 @@ static void s_mqtt_rr_client_submit_operation(struct aws_task *task, void *arg, (void)task; struct aws_mqtt_rr_client_operation *operation = arg; + struct aws_mqtt_request_response_client *client = operation->client_internal_ref; if (status == AWS_TASK_STATUS_CANCELED) { goto done; } // add appropriate client table entries - aws_hash_table_put(&operation->client_internal_ref->operations, &operation->id, operation, NULL); + aws_hash_table_put(&client->operations, &operation->id, operation, NULL); // NYI other tables - // NYI set up timeout + // add to timeout priority queue + aws_priority_queue_push_ref(&client->operations_by_timeout, (void *)&operation, &operation->priority_queue_node); // enqueue aws_linked_list_push_back(&operation->client_internal_ref->operation_queue, &operation->node); operation->state = AWS_MRROS_QUEUED; - // NYI wake service + s_mqtt_request_response_client_wake_service(operation->client_internal_ref); done: @@ -972,6 +1118,7 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, struct aws_mqtt_request_response_client *client = operation->client_internal_ref; aws_hash_table_remove(&client->operations, &operation->id, NULL, NULL); + s_remove_operation_from_timeout_queue(operation); aws_linked_list_remove(&operation->node); @@ -986,7 +1133,6 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, /* NYI: - Remove from timeout tracking Remove from topic filter table Remove from correlation token table @@ -1115,11 +1261,19 @@ int aws_mqtt_request_response_client_submit_request( return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } + uint64_t now = 0; + if (aws_high_res_clock_get_ticks(&now)) { + return aws_raise_error(AWS_ERROR_CLOCK_FAILURE); + } + struct aws_allocator *allocator = client->allocator; struct aws_mqtt_rr_client_operation *operation = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_rr_client_operation)); operation->allocator = allocator; operation->type = AWS_MRROT_REQUEST; + operation->timeout_timepoint_ns = + now + + aws_timestamp_convert(client->config.operation_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); s_aws_mqtt_request_operation_storage_init_from_options( &operation->storage.request_storage, allocator, request_options); @@ -1163,6 +1317,7 @@ struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_str aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_rr_client_operation)); operation->allocator = allocator; operation->type = AWS_MRROT_STREAMING; + operation->timeout_timepoint_ns = UINT64_MAX; s_aws_mqtt_streaming_operation_storage_init_from_options( &operation->storage.streaming_storage, allocator, streaming_options); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3ba0bb96..678a8fa6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -533,6 +533,7 @@ add_test_case(rrc_submit_streaming_operation_failure_invalid_subscription_topic_ add_test_case(rrc_submit_request_operation_failure_by_shutdown) add_test_case(rrc_submit_streaming_operation_and_shutdown) +add_test_case(rrc_submit_request_operation_failure_by_timeout) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 9448dd81..90731ea1 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -787,6 +787,7 @@ AWS_TEST_CASE( static int s_do_rrc_single_request_operation_test_fn( struct aws_allocator *allocator, + struct aws_mqtt_request_response_client_options *rr_client_options, struct aws_mqtt_request_operation_options *request_options, int expected_error_code, struct aws_byte_cursor *expected_payload, @@ -802,8 +803,8 @@ static int s_do_rrc_single_request_operation_test_fn( }; struct aws_rr_client_test_fixture fixture; - ASSERT_SUCCESS( - s_aws_rr_client_test_fixture_init_from_mqtt5(&fixture, allocator, NULL, &client_test_fixture_options, NULL)); + ASSERT_SUCCESS(s_aws_rr_client_test_fixture_init_from_mqtt5( + &fixture, allocator, rr_client_options, &client_test_fixture_options, NULL)); struct aws_rr_client_fixture_request_response_record *record = s_rrc_fixture_add_request_record(&fixture, request_options->serialized_request); @@ -850,13 +851,14 @@ static int s_rrc_submit_request_operation_failure_by_shutdown_fn(struct aws_allo }; return s_do_rrc_single_request_operation_test_fn( - allocator, &request, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, NULL, true); + allocator, NULL, &request, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, NULL, true); } AWS_TEST_CASE(rrc_submit_request_operation_failure_by_shutdown, s_rrc_submit_request_operation_failure_by_shutdown_fn) static int s_do_rrc_single_streaming_operation_test_fn( struct aws_allocator *allocator, + struct aws_mqtt_request_response_client_options *rr_client_options, struct aws_mqtt_streaming_operation_options *streaming_options, size_t expected_subscription_event_count, struct aws_rr_client_fixture_streaming_record_subscription_event *expected_subscription_events, @@ -872,8 +874,8 @@ static int s_do_rrc_single_streaming_operation_test_fn( }; struct aws_rr_client_test_fixture fixture; - ASSERT_SUCCESS( - s_aws_rr_client_test_fixture_init_from_mqtt5(&fixture, allocator, NULL, &client_test_fixture_options, NULL)); + ASSERT_SUCCESS(s_aws_rr_client_test_fixture_init_from_mqtt5( + &fixture, allocator, rr_client_options, &client_test_fixture_options, NULL)); struct aws_byte_cursor streaming_id = aws_byte_cursor_from_c_str("streaming1"); struct aws_rr_client_fixture_streaming_record *record = s_rrc_fixture_add_streaming_record(&fixture, streaming_id); @@ -935,7 +937,37 @@ static int s_rrc_submit_streaming_operation_and_shutdown_fn(struct aws_allocator }; return s_do_rrc_single_streaming_operation_test_fn( - allocator, &streaming_options, AWS_ARRAY_SIZE(expected_events), expected_events, true); + allocator, NULL, &streaming_options, AWS_ARRAY_SIZE(expected_events), expected_events, true); } AWS_TEST_CASE(rrc_submit_streaming_operation_and_shutdown, s_rrc_submit_streaming_operation_and_shutdown_fn) + +static int s_rrc_submit_request_operation_failure_by_timeout_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_mqtt_request_operation_response_path response_paths[] = { + { + .topic = aws_byte_cursor_from_c_str("response/filter/accepted"), + .correlation_token_json_path = aws_byte_cursor_from_c_str("client_token"), + }, + }; + + struct aws_mqtt_request_operation_options request = { + .subscription_topic_filter = aws_byte_cursor_from_c_str("response/filter/+"), + .response_paths = response_paths, + .response_path_count = AWS_ARRAY_SIZE(response_paths), + .publish_topic = aws_byte_cursor_from_c_str("get/shadow"), + .serialized_request = aws_byte_cursor_from_c_str("request1"), + .correlation_token = aws_byte_cursor_from_c_str("MyRequest#1"), + }; + + struct aws_mqtt_request_response_client_options rr_client_options = { + .max_subscriptions = 2, + .operation_timeout_seconds = 2, + }; + + return s_do_rrc_single_request_operation_test_fn( + allocator, &rr_client_options, &request, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, false); +} + +AWS_TEST_CASE(rrc_submit_request_operation_failure_by_timeout, s_rrc_submit_request_operation_failure_by_timeout_fn) \ No newline at end of file From 96e63a7150e14abc806fdea77d3ed54b43d60a93 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 14 Mar 2024 14:36:51 -0700 Subject: [PATCH 077/124] Cancel service task on shutdown --- source/request-response/request_response_client.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 22c1c94a..f52604cb 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -520,6 +520,11 @@ static void s_mqtt_request_response_client_external_shutdown_task_fn( /* stop handling adapter event callbacks */ client->state = AWS_RRCS_SHUTTING_DOWN; + if (client->scheduled_service_timepoint_ns != 0) { + aws_event_loop_cancel_task(client->loop, &client->service_task); + client->scheduled_service_timepoint_ns = 0; + } + aws_rr_subscription_manager_clean_up(&client->subscription_manager); if (client->client_adapter != NULL) { @@ -546,6 +551,12 @@ static void s_mqtt_request_response_client_wake_service(struct aws_mqtt_request_ uint64_t now = 0; aws_high_res_clock_get_ticks(&now); + AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(client->loop)); + + if (client->state != AWS_RRCS_ACTIVE) { + return; + } + if (client->scheduled_service_timepoint_ns == 0 || now < client->scheduled_service_timepoint_ns) { if (now < client->scheduled_service_timepoint_ns) { aws_event_loop_cancel_task(client->loop, &client->service_task); From 69d00c8122deb123b636da46c6b58a8f8ff5200b Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 14 Mar 2024 15:16:02 -0700 Subject: [PATCH 078/124] Newline --- tests/request-response/request_response_client_tests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 90731ea1..5dc0fa5c 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -970,4 +970,4 @@ static int s_rrc_submit_request_operation_failure_by_timeout_fn(struct aws_alloc allocator, &rr_client_options, &request, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, false); } -AWS_TEST_CASE(rrc_submit_request_operation_failure_by_timeout, s_rrc_submit_request_operation_failure_by_timeout_fn) \ No newline at end of file +AWS_TEST_CASE(rrc_submit_request_operation_failure_by_timeout, s_rrc_submit_request_operation_failure_by_timeout_fn) From cdc1a97e2e80ed93f5f742dfbee1b70c4be1b672 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 19 Mar 2024 10:01:59 -0700 Subject: [PATCH 079/124] Streaming operation intake --- include/aws/mqtt/mqtt.h | 3 + .../request_response_client.h | 1 - source/mqtt.c | 9 + .../request_response_client.c | 303 ++++++++++++++---- 4 files changed, 256 insertions(+), 60 deletions(-) diff --git a/include/aws/mqtt/mqtt.h b/include/aws/mqtt/mqtt.h index 72e4df48..519378f5 100644 --- a/include/aws/mqtt/mqtt.h +++ b/include/aws/mqtt/mqtt.h @@ -84,6 +84,9 @@ enum aws_mqtt_error { AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, + AWS_ERROR_MQTT_REQUEST_RESPONSE_NO_SUBSCRIPTION_CAPACITY, + AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE, + AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR, AWS_ERROR_END_MQTT_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_MQTT_PACKAGE_ID), }; diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index ff73cd06..450675d1 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -13,7 +13,6 @@ struct aws_mqtt_request_response_client; struct aws_mqtt_client_connection; struct aws_mqtt5_client; -struct aws_mqtt_streaming_operation; struct aws_mqtt_request_operation_response_path { struct aws_byte_cursor topic; diff --git a/source/mqtt.c b/source/mqtt.c index 68bd9bf0..0e7b0c0e 100644 --- a/source/mqtt.c +++ b/source/mqtt.c @@ -242,6 +242,15 @@ bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter) AWS_DEFINE_ERROR_INFO_MQTT( AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, "Request operation failed due to timeout"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_REQUEST_RESPONSE_NO_SUBSCRIPTION_CAPACITY, + "Streaming request operation failed because there was no space for the subscription"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE, + "Request operation failed because the associated subscribe failed synchronously"), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR, + "Request operation failed due to a non-specific internal error within the client."), }; /* clang-format on */ diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index f52604cb..07f4a5aa 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -54,8 +54,51 @@ state, removed on operation completion/destruction 4. &topic_filter -> &{topic_filter, linked_list} // added on in-thread pop from queue, removed from list on operation completion/destruction also checked for empty and removed from table + Note: 4 tracks both streaming and request-response operations but each uses the table in different ways. Both use + the table to react to subscription status events to move the operation forward state-wise. Additionally, + streaming operations use the table to map incoming publishes to listening streaming operations. OTOH, request + operations use table 2 and then 3 to map incoming publishes to operations. + */ +struct aws_rr_operation_list_topic_filter_entry { + struct aws_allocator *allocator; + + struct aws_byte_cursor topic_filter_cursor; + struct aws_byte_buf topic_filter; + + struct aws_linked_list operations; +}; + +struct aws_rr_operation_list_topic_filter_entry *s_aws_rr_operation_list_topic_filter_entry_new( + struct aws_allocator *allocator, + struct aws_byte_cursor topic_filter) { + struct aws_rr_operation_list_topic_filter_entry *entry = + aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_operation_list_topic_filter_entry)); + + entry->allocator = allocator; + aws_byte_buf_init_copy_from_cursor(&entry->topic_filter, allocator, topic_filter); + entry->topic_filter_cursor = aws_byte_cursor_from_buf(&entry->topic_filter); + + aws_linked_list_init(&entry->operations); + + return entry; +} + +void s_aws_rr_operation_list_topic_filter_entry_destroy(struct aws_rr_operation_list_topic_filter_entry *entry) { + if (entry == NULL) { + return; + } + + aws_byte_buf_clean_up(&entry->topic_filter); + + aws_mem_release(entry->allocator, entry); +} + +void s_aws_rr_operation_list_topic_filter_entry_hash_element_destroy(void *value) { + s_aws_rr_operation_list_topic_filter_entry_destroy(value); +} + /* All operations have an internal ref to the client they are a part of */ /* @@ -110,7 +153,7 @@ operation completion/destruction also checked for empty and removed from table /* WakeServiceTask(client) [Event Loop]: - If client.state != SHUTTING_DOWN && protocol client is connected + If client.state == ACTIVE && client.connected RescheduleServiceTask(now) */ @@ -127,6 +170,7 @@ operation completion/destruction also checked for empty and removed from table Check client's topic filter table entry for empty list, remove entry if so. (intrusive list removal already unlinked it from table) If client is not shutting down remove from subscription manager (otherwise it's already been cleaned up) + client.subscription_manager.release_subscription(operation.topic_filter) WakeServiceTask // queue may now be unblocked, does nothing if shutting down (Streaming) Invoke termination callback Release client internal ref @@ -155,6 +199,7 @@ operation completion/destruction also checked for empty and removed from table /* OnProtocolAdapterConnectionEvent(event) [Event Loop]: + client.connected <- event.connected client.subscription_manager.notify(event) WakeServiceTask */ @@ -173,6 +218,10 @@ operation completion/destruction also checked for empty and removed from table /* MakeRequest(operation) [Event Loop]: + operation.state <- SUBSCRIBED + if !client.connected + return + // Critical Requirement - the user data for the publish completion callback must be a weak ref that wraps // the operation. On operation destruction, we zero the weak ref (and dec ref it). operation.state <- AWAITING_RESPONSE @@ -193,18 +242,14 @@ operation completion/destruction also checked for empty and removed from table /* StreamingOperationOnSubscriptionStatusEvent(operation, event) [Event loop, top-level task loop]: - If event.type == Success and operation.state == SUBSCRIBING - operation.state <- SUBSCRIBED - Invoke Success/Failure callback with success - Else If event.type == Failure and operation.state == SUBSCRIBING + If event.type == Success + Emit SubscriptionEstablished + Else If event.type == Lost + Emit SubscriptionLost + Else if event.type == Halted operation.state <- TERMINAL - Invoke Success/Failure callback with failure - Else if event.type == Ended and operation.state != TERMINAL - operation.state <- TERMINAL - Invoke Ended callback + Emit SubscriptionHalted - If Failure or Ended: - sub manager release_subscription(operation id) */ /* @@ -230,12 +275,13 @@ operation completion/destruction also checked for empty and removed from table return // invariant, must be SUBSCRIBING or SUBSCRIBED at this point + Add operation to client's topic filter table + If operation is streaming Add operation to topic filter table operation.state <- {SUBSCRIBING, SUBSCRIBED} If operation is request - Add operation to client's topic filter table if result == SUBSCRIBING operation.state <- SUBSCRIBING else // (SUBSCRIBED) @@ -248,26 +294,26 @@ operation completion/destruction also checked for empty and removed from table For all timed out operations: OnOperationTimeout(operation) - While OperationQueue is not empty: - operation = peek queue - result = subscription manager acquire sub(operation) - if result == Blocked - break - pop operation - HandleAcquireSubscriptionResult(operation, result) + if client connected + + For all request operations where state == SUBSCRIBED + MakeRequest(operation) + + While OperationQueue is not empty: + operation = peek queue + result = subscription manager acquire sub(operation) + if result == Blocked + break + pop operation + HandleAcquireSubscriptionResult(operation, result) Reschedule Service for next timeout if it exists */ /* - OnOperationTimeout(operation) [Event Loop, Service Task Loop]: - - If operation.type == request and operation.state != PENDING_DESTROY - CompleteRequestOperation(operation, error) + OnOperationTimeout(operation) [Event Loop, Service Task Loop, operation is request]: - If operation.type == streaming and operation.state != {SUBSCRIBED, TERMINAL} - operation.state <- TERMINAL - Invoke operation failure callback + CompleteRequestOperation(operation, error) */ @@ -394,6 +440,8 @@ struct aws_mqtt_request_response_client { * timeouts, while streaming operations have UINT64_MAX timeouts. */ struct aws_priority_queue operations_by_timeout; + + struct aws_hash_table operation_lists_by_subscription_filter; }; static void s_aws_rr_client_on_zero_internal_ref_count(void *context) { @@ -418,6 +466,7 @@ static void s_mqtt_request_response_client_final_destroy(struct aws_mqtt_request aws_hash_table_clean_up(&client->operations); aws_priority_queue_clean_up(&client->operations_by_timeout); + aws_hash_table_clean_up(&client->operation_lists_by_subscription_filter); aws_mem_release(client->allocator, client); @@ -469,40 +518,38 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ aws_mqtt_rr_client_operation_release(operation); } -static void s_streaming_operation_on_client_shutdown(struct aws_mqtt_rr_client_operation *operation, int error_code) { +static void s_halt_streaming_operation_with_failure(struct aws_mqtt_rr_client_operation *operation, int error_code) { AWS_FATAL_ASSERT(operation->type == AWS_MRROT_STREAMING); AWS_FATAL_ASSERT(error_code != AWS_ERROR_SUCCESS); - switch (operation->state) { - case AWS_MRROS_QUEUED: - case AWS_MRROS_PENDING_SUBSCRIPTION: - case AWS_MRROS_SUBSCRIBED: { - aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback = - operation->storage.streaming_storage.options.subscription_status_callback; - void *user_data = operation->storage.streaming_storage.options.user_data; - if (subscription_status_callback != NULL) { - (*subscription_status_callback)(ARRSET_STREAMING_SUBSCRIPTION_HALTED, error_code, user_data); - } - } + if (operation->state == AWS_MRROS_PENDING_DESTROY || operation->state == AWS_MRROS_TERMINAL) { + return; + } - default: - break; + aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback = + operation->storage.streaming_storage.options.subscription_status_callback; + + if (subscription_status_callback != NULL) { + void *user_data = operation->storage.streaming_storage.options.user_data; + (*subscription_status_callback)(ARRSET_STREAMING_SUBSCRIPTION_HALTED, error_code, user_data); } operation->state = AWS_MRROS_TERMINAL; } +static void s_request_response_fail_operation(struct aws_mqtt_rr_client_operation *operation, int error_code) { + if (operation->type == AWS_MRROT_STREAMING) { + s_halt_streaming_operation_with_failure(operation, error_code); + } else { + s_complete_request_operation_with_failure(operation, error_code); + } +} + static int s_rr_client_clean_up_operation(void *context, struct aws_hash_element *elem) { (void)context; struct aws_mqtt_rr_client_operation *operation = elem->value; - if (operation->type == AWS_MRROT_REQUEST) { - /* Complete the request operation as a failure */ - s_complete_request_operation_with_failure(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN); - } else { - /* Non-terminal streaming operations should a subscription failure or ended event */ - s_streaming_operation_on_client_shutdown(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN); - } + s_request_response_fail_operation(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN); return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; } @@ -707,6 +754,15 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie sizeof(struct aws_mqtt_rr_client_operation *), s_compare_rr_operation_timeouts); + aws_hash_table_init( + &rr_client->operation_lists_by_subscription_filter, + allocator, + MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + s_aws_rr_operation_list_topic_filter_entry_hash_element_destroy); + aws_linked_list_init(&rr_client->operation_queue); aws_task_init( @@ -776,7 +832,7 @@ static void s_check_for_operation_timeouts(struct aws_mqtt_request_response_clie (void *)client, next_operation_by_timeout->id); - s_complete_request_operation_with_failure(next_operation_by_timeout, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT); + s_request_response_fail_operation(next_operation_by_timeout, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT); done = aws_priority_queue_size(timeout_queue) == 0; } @@ -796,6 +852,123 @@ static uint64_t s_mqtt_request_response_client_get_next_service_time(struct aws_ return UINT64_MAX; } +static struct aws_byte_cursor s_aws_mqtt_rr_operation_get_subscription_topic_filter( + struct aws_mqtt_rr_client_operation *operation) { + if (operation->type == AWS_MRROT_REQUEST) { + return operation->storage.request_storage.options.subscription_topic_filter; + } else { + return operation->storage.streaming_storage.options.topic_filter; + } +} + +/* + HandleAcquireSubscriptionResult(operation, result) [Event Loop, Service Task Loop]: + + // invariant, BLOCKED is not possible, it was already handled + If result == {No Capacity, Failure} + If operation is streaming + Invoke failure callback + operation.state <- TERMINAL + else + CompleteRequestOperation(operation, error) + return + + // invariant, must be SUBSCRIBING or SUBSCRIBED at this point + Add operation to client's topic filter table + + If operation is streaming + operation.state <- {SUBSCRIBING, SUBSCRIBED} + + If operation is request + if result == SUBSCRIBING + operation.state <- SUBSCRIBING + else // (SUBSCRIBED) + MakeRequest(op) +*/ + +static bool s_is_operation_in_list(const struct aws_mqtt_rr_client_operation *operation) { + return aws_linked_list_node_prev_is_valid(&operation->node) && aws_linked_list_node_next_is_valid(&operation->node); +} + +static int s_add_operation_to_subscription_topic_filter_table( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_rr_client_operation *operation) { + + struct aws_byte_cursor topic_filter_cursor = s_aws_mqtt_rr_operation_get_subscription_topic_filter(operation); + + struct aws_hash_element *element = NULL; + if (aws_hash_table_find(&client->operation_lists_by_subscription_filter, &topic_filter_cursor, &element)) { + return aws_raise_error(AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR); + } + + struct aws_rr_operation_list_topic_filter_entry *entry = NULL; + if (element == NULL) { + struct aws_byte_cursor topic_filter_cursor = s_aws_mqtt_rr_operation_get_subscription_topic_filter(operation); + entry = s_aws_rr_operation_list_topic_filter_entry_new(client->allocator, topic_filter_cursor); + aws_hash_table_put(&client->operation_lists_by_subscription_filter, &entry->topic_filter_cursor, entry, NULL); + } else { + entry = element->value; + } + + AWS_FATAL_ASSERT(entry != NULL); + + if (s_is_operation_in_list(operation)) { + aws_linked_list_remove(&operation->node); + } + + aws_linked_list_push_back(&entry->operations, &operation->node); + + return AWS_OP_SUCCESS; +} + +static void s_make_mqtt_request( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_rr_client_operation *operation) { + (void)client; + + AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); + + // TODO: NYI +} + +static void s_handle_operation_subscribe_result( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_rr_client_operation *operation, + enum aws_acquire_subscription_result_type subscribe_result) { + if (subscribe_result == AASRT_FAILURE || subscribe_result == AASRT_NO_CAPACITY) { + int error_code = (subscribe_result == AASRT_NO_CAPACITY) + ? AWS_ERROR_MQTT_REQUEST_RESPONSE_NO_SUBSCRIPTION_CAPACITY + : AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE; + s_request_response_fail_operation(operation, error_code); + return; + } + + if (s_add_operation_to_subscription_topic_filter_table(client, operation)) { + s_request_response_fail_operation(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR); + return; + } + + if (subscribe_result == AASRT_SUBSCRIBING) { + operation->state = AWS_MRROS_PENDING_SUBSCRIPTION; + return; + } + + if (operation->type == AWS_MRROT_STREAMING) { + operation->state = AWS_MRROS_SUBSCRIBED; + } else { + s_make_mqtt_request(client, operation); + } +} + +static enum aws_rr_subscription_type s_rr_operation_type_to_subscription_type( + enum aws_mqtt_request_response_operation_type type) { + if (type == AWS_MRROT_REQUEST) { + return ARRST_REQUEST_RESPONSE; + } + + return ARRST_EVENT_STREAM; +} + static void s_mqtt_request_response_service_task_fn( struct aws_task *task, void *arg, @@ -814,7 +987,26 @@ static void s_mqtt_request_response_service_task_fn( // timeouts s_check_for_operation_timeouts(client); - // TODO: operation intake and service + while (!aws_linked_list_empty(&client->operation_queue)) { + struct aws_linked_list_node *head = aws_linked_list_front(&client->operation_queue); + struct aws_mqtt_rr_client_operation *head_operation = + AWS_CONTAINER_OF(head, struct aws_mqtt_rr_client_operation, node); + + struct aws_rr_acquire_subscription_options subscribe_options = { + .topic_filter = s_aws_mqtt_rr_operation_get_subscription_topic_filter(head_operation), + .operation_id = head_operation->id, + .type = s_rr_operation_type_to_subscription_type(head_operation->type), + }; + + enum aws_acquire_subscription_result_type subscribe_result = + aws_rr_subscription_manager_acquire_subscription(&client->subscription_manager, &subscribe_options); + if (subscribe_result == AASRT_BLOCKED) { + break; + } + + aws_linked_list_pop_front(&client->operation_queue); + s_handle_operation_subscribe_result(client, head_operation, subscribe_result); + } // schedule next service client->scheduled_service_timepoint_ns = s_mqtt_request_response_client_get_next_service_time(client); @@ -1112,15 +1304,6 @@ static void s_aws_mqtt_request_operation_storage_clean_up(struct aws_mqtt_reques aws_byte_buf_clean_up(&storage->operation_data); } -static struct aws_byte_cursor s_aws_mqtt_rr_operation_get_subscription_topic_filter( - struct aws_mqtt_rr_client_operation *operation) { - if (operation->type == AWS_MRROT_REQUEST) { - return operation->storage.request_storage.options.subscription_topic_filter; - } else { - return operation->storage.streaming_storage.options.topic_filter; - } -} - static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, enum aws_task_status status) { (void)task; (void)status; @@ -1131,7 +1314,9 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, aws_hash_table_remove(&client->operations, &operation->id, NULL, NULL); s_remove_operation_from_timeout_queue(operation); - aws_linked_list_remove(&operation->node); + if (s_is_operation_in_list(operation)) { + aws_linked_list_remove(&operation->node); + } if (client->state != AWS_RRCS_SHUTTING_DOWN) { struct aws_rr_release_subscription_options release_options = { From 38757203ff3e72726afd5d0164c17a58ab0d8062 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 19 Mar 2024 11:40:07 -0700 Subject: [PATCH 080/124] aliasing error --- source/request-response/request_response_client.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 07f4a5aa..36dae9af 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -903,7 +903,6 @@ static int s_add_operation_to_subscription_topic_filter_table( struct aws_rr_operation_list_topic_filter_entry *entry = NULL; if (element == NULL) { - struct aws_byte_cursor topic_filter_cursor = s_aws_mqtt_rr_operation_get_subscription_topic_filter(operation); entry = s_aws_rr_operation_list_topic_filter_entry_new(client->allocator, topic_filter_cursor); aws_hash_table_put(&client->operation_lists_by_subscription_filter, &entry->topic_filter_cursor, entry, NULL); } else { @@ -1329,7 +1328,6 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, /* NYI: - Remove from topic filter table Remove from correlation token table */ From e3b0eee835e5824b78f8448ad79c9e2460e5cf45 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 22 Mar 2024 11:28:40 -0700 Subject: [PATCH 081/124] Logging checkpoint --- .../request_response_client.c | 357 +++++++++++++++--- 1 file changed, 295 insertions(+), 62 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 36dae9af..0419e0fa 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -27,14 +27,64 @@ enum aws_mqtt_request_response_operation_state { AWS_MRROS_NONE, // creation -> in event loop enqueue AWS_MRROS_QUEUED, // in event loop queue -> non blocked response from subscription manager AWS_MRROS_PENDING_SUBSCRIPTION, // subscribing response from sub manager -> subscription success/failure event - AWS_MRROS_AWAITING_RESPONSE, // (request only) subscription success -> (publish failure OR correlated response + AWS_MRROS_PENDING_RESPONSE, // (request only) subscription success -> (publish failure OR correlated response // received) AWS_MRROS_SUBSCRIBED, // (streaming only) subscription success -> (operation finished OR subscription ended event) AWS_MRROS_TERMINAL, // (streaming only) (subscription failure OR subscription ended) -> operation close/terminate - AWS_MRROS_PENDING_DESTROY, // (request only) the request operation's destroy task has been scheduled but not yet + AWS_MRROS_PENDING_DESTROY, // (request only) the operation's destroy task has been scheduled but not yet // executed }; +const char *s_aws_mqtt_request_response_operation_state_to_c_str(enum aws_mqtt_request_response_operation_state state) { + switch (state) { + case AWS_MRROS_NONE: + return "NONE"; + + case AWS_MRROS_QUEUED: + return "QUEUED"; + + case AWS_MRROS_PENDING_SUBSCRIPTION: + return "PENDING_SUBSCRIPTION"; + + case AWS_MRROS_PENDING_RESPONSE: + return "PENDING_RESPONSE"; + + case AWS_MRROS_SUBSCRIBED: + return "SUBSCRIBED"; + + case AWS_MRROS_TERMINAL: + return "TERMINAL"; + + case AWS_MRROS_PENDING_DESTROY: + return "PENDING_DESTROY"; + + default: + return "Unknown"; + } +} + +const char *s_aws_acquire_subscription_result_type(enum aws_acquire_subscription_result_type result) { + switch (result) { + case AASRT_SUBSCRIBED: + return "SUBSCRIBED"; + + case AASRT_SUBSCRIBING: + return "SUBSCRIBING"; + + case AASRT_BLOCKED: + return "BLOCKED"; + + case AASRT_NO_CAPACITY: + return "NO_CAPACITY"; + + case AASRT_FAILURE: + return "FAILURE"; + + default: + return "Unknown"; + } +} + /* Client Tables/Lookups @@ -497,6 +547,25 @@ static void s_remove_operation_from_timeout_queue(struct aws_mqtt_rr_client_oper } } +static void s_change_operation_state( + struct aws_mqtt_rr_client_operation *operation, + enum aws_mqtt_request_response_operation_state new_state) { + enum aws_mqtt_request_response_operation_state old_state = operation->state; + if (old_state == new_state) { + return; + } + + operation->state = new_state; + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response operation %" PRIu64 " changing state from %s to %s", + (void *)operation->client_internal_ref, + operation->id, + s_aws_mqtt_request_response_operation_state_to_c_str(old_state), + s_aws_mqtt_request_response_operation_state_to_c_str(new_state)); +} + static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_operation *operation, int error_code) { AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); AWS_FATAL_ASSERT(error_code != AWS_ERROR_SUCCESS); @@ -505,6 +574,14 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ return; } + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response operation with id:%" PRIu64 " failed with error code %d(%s)", + (void *)operation->client_internal_ref, + operation->id, + error_code, + aws_error_debug_str(error_code)); + aws_mqtt_request_operation_completion_fn *completion_callback = operation->storage.request_storage.options.completion_callback; void *user_data = operation->storage.request_storage.options.user_data; @@ -513,7 +590,7 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ (*completion_callback)(NULL, error_code, user_data); } - operation->state = AWS_MRROS_PENDING_DESTROY; + s_change_operation_state(operation, AWS_MRROS_PENDING_DESTROY); aws_mqtt_rr_client_operation_release(operation); } @@ -526,6 +603,14 @@ static void s_halt_streaming_operation_with_failure(struct aws_mqtt_rr_client_op return; } + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: streaming operation with id:%" PRIu64 " halted with error code %d(%s)", + (void *)operation->client_internal_ref, + operation->id, + error_code, + aws_error_debug_str(error_code)); + aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback = operation->storage.streaming_storage.options.subscription_status_callback; @@ -534,7 +619,7 @@ static void s_halt_streaming_operation_with_failure(struct aws_mqtt_rr_client_op (*subscription_status_callback)(ARRSET_STREAMING_SUBSCRIPTION_HALTED, error_code, user_data); } - operation->state = AWS_MRROS_TERMINAL; + s_change_operation_state(operation, AWS_MRROS_TERMINAL); } static void s_request_response_fail_operation(struct aws_mqtt_rr_client_operation *operation, int error_code) { @@ -611,6 +696,9 @@ static void s_mqtt_request_response_client_wake_service(struct aws_mqtt_request_ client->scheduled_service_timepoint_ns = now; aws_event_loop_schedule_task_now(client->loop, &client->service_task); + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, "id=%p: request-response client service task woke", (void *)client); } } @@ -649,10 +737,47 @@ static void s_aws_rr_client_protocol_adapter_subscription_event_callback( aws_rr_subscription_manager_on_protocol_adapter_subscription_event(&rr_client->subscription_manager, event); } +static void s_apply_publish_to_streaming_operation_list( + struct aws_rr_operation_list_topic_filter_entry *entry, + const struct aws_protocol_adapter_incoming_publish_event *publish_event) { + AWS_FATAL_ASSERT(entry != NULL); + + struct aws_linked_list_node *node = aws_linked_list_begin(&entry->operations); + while (node != aws_linked_list_end(&entry->operations)) { + struct aws_mqtt_rr_client_operation *operation = + AWS_CONTAINER_OF(node, struct aws_mqtt_rr_client_operation, node); + node = aws_linked_list_next(node); + + if (operation->type != AWS_MRROT_STREAMING) { + continue; + } + + if (operation->state == AWS_MRROS_PENDING_DESTROY || operation->state == AWS_MRROS_TERMINAL) { + continue; + } + + aws_mqtt_streaming_operation_incoming_publish_fn *incoming_publish_callback = + operation->storage.streaming_storage.options.incoming_publish_callback; + if (!incoming_publish_callback) { + continue; + } + + void *user_data = operation->storage.streaming_storage.options.user_data; + (*incoming_publish_callback)(publish_event->payload, user_data); + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on topic '" PRInSTR + "' routed to streaming operation %" PRIu64, + (void *)operation->client_internal_ref, + AWS_BYTE_CURSOR_PRI(publish_event->topic), + operation->id); + } +} + static void s_aws_rr_client_protocol_adapter_incoming_publish_callback( - const struct aws_protocol_adapter_incoming_publish_event *publish, + const struct aws_protocol_adapter_incoming_publish_event *publish_event, void *user_data) { - (void)publish; struct aws_mqtt_request_response_client *rr_client = user_data; @@ -662,7 +787,23 @@ static void s_aws_rr_client_protocol_adapter_incoming_publish_callback( return; } - /* NYI */ + /* Streaming operation handling */ + struct aws_hash_element *subscription_filter_element = NULL; + if (aws_hash_table_find( + &rr_client->operation_lists_by_subscription_filter, &publish_event->topic, &subscription_filter_element) == + AWS_OP_SUCCESS) { + if (subscription_filter_element != NULL) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on topic '" PRInSTR "'", + (void *)rr_client, + AWS_BYTE_CURSOR_PRI(publish_event->topic)); + + s_apply_publish_to_streaming_operation_list(subscription_filter_element->value, publish_event); + } + } + + /* Request-Response handling NYI */ } static void s_aws_rr_client_protocol_adapter_terminate_callback(void *user_data) { @@ -683,6 +824,11 @@ static void s_aws_rr_client_protocol_adapter_connection_event_callback( return; } + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client applying connection event to subscription manager", + (void *)rr_client); + aws_rr_subscription_manager_on_protocol_adapter_connection_event(&rr_client->subscription_manager, event); } @@ -826,12 +972,6 @@ static void s_check_for_operation_timeouts(struct aws_mqtt_request_response_clie /* Ack timeout for this operation has been reached */ aws_priority_queue_pop(timeout_queue, &next_operation_by_timeout); - AWS_LOGF_INFO( - AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request operation with id:%" PRIu64 " has timed out", - (void *)client, - next_operation_by_timeout->id); - s_request_response_fail_operation(next_operation_by_timeout, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT); done = aws_priority_queue_size(timeout_queue) == 0; @@ -861,31 +1001,7 @@ static struct aws_byte_cursor s_aws_mqtt_rr_operation_get_subscription_topic_fil } } -/* - HandleAcquireSubscriptionResult(operation, result) [Event Loop, Service Task Loop]: - - // invariant, BLOCKED is not possible, it was already handled - If result == {No Capacity, Failure} - If operation is streaming - Invoke failure callback - operation.state <- TERMINAL - else - CompleteRequestOperation(operation, error) - return - - // invariant, must be SUBSCRIBING or SUBSCRIBED at this point - Add operation to client's topic filter table - - If operation is streaming - operation.state <- {SUBSCRIBING, SUBSCRIBED} - - If operation is request - if result == SUBSCRIBING - operation.state <- SUBSCRIBING - else // (SUBSCRIBED) - MakeRequest(op) -*/ - +/* TODO: add aws-c-common API? */ static bool s_is_operation_in_list(const struct aws_mqtt_rr_client_operation *operation) { return aws_linked_list_node_prev_is_valid(&operation->node) && aws_linked_list_node_next_is_valid(&operation->node); } @@ -905,6 +1021,11 @@ static int s_add_operation_to_subscription_topic_filter_table( if (element == NULL) { entry = s_aws_rr_operation_list_topic_filter_entry_new(client->allocator, topic_filter_cursor); aws_hash_table_put(&client->operation_lists_by_subscription_filter, &entry->topic_filter_cursor, entry, NULL); + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client adding topic filter '" PRInSTR "' to subscriptions table", + (void *)client, + AWS_BYTE_CURSOR_PRI(topic_filter_cursor)); } else { entry = element->value; } @@ -915,6 +1036,14 @@ static int s_add_operation_to_subscription_topic_filter_table( aws_linked_list_remove(&operation->node); } + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client adding operation %" PRIu64 " to subscription table with topic_filter '" PRInSTR + "'", + (void *)client, + operation->id, + AWS_BYTE_CURSOR_PRI(topic_filter_cursor)); + aws_linked_list_push_back(&entry->operations, &operation->node); return AWS_OP_SUCCESS; @@ -948,12 +1077,12 @@ static void s_handle_operation_subscribe_result( } if (subscribe_result == AASRT_SUBSCRIBING) { - operation->state = AWS_MRROS_PENDING_SUBSCRIPTION; + s_change_operation_state(operation, AWS_MRROS_PENDING_SUBSCRIPTION); return; } if (operation->type == AWS_MRROT_STREAMING) { - operation->state = AWS_MRROS_SUBSCRIBED; + s_change_operation_state(operation, AWS_MRROS_SUBSCRIBED); } else { s_make_mqtt_request(client, operation); } @@ -968,6 +1097,38 @@ static enum aws_rr_subscription_type s_rr_operation_type_to_subscription_type( return ARRST_EVENT_STREAM; } +static void s_process_queued_operations(struct aws_mqtt_request_response_client *client) { + while (!aws_linked_list_empty(&client->operation_queue)) { + struct aws_linked_list_node *head = aws_linked_list_front(&client->operation_queue); + struct aws_mqtt_rr_client_operation *head_operation = + AWS_CONTAINER_OF(head, struct aws_mqtt_rr_client_operation, node); + + struct aws_rr_acquire_subscription_options subscribe_options = { + .topic_filter = s_aws_mqtt_rr_operation_get_subscription_topic_filter(head_operation), + .operation_id = head_operation->id, + .type = s_rr_operation_type_to_subscription_type(head_operation->type), + }; + + enum aws_acquire_subscription_result_type subscribe_result = + aws_rr_subscription_manager_acquire_subscription(&client->subscription_manager, &subscribe_options); + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client intake, queued operation %" PRIu64 + " yielded acquire subscription result: %s", + (void *)client, + head_operation->id, + s_aws_acquire_subscription_result_type(subscribe_result)); + + if (subscribe_result == AASRT_BLOCKED) { + break; + } + + aws_linked_list_pop_front(&client->operation_queue); + s_handle_operation_subscribe_result(client, head_operation, subscribe_result); + } +} + static void s_mqtt_request_response_service_task_fn( struct aws_task *task, void *arg, @@ -986,31 +1147,19 @@ static void s_mqtt_request_response_service_task_fn( // timeouts s_check_for_operation_timeouts(client); - while (!aws_linked_list_empty(&client->operation_queue)) { - struct aws_linked_list_node *head = aws_linked_list_front(&client->operation_queue); - struct aws_mqtt_rr_client_operation *head_operation = - AWS_CONTAINER_OF(head, struct aws_mqtt_rr_client_operation, node); - - struct aws_rr_acquire_subscription_options subscribe_options = { - .topic_filter = s_aws_mqtt_rr_operation_get_subscription_topic_filter(head_operation), - .operation_id = head_operation->id, - .type = s_rr_operation_type_to_subscription_type(head_operation->type), - }; - - enum aws_acquire_subscription_result_type subscribe_result = - aws_rr_subscription_manager_acquire_subscription(&client->subscription_manager, &subscribe_options); - if (subscribe_result == AASRT_BLOCKED) { - break; - } - - aws_linked_list_pop_front(&client->operation_queue); - s_handle_operation_subscribe_result(client, head_operation, subscribe_result); - } + // operation queue + s_process_queued_operations(client); // schedule next service client->scheduled_service_timepoint_ns = s_mqtt_request_response_client_get_next_service_time(client); aws_event_loop_schedule_task_future( client->loop, &client->service_task, client->scheduled_service_timepoint_ns); + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client service, next timepoint: %" PRIu64, + (void *)client, + client->scheduled_service_timepoint_ns); } } @@ -1265,6 +1414,12 @@ static void s_mqtt_rr_client_submit_operation(struct aws_task *task, void *arg, goto done; } + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client, queuing operation %" PRIu64, + (void *)client, + operation->id); + // add appropriate client table entries aws_hash_table_put(&client->operations, &operation->id, operation, NULL); @@ -1276,7 +1431,7 @@ static void s_mqtt_rr_client_submit_operation(struct aws_task *task, void *arg, // enqueue aws_linked_list_push_back(&operation->client_internal_ref->operation_queue, &operation->node); - operation->state = AWS_MRROS_QUEUED; + s_change_operation_state(operation, AWS_MRROS_QUEUED); s_mqtt_request_response_client_wake_service(operation->client_internal_ref); @@ -1374,7 +1529,7 @@ static void s_aws_mqtt_rr_client_operation_init_shared( operation->client_internal_ref = s_aws_mqtt_request_response_client_acquire_internal(client); operation->id = s_aws_mqtt_request_response_client_allocate_operation_id(client); - operation->state = AWS_MRROS_NONE; + s_change_operation_state(operation, AWS_MRROS_NONE); aws_task_init( &operation->submit_task, @@ -1442,6 +1597,76 @@ void s_aws_mqtt_request_operation_storage_init_from_options( storage->options.response_paths = storage->operation_response_paths.data; } +static void s_log_request_response_operation( + struct aws_mqtt_rr_client_operation *operation, + struct aws_mqtt_request_response_client *client) { + struct aws_logger *log_handle = aws_logger_get_conditional(AWS_LS_MQTT_REQUEST_RESPONSE, AWS_LL_DEBUG); + if (log_handle == NULL) { + return; + } + + struct aws_mqtt_request_operation_options *options = &operation->storage.request_storage.options; + + AWS_LOGUF( + log_handle, + AWS_LL_DEBUG, + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client operation %" PRIu64, + ": subscription topic filter: '" PRInSTR "'", + (void *)client, + operation->id, + AWS_BYTE_CURSOR_PRI(options->subscription_topic_filter)); + AWS_LOGUF( + log_handle, + AWS_LL_DEBUG, + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client operation %" PRIu64, + ": correlation token: '" PRInSTR "'", + (void *)client, + operation->id, + AWS_BYTE_CURSOR_PRI(options->correlation_token)); + AWS_LOGUF( + log_handle, + AWS_LL_DEBUG, + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client operation %" PRIu64, + ": publish topic: '" PRInSTR "'", + (void *)client, + operation->id, + AWS_BYTE_CURSOR_PRI(options->publish_topic)); + + AWS_LOGUF( + log_handle, + AWS_LL_DEBUG, + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client operation %" PRIu64, + ": %zu response paths:", + (void *)client, + operation->id, + options->response_path_count); + for (size_t i = 0; i < options->response_path_count; ++i) { + struct aws_mqtt_request_operation_response_path *response_path = &options->response_paths[i]; + AWS_LOGUF( + log_handle, + AWS_LL_DEBUG, + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client operation %" PRIu64, + ": response path %zu topic '" PRInSTR "'", + (void *)client, + operation->id, + AWS_BYTE_CURSOR_PRI(response_path->topic)); + AWS_LOGUF( + log_handle, + AWS_LL_DEBUG, + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client operation %" PRIu64, + ": response path %zu correlation token path '" PRInSTR "'", + (void *)client, + operation->id, + AWS_BYTE_CURSOR_PRI(response_path->correlation_token_json_path)); + } +} + int aws_mqtt_request_response_client_submit_request( struct aws_mqtt_request_response_client *client, const struct aws_mqtt_request_operation_options *request_options) { @@ -1473,6 +1698,14 @@ int aws_mqtt_request_response_client_submit_request( &operation->storage.request_storage, allocator, request_options); s_aws_mqtt_rr_client_operation_init_shared(operation, client); + AWS_LOGF_INFO( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client - submitting request-response operation with id " PRIu64, + (void *)client, + operation->id); + + s_log_request_response_operation(operation, client); + aws_event_loop_schedule_task_now(client->loop, &operation->submit_task); return AWS_OP_SUCCESS; From aabe34ddc9863199c7b1d13fc1d6f1814fe31065 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 22 Mar 2024 14:41:38 -0700 Subject: [PATCH 082/124] Logging polish --- .../request_response_client.c | 60 ++++++++++++++----- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 0419e0fa..b648e9a0 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -576,7 +576,7 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response operation with id:%" PRIu64 " failed with error code %d(%s)", + "id=%p: request-response operation %" PRIu64 " failed with error code %d(%s)", (void *)operation->client_internal_ref, operation->id, error_code, @@ -605,7 +605,7 @@ static void s_halt_streaming_operation_with_failure(struct aws_mqtt_rr_client_op AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: streaming operation with id:%" PRIu64 " halted with error code %d(%s)", + "id=%p: streaming operation %" PRIu64 " halted with error code %d(%s)", (void *)operation->client_internal_ref, operation->id, error_code, @@ -1611,26 +1611,25 @@ static void s_log_request_response_operation( log_handle, AWS_LL_DEBUG, AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client operation %" PRIu64, - ": subscription topic filter: '" PRInSTR "'", + "id=%p: request-response client operation %" PRIu64 " - subscription topic filter: '" PRInSTR "'", (void *)client, operation->id, AWS_BYTE_CURSOR_PRI(options->subscription_topic_filter)); + AWS_LOGUF( log_handle, AWS_LL_DEBUG, AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client operation %" PRIu64, - ": correlation token: '" PRInSTR "'", + "id=%p: request-response client operation %" PRIu64 " - correlation token: '" PRInSTR "'", (void *)client, operation->id, AWS_BYTE_CURSOR_PRI(options->correlation_token)); + AWS_LOGUF( log_handle, AWS_LL_DEBUG, AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client operation %" PRIu64, - ": publish topic: '" PRInSTR "'", + "id=%p: request-response client operation %" PRIu64 " - publish topic: '" PRInSTR "'", (void *)client, operation->id, AWS_BYTE_CURSOR_PRI(options->publish_topic)); @@ -1639,30 +1638,33 @@ static void s_log_request_response_operation( log_handle, AWS_LL_DEBUG, AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client operation %" PRIu64, - ": %zu response paths:", + "id=%p: request-response client operation %" PRIu64 " - %zu response paths:", (void *)client, operation->id, options->response_path_count); + for (size_t i = 0; i < options->response_path_count; ++i) { struct aws_mqtt_request_operation_response_path *response_path = &options->response_paths[i]; + AWS_LOGUF( log_handle, AWS_LL_DEBUG, AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client operation %" PRIu64, - ": response path %zu topic '" PRInSTR "'", + "id=%p: request-response client operation %" PRIu64 " - response path %zu topic '" PRInSTR "'", (void *)client, operation->id, + i, AWS_BYTE_CURSOR_PRI(response_path->topic)); + AWS_LOGUF( log_handle, AWS_LL_DEBUG, AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client operation %" PRIu64, - ": response path %zu correlation token path '" PRInSTR "'", + "id=%p: request-response client operation %" PRIu64 " - response path %zu correlation token path '" PRInSTR + "'", (void *)client, operation->id, + i, AWS_BYTE_CURSOR_PRI(response_path->correlation_token_json_path)); } } @@ -1700,7 +1702,7 @@ int aws_mqtt_request_response_client_submit_request( AWS_LOGF_INFO( AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client - submitting request-response operation with id " PRIu64, + "id=%p: request-response client - submitting request-response operation with id %" PRIu64, (void *)client, operation->id); @@ -1724,6 +1726,26 @@ void s_aws_mqtt_streaming_operation_storage_init_from_options( aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.topic_filter) == AWS_OP_SUCCESS); } +static void s_log_streaming_operation( + struct aws_mqtt_rr_client_operation *operation, + struct aws_mqtt_request_response_client *client) { + struct aws_logger *log_handle = aws_logger_get_conditional(AWS_LS_MQTT_REQUEST_RESPONSE, AWS_LL_DEBUG); + if (log_handle == NULL) { + return; + } + + struct aws_mqtt_streaming_operation_options *options = &operation->storage.streaming_storage.options; + + AWS_LOGUF( + log_handle, + AWS_LL_DEBUG, + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client streaming operation %" PRIu64 ": topic filter: '" PRInSTR "'", + (void *)client, + operation->id, + AWS_BYTE_CURSOR_PRI(options->topic_filter)); +} + struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_streaming_operation( struct aws_mqtt_request_response_client *client, const struct aws_mqtt_streaming_operation_options *streaming_options) { @@ -1750,6 +1772,14 @@ struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_str &operation->storage.streaming_storage, allocator, streaming_options); s_aws_mqtt_rr_client_operation_init_shared(operation, client); + AWS_LOGF_INFO( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client - submitting streaming operation with id %" PRIu64, + (void *)client, + operation->id); + + s_log_streaming_operation(operation, client); + aws_event_loop_schedule_task_now(client->loop, &operation->submit_task); return operation; From 93283ab6c412dec7bfcb4616eb8923969a98fe98 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 28 Mar 2024 05:55:19 -0700 Subject: [PATCH 083/124] Checkpoint --- .../request_response_client.h | 26 ++++- .../request_response_client_tests.c | 98 +++++++++++++++++++ 2 files changed, 121 insertions(+), 3 deletions(-) diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 450675d1..5cad26b7 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -8,8 +8,6 @@ #include -#include - struct aws_mqtt_request_response_client; struct aws_mqtt_client_connection; struct aws_mqtt5_client; @@ -46,8 +44,30 @@ struct aws_mqtt_request_operation_storage { struct aws_byte_buf operation_data; }; +/* + * Describes a change to the state of a request operation subscription + */ +enum aws_rr_streaming_subscription_event_type { + + /* + * The streaming operation is successfully subscribed to its topic (filter) + */ + ARRSSET_SUBSCRIPTION_ESTABLISHED, + + /* + * The streaming operation has temporarily lost its subscription to its topic (filter) + */ + ARRSSET_SUBSCRIPTION_LOST, + + /* + * The streaming operation has entered a terminal state where it has given up trying to subscribe + * to its topic (filter). This is always due to user error (bad topic filter or IoT Core permission policy). + */ + ARRSSET_SUBSCRIPTION_HALTED, +}; + typedef void(aws_mqtt_streaming_operation_subscription_status_fn)( - enum aws_rr_subscription_event_type status, + enum aws_rr_streaming_subscription_event_type status, int error_code, void *user_data); typedef void(aws_mqtt_streaming_operation_incoming_publish_fn)(struct aws_byte_cursor payload, void *user_data); diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 5dc0fa5c..7ababf77 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -330,6 +330,104 @@ static void s_rrc_wait_on_streaming_termination( aws_mutex_unlock(&fixture->lock); } +struct rrc_streaming_event_wait_context { + struct aws_byte_cursor operation_key; + struct aws_rr_client_test_fixture *fixture; + size_t event_count; +}; + +static bool s_streaming_operation_has_n_publishes(void *context) { + struct rrc_streaming_event_wait_context *streaming_publish_context = context; + + struct aws_hash_element *element = NULL; + aws_hash_table_find( + &streaming_publish_context->fixture->streaming_records, &streaming_publish_context->operation_key, &element); + + AWS_FATAL_ASSERT(element != NULL && element->value != NULL); + + struct aws_rr_client_fixture_streaming_record *record = element->value; + + return aws_array_list_length(&record->publishes) >= streaming_publish_context->event_count; +} + +static void s_rrc_wait_for_n_streaming_publishes( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor key, + size_t count) { + struct rrc_streaming_event_wait_context context = { + .operation_key = key, + .fixture = fixture, + .event_count = count, + }; + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred(&fixture->signal, &fixture->lock, s_streaming_operation_has_n_publishes, &context); + aws_mutex_unlock(&fixture->lock); +} + +static int s_rrc_verify_streaming_publishes( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor key, + size_t expected_publish_count, + struct aws_byte_cursor *expected_publishes) { + + aws_mutex_lock(&fixture->lock); + + struct aws_hash_element *element = NULL; + aws_hash_table_find(&fixture->streaming_records, &key, &element); + + AWS_FATAL_ASSERT(element != NULL && element->value != NULL); + + struct aws_rr_client_fixture_streaming_record *record = element->value; + + size_t actual_publish_count = aws_array_list_length(&record->publishes); + ASSERT_INT_EQUALS(expected_publish_count, actual_publish_count); + + for (size_t i = 0; i < actual_publish_count; ++i) { + struct aws_byte_buf actual_payload; + aws_array_list_get_at(&record->publishes, &actual_payload, i); + + struct aws_byte_cursor *expected_payload = &expected_publishes[i]; + + ASSERT_BIN_ARRAYS_EQUALS( + expected_payload->ptr, expected_payload->len, actual_payload.buffer, actual_payload.len); + } + + aws_mutex_unlock(&fixture->lock); + + return AWS_OP_SUCCESS; +} + +static bool s_streaming_operation_has_n_subscription_events(void *context) { + struct rrc_streaming_event_wait_context *streaming_publish_context = context; + + struct aws_hash_element *element = NULL; + aws_hash_table_find( + &streaming_publish_context->fixture->streaming_records, &streaming_publish_context->operation_key, &element); + + AWS_FATAL_ASSERT(element != NULL && element->value != NULL); + + struct aws_rr_client_fixture_streaming_record *record = element->value; + + return aws_array_list_length(&record->subscription_events) >= streaming_publish_context->event_count; +} + +static void s_rrc_wait_for_n_streaming_subscription_events( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor key, + size_t count) { + struct rrc_streaming_event_wait_context context = { + .operation_key = key, + .fixture = fixture, + .event_count = count, + }; + + aws_mutex_lock(&fixture->lock); + aws_condition_variable_wait_pred( + &fixture->signal, &fixture->lock, s_streaming_operation_has_n_subscription_events, &context); + aws_mutex_unlock(&fixture->lock); +} + static int s_rrc_verify_streaming_record_subscription_events( struct aws_rr_client_test_fixture *fixture, struct aws_byte_cursor key, From d58f19de47d32859f9194feb7ff4dde5adeb9e37 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 26 Mar 2024 13:19:57 -0700 Subject: [PATCH 084/124] Checkpoint --- .../request-response/request_response_client.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index b648e9a0..e7a62420 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -111,6 +111,9 @@ operation completion/destruction also checked for empty and removed from table */ +/* + * This is the (key and) value in hash table (4) above. + */ struct aws_rr_operation_list_topic_filter_entry { struct aws_allocator *allocator; @@ -120,7 +123,7 @@ struct aws_rr_operation_list_topic_filter_entry { struct aws_linked_list operations; }; -struct aws_rr_operation_list_topic_filter_entry *s_aws_rr_operation_list_topic_filter_entry_new( +static struct aws_rr_operation_list_topic_filter_entry *s_aws_rr_operation_list_topic_filter_entry_new( struct aws_allocator *allocator, struct aws_byte_cursor topic_filter) { struct aws_rr_operation_list_topic_filter_entry *entry = @@ -135,7 +138,7 @@ struct aws_rr_operation_list_topic_filter_entry *s_aws_rr_operation_list_topic_f return entry; } -void s_aws_rr_operation_list_topic_filter_entry_destroy(struct aws_rr_operation_list_topic_filter_entry *entry) { +static void s_aws_rr_operation_list_topic_filter_entry_destroy(struct aws_rr_operation_list_topic_filter_entry *entry) { if (entry == NULL) { return; } @@ -145,7 +148,7 @@ void s_aws_rr_operation_list_topic_filter_entry_destroy(struct aws_rr_operation_ aws_mem_release(entry->allocator, entry); } -void s_aws_rr_operation_list_topic_filter_entry_hash_element_destroy(void *value) { +static void s_aws_rr_operation_list_topic_filter_entry_hash_element_destroy(void *value) { s_aws_rr_operation_list_topic_filter_entry_destroy(value); } @@ -1370,6 +1373,15 @@ static bool s_are_request_operation_options_valid( return false; } + if (!aws_mqtt_is_valid_topic_filter(&request_options->subscription_topic_filter)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "(%p) rr client request options - " PRInSTR " is not a valid topic filter", + (void *)client, + AWS_BYTE_CURSOR_PRI(request_options->subscription_topic_filter)); + return false; + } + if (request_options->serialized_request.len == 0) { AWS_LOGF_ERROR( AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - empty request payload", (void *)client); From b35eb69638e4d2c81f921508f372f745e16aea62 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 29 Mar 2024 08:20:09 -0700 Subject: [PATCH 085/124] Checkpoint --- .../request-response/request_response.h | 57 ------ .../request-response/subscription_manager.h | 47 ++++- .../request_response_client.c | 167 ++++++++++++++---- tests/CMakeLists.txt | 12 ++ .../request_response_client_tests.c | 109 +++++++++++- 5 files changed, 295 insertions(+), 97 deletions(-) delete mode 100644 include/aws/mqtt/private/request-response/request_response.h diff --git a/include/aws/mqtt/private/request-response/request_response.h b/include/aws/mqtt/private/request-response/request_response.h deleted file mode 100644 index 78e7819d..00000000 --- a/include/aws/mqtt/private/request-response/request_response.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_H -#define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_H - -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -#include - -/* - * Describes a change to the state of a request operation subscription - */ -enum aws_rr_subscription_event_type { - - /* - * A request subscription subscribe succeeded - */ - ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, - - /* - * A request subscription subscribe failed - */ - ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE, - - /* - * A previously successful request subscription has ended. - * - * Under normal circumstances this can happen when - * - * (1) failure to rejoin a session - * (2) a successful unsubscribe when the subscription is no longer needed - */ - ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED, - - /* - * A streaming subscription subscribe succeeded - */ - ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, - - /* - * The protocol client failed to rejoin a session containing a previously-established streaming subscription - */ - ARRSET_STREAMING_SUBSCRIPTION_LOST, - - /* - * A streaming subscription subscribe attempt resulted in an error or reason code that the client has determined - * will result in indefinite failures to subscribe. In this case, we stop attempting to resubscribe. - * - * Situations that can lead to this: - * (1) Permission failures - * (2) Invalid topic filter - */ - ARRSET_STREAMING_SUBSCRIPTION_HALTED -}; - -#endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_H */ diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 61d0e400..a08bf09f 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -9,12 +9,57 @@ #include #include -#include struct aws_mqtt_protocol_adapter; struct aws_protocol_adapter_connection_event; struct aws_protocol_adapter_subscription_event; +/* + * Describes a change to the state of a request operation subscription + */ +enum aws_rr_subscription_event_type { + + /* + * A request subscription subscribe succeeded + */ + ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + + /* + * A request subscription subscribe failed + */ + ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE, + + /* + * A previously successful request subscription has ended. + * + * Under normal circumstances this can happen when + * + * (1) failure to rejoin a session + * (2) a successful unsubscribe when the subscription is no longer needed + */ + ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED, + + /* + * A streaming subscription subscribe succeeded + */ + ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, + + /* + * The protocol client failed to rejoin a session containing a previously-established streaming subscription + */ + ARRSET_STREAMING_SUBSCRIPTION_LOST, + + /* + * A streaming subscription subscribe attempt resulted in an error or reason code that the client has determined + * will result in indefinite failures to subscribe. In this case, we stop attempting to resubscribe. + * + * Situations that can lead to this: + * (1) Permission failures + * (2) Invalid topic filter + */ + ARRSET_STREAMING_SUBSCRIPTION_HALTED +}; + struct aws_rr_subscription_status_event { enum aws_rr_subscription_event_type type; struct aws_byte_cursor topic_filter; diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index e7a62420..6f120ede 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -305,16 +305,6 @@ static void s_aws_rr_operation_list_topic_filter_entry_hash_element_destroy(void */ -/* - OnSubscriptionStatusEvent(event) [Event Loop, top-level task from sub manager]: - - For all operations in topic_filter table list: - if operation.type == Streaming - StreamingOperationOnSubscriptionStatusEvent(operation, event) - else - RequestOperationOnSubscriptionStatusEvent(operation, event) - */ - /* HandleAcquireSubscriptionResult(operation, result) [Event Loop, Service Task Loop]: @@ -497,6 +487,24 @@ struct aws_mqtt_request_response_client { struct aws_hash_table operation_lists_by_subscription_filter; }; +struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_acquire_internal( + struct aws_mqtt_request_response_client *client) { + if (client != NULL) { + aws_ref_count_acquire(&client->internal_ref_count); + } + + return client; +} + +struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_release_internal( + struct aws_mqtt_request_response_client *client) { + if (client != NULL) { + aws_ref_count_release(&client->internal_ref_count); + } + + return NULL; +} + static void s_aws_rr_client_on_zero_internal_ref_count(void *context) { struct aws_mqtt_request_response_client *client = context; @@ -598,6 +606,16 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ aws_mqtt_rr_client_operation_release(operation); } +static void s_streaming_operation_emit_streaming_subscription_event(struct aws_mqtt_rr_client_operation *operation, enum aws_rr_streaming_subscription_event_type event_type, int error_code) { + aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback = + operation->storage.streaming_storage.options.subscription_status_callback; + + if (subscription_status_callback != NULL) { + void *user_data = operation->storage.streaming_storage.options.user_data; + (*subscription_status_callback)(event_type, error_code, user_data); + } +} + static void s_halt_streaming_operation_with_failure(struct aws_mqtt_rr_client_operation *operation, int error_code) { AWS_FATAL_ASSERT(operation->type == AWS_MRROT_STREAMING); AWS_FATAL_ASSERT(error_code != AWS_ERROR_SUCCESS); @@ -614,13 +632,7 @@ static void s_halt_streaming_operation_with_failure(struct aws_mqtt_rr_client_op error_code, aws_error_debug_str(error_code)); - aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback = - operation->storage.streaming_storage.options.subscription_status_callback; - - if (subscription_status_callback != NULL) { - void *user_data = operation->storage.streaming_storage.options.user_data; - (*subscription_status_callback)(ARRSET_STREAMING_SUBSCRIPTION_HALTED, error_code, user_data); - } + s_streaming_operation_emit_streaming_subscription_event(operation, ARRSSET_SUBSCRIPTION_HALTED, error_code); s_change_operation_state(operation, AWS_MRROS_TERMINAL); } @@ -705,6 +717,104 @@ static void s_mqtt_request_response_client_wake_service(struct aws_mqtt_request_ } } +struct aws_rr_subscription_status_event_task { + struct aws_allocator *allocator; + + struct aws_task task; + + struct aws_mqtt_request_response_client *rr_client; + + enum aws_rr_subscription_event_type type; + struct aws_byte_buf topic_filter; + uint64_t operation_id; +}; + +static void s_aws_rr_subscription_status_event_task_delete(struct aws_rr_subscription_status_event_task *task) { + if (task == NULL) { + return; + } + + aws_byte_buf_clean_up(&task->topic_filter); + s_aws_mqtt_request_response_client_release_internal(task->rr_client); + + aws_mem_release(task->allocator, task); +} + +static void s_on_streaming_operation_subscription_status_event(struct aws_mqtt_rr_client_operation *operation, struct aws_byte_cursor topic_filter, enum aws_rr_subscription_event_type event_type) { + (void)topic_filter; + + switch (event_type) { + case ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED: + if (operation->state == AWS_MRROS_PENDING_SUBSCRIPTION) { + s_change_operation_state(operation, AWS_MRROS_SUBSCRIBED); + } + + s_streaming_operation_emit_streaming_subscription_event(operation, ARRSSET_SUBSCRIPTION_ESTABLISHED, AWS_ERROR_SUCCESS); + break; + + case ARRSET_STREAMING_SUBSCRIPTION_LOST: + s_streaming_operation_emit_streaming_subscription_event(operation, ARRSSET_SUBSCRIPTION_LOST, AWS_ERROR_SUCCESS); + break; + + case ARRSET_STREAMING_SUBSCRIPTION_HALTED: + s_halt_streaming_operation_with_failure(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE); + break; + + default: + AWS_FATAL_ASSERT(false); + } +} + +static void s_handle_subscription_status_event_task(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_rr_subscription_status_event_task *event_task = arg; + + if (status == AWS_TASK_STATUS_CANCELED) { + goto done; + } + + struct aws_hash_element *element = NULL; + if (aws_hash_table_find(&event_task->rr_client->operations, &event_task->operation_id, &element) || element == NULL) { + goto done; + } + + struct aws_mqtt_rr_client_operation *operation = element->value; + + switch (event_task->type) { + case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS: + case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE: + case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED: + /* NYI */ + break; + + case ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED: + case ARRSET_STREAMING_SUBSCRIPTION_LOST: + case ARRSET_STREAMING_SUBSCRIPTION_HALTED: + s_on_streaming_operation_subscription_status_event(operation, aws_byte_cursor_from_buf(&event_task->topic_filter), event_task->type); + break; + } + +done: + + s_aws_rr_subscription_status_event_task_delete(event_task); +} + +static struct aws_rr_subscription_status_event_task *s_aws_rr_subscription_status_event_task_new(struct aws_allocator *allocator, struct aws_mqtt_request_response_client *rr_client, const struct aws_rr_subscription_status_event *event) { + struct aws_rr_subscription_status_event_task *task = aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_subscription_status_event_task)); + + task->allocator = allocator; + task->type = event->type; + task->operation_id = event->operation_id; + task->rr_client = s_aws_mqtt_request_response_client_acquire_internal(rr_client); + + aws_byte_buf_init_copy_from_cursor(&task->topic_filter, allocator, event->topic_filter); + + aws_task_init(&task->task, s_handle_subscription_status_event_task, task, "SubscriptionStatusEventTask"); + + return task; +} + static void s_aws_rr_client_subscription_status_event_callback( const struct aws_rr_subscription_status_event *event, void *userdata) { @@ -723,7 +833,9 @@ static void s_aws_rr_client_subscription_status_event_callback( AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(rr_client->loop)); AWS_FATAL_ASSERT(rr_client->state != AWS_RRCS_SHUTTING_DOWN); - /* NYI */ + struct aws_rr_subscription_status_event_task *task = s_aws_rr_subscription_status_event_task_new(rr_client->allocator, rr_client, event); + + aws_event_loop_schedule_task_now(rr_client->loop, &task->task); } static void s_aws_rr_client_protocol_adapter_subscription_event_callback( @@ -1086,6 +1198,7 @@ static void s_handle_operation_subscribe_result( if (operation->type == AWS_MRROT_STREAMING) { s_change_operation_state(operation, AWS_MRROS_SUBSCRIBED); + s_streaming_operation_emit_streaming_subscription_event(operation, ARRSSET_SUBSCRIPTION_ESTABLISHED, AWS_ERROR_SUCCESS); } else { s_make_mqtt_request(client, operation); } @@ -1302,24 +1415,6 @@ struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_releas return NULL; } -struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_acquire_internal( - struct aws_mqtt_request_response_client *client) { - if (client != NULL) { - aws_ref_count_acquire(&client->internal_ref_count); - } - - return client; -} - -struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_release_internal( - struct aws_mqtt_request_response_client *client) { - if (client != NULL) { - aws_ref_count_release(&client->internal_ref_count); - } - - return NULL; -} - ///////////////////////////////////////////////// static bool s_are_request_operation_options_valid( diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 678a8fa6..aef4955a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -535,6 +535,18 @@ add_test_case(rrc_submit_request_operation_failure_by_shutdown) add_test_case(rrc_submit_streaming_operation_and_shutdown) add_test_case(rrc_submit_request_operation_failure_by_timeout) +add_test_case(rrc_streaming_operation_success) +#add_test_case(rrc_streaming_operation_success_overlapping) +#add_test_case(rrc_streaming_operation_clean_session_reestablish_subscription) +#add_test_case(rrc_streaming_operation_resume_session) +#add_test_case(rrc_streaming_operation_first_subscribe_times_out_resub_succeeds) +#add_test_case(rrc_streaming_operation_first_subscribe_retryable_failure_resub_succeeds) +#add_test_case(rrc_streaming_operation_subscribe_unretryable_failure) +#add_test_case(rrc_streaming_operation_failure_exceeds_subscription_budget) +#add_test_case(rrc_streaming_operation_success_delayed_by_request_operations) +#add_test_case(rrc_streaming_operation_success_sandwiched_by_request_operations) + + generate_test_driver(${PROJECT_NAME}-tests) set(TEST_PAHO_CLIENT_BINARY_NAME ${PROJECT_NAME}-paho-client) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 7ababf77..04ffc16e 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -194,7 +194,7 @@ struct aws_rr_client_fixture_streaming_record { }; struct aws_rr_client_fixture_streaming_record_subscription_event { - enum aws_rr_subscription_event_type status; + enum aws_rr_streaming_subscription_event_type status; int error_code; }; @@ -245,7 +245,7 @@ static void s_aws_rr_client_fixture_streaming_record_hash_destroy(void *element) } static void s_rrc_fixture_streaming_operation_subscription_status_callback( - enum aws_rr_subscription_event_type status, + enum aws_rr_streaming_subscription_event_type status, int error_code, void *user_data) { @@ -1025,7 +1025,7 @@ static int s_rrc_submit_streaming_operation_and_shutdown_fn(struct aws_allocator struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { { - .status = ARRSET_STREAMING_SUBSCRIPTION_HALTED, + .status = ARRSSET_SUBSCRIPTION_HALTED, .error_code = AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, }, }; @@ -1069,3 +1069,106 @@ static int s_rrc_submit_request_operation_failure_by_timeout_fn(struct aws_alloc } AWS_TEST_CASE(rrc_submit_request_operation_failure_by_timeout, s_rrc_submit_request_operation_failure_by_timeout_fn) + +static struct aws_mqtt_rr_client_operation *s_create_streaming_operation(struct aws_rr_client_test_fixture *fixture, struct aws_byte_cursor record_key, struct aws_byte_cursor topic_filter) { + struct aws_rr_client_fixture_streaming_record *record = s_rrc_fixture_add_streaming_record(&fixture, record_key); + + struct aws_mqtt_streaming_operation_options streaming_options = { + .topic_filter = topic_filter, + }; + streaming_options.incoming_publish_callback = s_rrc_fixture_streaming_operation_incoming_publish_callback; + streaming_options.subscription_status_callback = s_rrc_fixture_streaming_operation_subscription_status_callback; + streaming_options.terminated_callback = s_rrc_fixture_streaming_operation_terminated_callback; + streaming_options.user_data = record; + + struct aws_mqtt_rr_client_operation *streaming_operation = + aws_mqtt_request_response_client_create_streaming_operation(fixture->rr_client, &streaming_options); + ASSERT_NOT_NULL(streaming_operation); + + return streaming_operation; +} + +static int s_rrc_publish_5(struct aws_mqtt5_client *client, struct aws_byte_cursor topic, struct aws_byte_cursor payload) { + + + return AWS_OP_SUCCESS; +} + +static int s_rrc_publish_311(struct aws_mqtt_client_connection *connection, struct aws_byte_cursor topic, struct aws_byte_cursor payload) { + + + return AWS_OP_SUCCESS; +} + +static int s_rrc_protocol_client_publish(struct aws_rr_client_test_fixture *fixture, struct aws_byte_cursor topic, struct aws_byte_cursor payload) { + + if (fixture->test_protocol == RRCP_MQTT311) { + return s_rrc_publish_5(fixture->client_test_fixture.mqtt311_test_fixture.mqtt_connection, topic, payload); + } else { + return s_rrc_publish_5(fixture->client_test_fixture.mqtt5_test_fixture.client, topic, payload); + } +} + +static int s_rrc_streaming_operation_success_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + aws_mqtt5_client_test_init_default_options(&client_test_options); + + client_test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = ??; + client_test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = ??; + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { + .client_options = &client_test_options.client_options, + .server_function_table = &client_test_options.server_function_table, + }; + + struct aws_mqtt_request_response_client_options rr_client_options = { + .max_subscriptions = 2, + .operation_timeout_seconds = 2, + }; + + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_aws_rr_client_test_fixture_init_from_mqtt5( + &fixture, allocator, &rr_client_options, &client_test_fixture_options, NULL)); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation = s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + }; + s_rrc_verify_streaming_record_subscription_events(&fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events); + + // two publishes on the mqtt client that get reflected into our subscription topic + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload1"); + s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1); + s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, + }; + s_rrc_verify_streaming_publishes(&fixture, record_key1, AWS_ARRAY_SIZE(expected_publishes), expected_publishes); + + aws_mqtt_rr_client_operation_release(operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_streaming_operation_success, s_rrc_streaming_operation_success_fn) From e98c2175573a40b3cecf6fca6925afa7dbf54ddd Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 29 Mar 2024 17:24:24 -0700 Subject: [PATCH 086/124] Checkpoint --- .../request-response/subscription_manager.h | 10 +- .../request_response_client.c | 49 +- .../request-response/subscription_manager.c | 37 +- tests/CMakeLists.txt | 17 +- .../request_response_client_tests.c | 846 +++++++++++++++++- tests/v5/mqtt5_client_tests.c | 10 +- 6 files changed, 908 insertions(+), 61 deletions(-) diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index a08bf09f..4d644af2 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -35,7 +35,6 @@ enum aws_rr_subscription_event_type { * Under normal circumstances this can happen when * * (1) failure to rejoin a session - * (2) a successful unsubscribe when the subscription is no longer needed */ ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED, @@ -57,7 +56,14 @@ enum aws_rr_subscription_event_type { * (1) Permission failures * (2) Invalid topic filter */ - ARRSET_STREAMING_SUBSCRIPTION_HALTED + ARRSET_STREAMING_SUBSCRIPTION_HALTED, + + /* + * A subscription has been unsubscribed from + * + * This particular event is global, with an operation id of 0. + */ + ARRSET_UNSUBSCRIBE_COMPLETE, }; struct aws_rr_subscription_status_event { diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 6f120ede..a4c3819f 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -606,7 +606,10 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ aws_mqtt_rr_client_operation_release(operation); } -static void s_streaming_operation_emit_streaming_subscription_event(struct aws_mqtt_rr_client_operation *operation, enum aws_rr_streaming_subscription_event_type event_type, int error_code) { +static void s_streaming_operation_emit_streaming_subscription_event( + struct aws_mqtt_rr_client_operation *operation, + enum aws_rr_streaming_subscription_event_type event_type, + int error_code) { aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback = operation->storage.streaming_storage.options.subscription_status_callback; @@ -740,7 +743,10 @@ static void s_aws_rr_subscription_status_event_task_delete(struct aws_rr_subscri aws_mem_release(task->allocator, task); } -static void s_on_streaming_operation_subscription_status_event(struct aws_mqtt_rr_client_operation *operation, struct aws_byte_cursor topic_filter, enum aws_rr_subscription_event_type event_type) { +static void s_on_streaming_operation_subscription_status_event( + struct aws_mqtt_rr_client_operation *operation, + struct aws_byte_cursor topic_filter, + enum aws_rr_subscription_event_type event_type) { (void)topic_filter; switch (event_type) { @@ -749,11 +755,13 @@ static void s_on_streaming_operation_subscription_status_event(struct aws_mqtt_r s_change_operation_state(operation, AWS_MRROS_SUBSCRIBED); } - s_streaming_operation_emit_streaming_subscription_event(operation, ARRSSET_SUBSCRIPTION_ESTABLISHED, AWS_ERROR_SUCCESS); + s_streaming_operation_emit_streaming_subscription_event( + operation, ARRSSET_SUBSCRIPTION_ESTABLISHED, AWS_ERROR_SUCCESS); break; case ARRSET_STREAMING_SUBSCRIPTION_LOST: - s_streaming_operation_emit_streaming_subscription_event(operation, ARRSSET_SUBSCRIPTION_LOST, AWS_ERROR_SUCCESS); + s_streaming_operation_emit_streaming_subscription_event( + operation, ARRSSET_SUBSCRIPTION_LOST, AWS_ERROR_SUCCESS); break; case ARRSET_STREAMING_SUBSCRIPTION_HALTED: @@ -774,8 +782,14 @@ static void s_handle_subscription_status_event_task(struct aws_task *task, void goto done; } + if (event_task->type == ARRSET_UNSUBSCRIBE_COMPLETE) { + s_mqtt_request_response_client_wake_service(event_task->rr_client); + goto done; + } + struct aws_hash_element *element = NULL; - if (aws_hash_table_find(&event_task->rr_client->operations, &event_task->operation_id, &element) || element == NULL) { + if (aws_hash_table_find(&event_task->rr_client->operations, &event_task->operation_id, &element) || + element == NULL) { goto done; } @@ -791,7 +805,11 @@ static void s_handle_subscription_status_event_task(struct aws_task *task, void case ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED: case ARRSET_STREAMING_SUBSCRIPTION_LOST: case ARRSET_STREAMING_SUBSCRIPTION_HALTED: - s_on_streaming_operation_subscription_status_event(operation, aws_byte_cursor_from_buf(&event_task->topic_filter), event_task->type); + s_on_streaming_operation_subscription_status_event( + operation, aws_byte_cursor_from_buf(&event_task->topic_filter), event_task->type); + break; + + default: break; } @@ -800,8 +818,12 @@ static void s_handle_subscription_status_event_task(struct aws_task *task, void s_aws_rr_subscription_status_event_task_delete(event_task); } -static struct aws_rr_subscription_status_event_task *s_aws_rr_subscription_status_event_task_new(struct aws_allocator *allocator, struct aws_mqtt_request_response_client *rr_client, const struct aws_rr_subscription_status_event *event) { - struct aws_rr_subscription_status_event_task *task = aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_subscription_status_event_task)); +static struct aws_rr_subscription_status_event_task *s_aws_rr_subscription_status_event_task_new( + struct aws_allocator *allocator, + struct aws_mqtt_request_response_client *rr_client, + const struct aws_rr_subscription_status_event *event) { + struct aws_rr_subscription_status_event_task *task = + aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_subscription_status_event_task)); task->allocator = allocator; task->type = event->type; @@ -833,7 +855,8 @@ static void s_aws_rr_client_subscription_status_event_callback( AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(rr_client->loop)); AWS_FATAL_ASSERT(rr_client->state != AWS_RRCS_SHUTTING_DOWN); - struct aws_rr_subscription_status_event_task *task = s_aws_rr_subscription_status_event_task_new(rr_client->allocator, rr_client, event); + struct aws_rr_subscription_status_event_task *task = + s_aws_rr_subscription_status_event_task_new(rr_client->allocator, rr_client, event); aws_event_loop_schedule_task_now(rr_client->loop, &task->task); } @@ -1198,7 +1221,8 @@ static void s_handle_operation_subscribe_result( if (operation->type == AWS_MRROT_STREAMING) { s_change_operation_state(operation, AWS_MRROS_SUBSCRIBED); - s_streaming_operation_emit_streaming_subscription_event(operation, ARRSSET_SUBSCRIPTION_ESTABLISHED, AWS_ERROR_SUCCESS); + s_streaming_operation_emit_streaming_subscription_event( + operation, ARRSSET_SUBSCRIPTION_ESTABLISHED, AWS_ERROR_SUCCESS); } else { s_make_mqtt_request(client, operation); } @@ -1533,7 +1557,10 @@ static void s_mqtt_rr_client_submit_operation(struct aws_task *task, void *arg, // NYI other tables // add to timeout priority queue - aws_priority_queue_push_ref(&client->operations_by_timeout, (void *)&operation, &operation->priority_queue_node); + if (operation->type == AWS_MRROT_REQUEST) { + aws_priority_queue_push_ref( + &client->operations_by_timeout, (void *)&operation, &operation->priority_queue_node); + } // enqueue aws_linked_list_push_back(&operation->client_internal_ref->operation_queue, &operation->node); diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 98a7206c..d025d765 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -202,6 +202,7 @@ static struct aws_rr_subscription_record *s_get_subscription_record( struct aws_subscription_stats { size_t request_response_subscriptions; size_t event_stream_subscriptions; + size_t unsubscribing_event_stream_subscriptions; }; static int s_rr_subscription_count_foreach_wrap(void *context, struct aws_hash_element *elem) { @@ -210,6 +211,9 @@ static int s_rr_subscription_count_foreach_wrap(void *context, struct aws_hash_e if (subscription->type == ARRST_EVENT_STREAM) { ++stats->event_stream_subscriptions; + if (subscription->pending_action == ARRSPAT_UNSUBSCRIBING) { + ++stats->unsubscribing_event_stream_subscriptions; + } } else { ++stats->request_response_subscriptions; } @@ -227,9 +231,10 @@ static void s_get_subscription_stats( AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, "request-response subscription manager current stats: %d event stream sub records, %d request-response sub " - "records", + "records, %d unsubscribing event stream subscriptions", (int)stats->event_stream_subscriptions, - (int)stats->request_response_subscriptions); + (int)stats->request_response_subscriptions, + (int)stats->unsubscribing_event_stream_subscriptions); } static void s_remove_listener_from_subscription_record( @@ -326,6 +331,9 @@ static const char *s_rr_subscription_event_type_to_c_str(enum aws_rr_subscriptio case ARRSET_STREAMING_SUBSCRIPTION_HALTED: return "StreamingSubscriptionHalted"; + + case ARRSET_UNSUBSCRIBE_COMPLETE: + return "UnsubscribeComplete"; } return "Unknown"; @@ -344,9 +352,10 @@ static bool s_subscription_type_matches_event_type( case ARRSET_STREAMING_SUBSCRIPTION_LOST: case ARRSET_STREAMING_SUBSCRIPTION_HALTED: return subscription_type == ARRST_EVENT_STREAM; - } - return false; + default: + return true; + } } static void s_emit_subscription_event( @@ -447,7 +456,8 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su if (!space_for_subscription) { // could space eventually free up? - if (options->type == ARRST_REQUEST_RESPONSE || stats.request_response_subscriptions > 1) { + if (options->type == ARRST_REQUEST_RESPONSE || stats.request_response_subscriptions > 1 || + stats.unsubscribing_event_stream_subscriptions > 0) { AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, "request-response subscription manager - acquire subscription for ('" PRInSTR @@ -568,7 +578,14 @@ static void s_handle_protocol_adapter_request_subscription_event( if (event->error_code == AWS_ERROR_SUCCESS) { record->status = ARRSST_NOT_SUBSCRIBED; - s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED); + + struct aws_rr_subscription_status_event event = { + .type = ARRSET_UNSUBSCRIBE_COMPLETE, + .topic_filter = record->topic_filter_cursor, + .operation_id = 0, + }; + + (*manager->config.subscription_status_callback)(&event, manager->config.userdata); } } } @@ -599,6 +616,14 @@ static void s_handle_protocol_adapter_streaming_subscription_event( if (event->error_code == AWS_ERROR_SUCCESS) { record->status = ARRSST_NOT_SUBSCRIBED; + + struct aws_rr_subscription_status_event event = { + .type = ARRSET_UNSUBSCRIBE_COMPLETE, + .topic_filter = record->topic_filter_cursor, + .operation_id = 0, + }; + + (*manager->config.subscription_status_callback)(&event, manager->config.userdata); } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aef4955a..91c6e310 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -535,14 +535,15 @@ add_test_case(rrc_submit_request_operation_failure_by_shutdown) add_test_case(rrc_submit_streaming_operation_and_shutdown) add_test_case(rrc_submit_request_operation_failure_by_timeout) -add_test_case(rrc_streaming_operation_success) -#add_test_case(rrc_streaming_operation_success_overlapping) -#add_test_case(rrc_streaming_operation_clean_session_reestablish_subscription) -#add_test_case(rrc_streaming_operation_resume_session) -#add_test_case(rrc_streaming_operation_first_subscribe_times_out_resub_succeeds) -#add_test_case(rrc_streaming_operation_first_subscribe_retryable_failure_resub_succeeds) -#add_test_case(rrc_streaming_operation_subscribe_unretryable_failure) -#add_test_case(rrc_streaming_operation_failure_exceeds_subscription_budget) +add_test_case(rrc_streaming_operation_success_single) +add_test_case(rrc_streaming_operation_success_overlapping) +add_test_case(rrc_streaming_operation_success_starting_offline) +add_test_case(rrc_streaming_operation_clean_session_reestablish_subscription) +add_test_case(rrc_streaming_operation_resume_session) +add_test_case(rrc_streaming_operation_first_subscribe_times_out_resub_succeeds) +add_test_case(rrc_streaming_operation_first_subscribe_retryable_failure_resub_succeeds) +add_test_case(rrc_streaming_operation_subscribe_unretryable_failure) +add_test_case(rrc_streaming_operation_failure_exceeds_subscription_budget) #add_test_case(rrc_streaming_operation_success_delayed_by_request_operations) #add_test_case(rrc_streaming_operation_success_sandwiched_by_request_operations) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 04ffc16e..12408d99 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -1070,8 +1070,11 @@ static int s_rrc_submit_request_operation_failure_by_timeout_fn(struct aws_alloc AWS_TEST_CASE(rrc_submit_request_operation_failure_by_timeout, s_rrc_submit_request_operation_failure_by_timeout_fn) -static struct aws_mqtt_rr_client_operation *s_create_streaming_operation(struct aws_rr_client_test_fixture *fixture, struct aws_byte_cursor record_key, struct aws_byte_cursor topic_filter) { - struct aws_rr_client_fixture_streaming_record *record = s_rrc_fixture_add_streaming_record(&fixture, record_key); +static struct aws_mqtt_rr_client_operation *s_create_streaming_operation( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor record_key, + struct aws_byte_cursor topic_filter) { + struct aws_rr_client_fixture_streaming_record *record = s_rrc_fixture_add_streaming_record(fixture, record_key); struct aws_mqtt_streaming_operation_options streaming_options = { .topic_filter = topic_filter, @@ -1081,58 +1084,626 @@ static struct aws_mqtt_rr_client_operation *s_create_streaming_operation(struct streaming_options.terminated_callback = s_rrc_fixture_streaming_operation_terminated_callback; streaming_options.user_data = record; - struct aws_mqtt_rr_client_operation *streaming_operation = - aws_mqtt_request_response_client_create_streaming_operation(fixture->rr_client, &streaming_options); - ASSERT_NOT_NULL(streaming_operation); + return aws_mqtt_request_response_client_create_streaming_operation(fixture->rr_client, &streaming_options); +} - return streaming_operation; +static int s_rrc_publish_5( + struct aws_mqtt5_client *client, + struct aws_byte_cursor topic, + struct aws_byte_cursor payload) { + struct aws_mqtt5_packet_publish_view publish_options = { + .topic = topic, + .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, + .payload = payload, + }; + + struct aws_mqtt5_publish_completion_options completion_options; + AWS_ZERO_STRUCT(completion_options); + + return aws_mqtt5_client_publish(client, &publish_options, &completion_options); +} + +static int s_rrc_publish_311( + struct aws_mqtt_client_connection *connection, + struct aws_byte_cursor topic, + struct aws_byte_cursor payload) { + return aws_mqtt_client_connection_publish( + connection, &topic, AWS_MQTT_QOS_AT_LEAST_ONCE, false, &payload, NULL, NULL); +} + +static int s_rrc_protocol_client_publish( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor topic, + struct aws_byte_cursor payload) { + + if (fixture->test_protocol == RRCP_MQTT311) { + return s_rrc_publish_311(fixture->client_test_fixture.mqtt311_test_fixture.mqtt_connection, topic, payload); + } else { + return s_rrc_publish_5(fixture->client_test_fixture.mqtt5_test_fixture.client, topic, payload); + } } -static int s_rrc_publish_5(struct aws_mqtt5_client *client, struct aws_byte_cursor topic, struct aws_byte_cursor payload) { +typedef void(modify_fixture_options_fn)( + struct aws_mqtt_request_response_client_options *fixture_options, + struct mqtt5_client_test_options *client_test_options); + +static int s_init_fixture_streaming_operation_success( + struct aws_rr_client_test_fixture *fixture, + struct mqtt5_client_test_options *client_test_options, + struct aws_allocator *allocator, + modify_fixture_options_fn *config_modifier, + void *user_data) { + aws_mqtt5_client_test_init_default_options(client_test_options); + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + aws_mqtt5_server_send_suback_on_subscribe; + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = + aws_mqtt5_mock_server_handle_publish_puback_and_forward; + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { + .client_options = &client_test_options->client_options, + .server_function_table = &client_test_options->server_function_table, + .mock_server_user_data = user_data, + }; + + struct aws_mqtt_request_response_client_options rr_client_options = { + .max_subscriptions = 2, + .operation_timeout_seconds = 2, + }; + + if (config_modifier != NULL) { + (*config_modifier)(&rr_client_options, client_test_options); + } + + ASSERT_SUCCESS(s_aws_rr_client_test_fixture_init_from_mqtt5( + fixture, allocator, &rr_client_options, &client_test_fixture_options, NULL)); return AWS_OP_SUCCESS; } -static int s_rrc_publish_311(struct aws_mqtt_client_connection *connection, struct aws_byte_cursor topic, struct aws_byte_cursor payload) { +/* + * Minimal success test: + * + * Create a streaming operation, verify subscription established, inject several publishes to the operation's topic, + * verify publishes received and routed through callbacks + */ +static int s_rrc_streaming_operation_success_single_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation = s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events)); + + // two publishes on the mqtt client that get reflected into our subscription topic + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_publishes), expected_publishes)); + + aws_mqtt_rr_client_operation_release(operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); - return AWS_OP_SUCCESS; } -static int s_rrc_protocol_client_publish(struct aws_rr_client_test_fixture *fixture, struct aws_byte_cursor topic, struct aws_byte_cursor payload) { +AWS_TEST_CASE(rrc_streaming_operation_success_single, s_rrc_streaming_operation_success_single_fn) - if (fixture->test_protocol == RRCP_MQTT311) { - return s_rrc_publish_5(fixture->client_test_fixture.mqtt311_test_fixture.mqtt_connection, topic, payload); - } else { - return s_rrc_publish_5(fixture->client_test_fixture.mqtt5_test_fixture.client, topic, payload); +/* + * Variant of the minimal success test where we create two operations on the same topic filter, verify they both + * get subscriptions established and publishes, then close one, send another publish and verify only the still-open + * operation received it. + */ +static int s_rrc_streaming_operation_success_overlapping_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation1 = + s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + struct aws_byte_cursor record_key2 = aws_byte_cursor_from_c_str("key2"); + struct aws_mqtt_rr_client_operation *operation2 = + s_create_streaming_operation(&fixture, record_key2, topic_filter1); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key2, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events)); + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key2, AWS_ARRAY_SIZE(expected_events), expected_events)); + + // two publishes on the mqtt client that get reflected into our subscription topic + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + struct aws_byte_cursor payload3 = aws_byte_cursor_from_c_str("Payload3"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key2, 2); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, + payload3, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes(&fixture, record_key1, 2, expected_publishes)); + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes(&fixture, record_key2, 2, expected_publishes)); + + // close the first, wait for terminate + aws_mqtt_rr_client_operation_release(operation1); + s_rrc_wait_on_streaming_termination(&fixture, record_key1); + + // publish again + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload3)); + + // verify second operation got the new publish + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key2, 3); + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes( + &fixture, record_key2, AWS_ARRAY_SIZE(expected_publishes), expected_publishes)); + + // verify first operation did not + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes(&fixture, record_key1, 2, expected_publishes)); + + aws_mqtt_rr_client_operation_release(operation2); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_streaming_operation_success_overlapping, s_rrc_streaming_operation_success_overlapping_fn) + +/* + * Variant of the simple test where we start the protocol client offline. In addition to the normal verifies, we also + * verify nothing happens event wise until we start the client. + */ +static int s_rrc_streaming_operation_success_starting_offline_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + /* stop and start the underlying client */ + aws_mqtt5_client_stop(fixture.client_test_fixture.mqtt5_test_fixture.client, NULL, NULL); + aws_wait_for_stopped_lifecycle_event(&fixture.client_test_fixture.mqtt5_test_fixture); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation = s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + /* wait a while (longer than request timeout) to see if anything happens */ + aws_thread_current_sleep(aws_timestamp_convert(3, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); + + /* fails the test if any subscription events have been emitted */ + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events(&fixture, record_key1, 0, NULL)); + + /* start the protocol client */ + aws_mqtt5_client_start(fixture.client_test_fixture.mqtt5_test_fixture.client); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events)); + + // two publishes on the mqtt client that get reflected into our subscription topic + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_publishes), expected_publishes)); + + aws_mqtt_rr_client_operation_release(operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_streaming_operation_success_starting_offline, s_rrc_streaming_operation_success_starting_offline_fn) + +static void s_rrc_force_clean_session_config( + struct aws_mqtt_request_response_client_options *fixture_options, + struct mqtt5_client_test_options *client_test_options) { + client_test_options->client_options.session_behavior = AWS_MQTT5_CSBT_CLEAN; +} + +/* + * Verifies that a streaming operation recovers properly from a clean session resumption (by resubscribing), emitting + * the proper subscription events, and receiving expected publishes once the subscription is re-established. + */ +static int s_rrc_streaming_operation_clean_session_reestablish_subscription_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success( + &fixture, &client_test_options, allocator, s_rrc_force_clean_session_config, NULL)); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation = s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + { + .status = ARRSSET_SUBSCRIPTION_LOST, + .error_code = AWS_ERROR_SUCCESS, + }, + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events(&fixture, record_key1, 1, expected_events)); + + // two publishes on the mqtt client that get reflected into our subscription topic + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 1); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes(&fixture, record_key1, 1, expected_publishes)); + + /* stop and start the underlying client, this will force a resubscribe since it's a clean session */ + aws_mqtt5_client_stop(fixture.client_test_fixture.mqtt5_test_fixture.client, NULL, NULL); + aws_wait_for_stopped_lifecycle_event(&fixture.client_test_fixture.mqtt5_test_fixture); + + aws_mqtt5_client_start(fixture.client_test_fixture.mqtt5_test_fixture.client); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 3); + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events(&fixture, record_key1, 3, expected_events)); + + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes(&fixture, record_key1, 2, expected_publishes)); + + aws_mqtt_rr_client_operation_release(operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_streaming_operation_clean_session_reestablish_subscription, + s_rrc_streaming_operation_clean_session_reestablish_subscription_fn) + +static void s_rrc_force_resume_session_config( + struct aws_mqtt_request_response_client_options *fixture_options, + struct mqtt5_client_test_options *client_test_options) { + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_CONNECT] = + aws_mqtt5_mock_server_handle_connect_honor_session_unconditional; + client_test_options->client_options.session_behavior = AWS_MQTT5_CSBT_REJOIN_ALWAYS; +} + +/* + * Variant of the clean session test where instead we always resume a session. Verify we don't get subscription + * lost/established events afterwards and can still receive messages. + */ +static int s_rrc_streaming_operation_resume_session_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success( + &fixture, &client_test_options, allocator, s_rrc_force_resume_session_config, NULL)); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation = s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events(&fixture, record_key1, 1, expected_events)); + + // two publishes on the mqtt client that get reflected into our subscription topic + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 1); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes(&fixture, record_key1, 1, expected_publishes)); + + /* stop and start the underlying client */ + aws_mqtt5_client_stop(fixture.client_test_fixture.mqtt5_test_fixture.client, NULL, NULL); + aws_wait_for_stopped_lifecycle_event(&fixture.client_test_fixture.mqtt5_test_fixture); + + aws_mqtt5_client_start(fixture.client_test_fixture.mqtt5_test_fixture.client); + + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes(&fixture, record_key1, 2, expected_publishes)); + + /* check events after the publish has completed, that shows nothing happened subscription-wise */ + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events(&fixture, record_key1, 1, expected_events)); + + aws_mqtt_rr_client_operation_release(operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_streaming_operation_resume_session, s_rrc_streaming_operation_resume_session_fn) + +struct rrc_subscribe_handler_context { + struct aws_rr_client_test_fixture *fixture; + + size_t subscribes_received; +}; + +static enum aws_mqtt5_suback_reason_code s_rrc_success_suback_rcs[] = { + AWS_MQTT5_SARC_GRANTED_QOS_1, +}; + +int s_handle_subscribe_with_initial_timeout( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data) { + (void)packet; + + struct rrc_subscribe_handler_context *context = user_data; + + bool should_respond = false; + aws_mutex_lock(&context->fixture->lock); + should_respond = context->subscribes_received > 0; + ++context->subscribes_received; + aws_mutex_unlock(&context->fixture->lock); + + if (!should_respond) { + return AWS_OP_SUCCESS; } + + struct aws_mqtt5_packet_subscribe_view *subscribe_packet = packet; + + struct aws_mqtt5_packet_suback_view suback_view = { + .packet_id = subscribe_packet->packet_id, + .reason_code_count = 1, + .reason_codes = s_rrc_success_suback_rcs, + }; + + return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view); +} + +static void s_rrc_initial_subscribe_timeout_config( + struct aws_mqtt_request_response_client_options *fixture_options, + struct mqtt5_client_test_options *client_test_options) { + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + s_handle_subscribe_with_initial_timeout; } -static int s_rrc_streaming_operation_success_fn(struct aws_allocator *allocator, void *ctx) { +/* + * Variant of the basic success test where the first subscribe is ignored, causing it to timeout. Verify the + * client sends a second subscribe (which succeeds) after which everything is fine. + */ +static int s_rrc_streaming_operation_first_subscribe_times_out_resub_succeeds_fn( + struct aws_allocator *allocator, + void *ctx) { (void)ctx; aws_mqtt_library_init(allocator); struct mqtt5_client_test_options client_test_options; - aws_mqtt5_client_test_init_default_options(&client_test_options); + struct aws_rr_client_test_fixture fixture; - client_test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = ??; - client_test_options.server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = ??; + struct rrc_subscribe_handler_context subscribe_context = { + .fixture = &fixture, + .subscribes_received = 0, + }; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success( + &fixture, &client_test_options, allocator, s_rrc_initial_subscribe_timeout_config, &subscribe_context)); - struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { - .client_options = &client_test_options.client_options, - .server_function_table = &client_test_options.server_function_table, + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation = s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events)); - struct aws_mqtt_request_response_client_options rr_client_options = { - .max_subscriptions = 2, - .operation_timeout_seconds = 2, + // verify we ignored the first subscribe, triggering a second + aws_mutex_lock(&fixture.lock); + ASSERT_INT_EQUALS(2, subscribe_context.subscribes_received); + aws_mutex_unlock(&fixture.lock); + + // two publishes on the mqtt client that get reflected into our subscription topic + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_publishes), expected_publishes)); + + aws_mqtt_rr_client_operation_release(operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_streaming_operation_first_subscribe_times_out_resub_succeeds, + s_rrc_streaming_operation_first_subscribe_times_out_resub_succeeds_fn) + +static enum aws_mqtt5_suback_reason_code s_rrc_retryable_suback_rcs[] = { + AWS_MQTT5_SARC_UNSPECIFIED_ERROR, +}; + +int s_handle_subscribe_with_initial_retryable_failure( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data) { + (void)packet; + + struct rrc_subscribe_handler_context *context = user_data; + + bool should_succeed = false; + aws_mutex_lock(&context->fixture->lock); + should_succeed = context->subscribes_received > 0; + ++context->subscribes_received; + aws_mutex_unlock(&context->fixture->lock); + + struct aws_mqtt5_packet_subscribe_view *subscribe_packet = packet; + + struct aws_mqtt5_packet_suback_view suback_view = { + .packet_id = subscribe_packet->packet_id, + .reason_code_count = 1, + }; + + if (should_succeed) { + suback_view.reason_codes = s_rrc_success_suback_rcs; + } else { + suback_view.reason_codes = s_rrc_retryable_suback_rcs; + } + + return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view); +} + +static void s_rrc_initial_subscribe_retryable_failure_config( + struct aws_mqtt_request_response_client_options *fixture_options, + struct mqtt5_client_test_options *client_test_options) { + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + s_handle_subscribe_with_initial_retryable_failure; +} + +/* + * Variant of the basic success test where the first subscribe triggers a retryable suback failure. Verify the + * client sends a second subscribe (which succeeds) after which everything is fine. + */ +static int s_rrc_streaming_operation_first_subscribe_retryable_failure_resub_succeeds_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; struct aws_rr_client_test_fixture fixture; - ASSERT_SUCCESS(s_aws_rr_client_test_fixture_init_from_mqtt5( - &fixture, allocator, &rr_client_options, &client_test_fixture_options, NULL)); + + struct rrc_subscribe_handler_context subscribe_context = { + .fixture = &fixture, + .subscribes_received = 0, + }; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success( + &fixture, + &client_test_options, + allocator, + s_rrc_initial_subscribe_retryable_failure_config, + &subscribe_context)); struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); @@ -1146,13 +1717,19 @@ static int s_rrc_streaming_operation_success_fn(struct aws_allocator *allocator, .error_code = AWS_ERROR_SUCCESS, }, }; - s_rrc_verify_streaming_record_subscription_events(&fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events); + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events)); + + // verify we ignored the first subscribe, triggering a second + aws_mutex_lock(&fixture.lock); + ASSERT_INT_EQUALS(2, subscribe_context.subscribes_received); + aws_mutex_unlock(&fixture.lock); // two publishes on the mqtt client that get reflected into our subscription topic struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); - struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload1"); - s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1); - s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); @@ -1160,7 +1737,8 @@ static int s_rrc_streaming_operation_success_fn(struct aws_allocator *allocator, payload1, payload2, }; - s_rrc_verify_streaming_publishes(&fixture, record_key1, AWS_ARRAY_SIZE(expected_publishes), expected_publishes); + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_publishes), expected_publishes)); aws_mqtt_rr_client_operation_release(operation); @@ -1171,4 +1749,208 @@ static int s_rrc_streaming_operation_success_fn(struct aws_allocator *allocator, return AWS_OP_SUCCESS; } -AWS_TEST_CASE(rrc_streaming_operation_success, s_rrc_streaming_operation_success_fn) +AWS_TEST_CASE( + rrc_streaming_operation_first_subscribe_retryable_failure_resub_succeeds, + s_rrc_streaming_operation_first_subscribe_retryable_failure_resub_succeeds_fn) + +static enum aws_mqtt5_suback_reason_code s_rrc_unretryable_suback_rcs[] = { + AWS_MQTT5_SARC_NOT_AUTHORIZED, +}; + +int s_handle_subscribe_with_terminal_failure( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data) { + (void)packet; + + struct rrc_subscribe_handler_context *context = user_data; + + aws_mutex_lock(&context->fixture->lock); + ++context->subscribes_received; + aws_mutex_unlock(&context->fixture->lock); + + struct aws_mqtt5_packet_subscribe_view *subscribe_packet = packet; + + struct aws_mqtt5_packet_suback_view suback_view = { + .packet_id = subscribe_packet->packet_id, + .reason_code_count = 1, + .reason_codes = s_rrc_unretryable_suback_rcs, + }; + + return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view); +} + +static void s_rrc_subscribe_terminal_failure_config( + struct aws_mqtt_request_response_client_options *fixture_options, + struct mqtt5_client_test_options *client_test_options) { + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + s_handle_subscribe_with_terminal_failure; +} + +/* + * Failure variant where the subscribe triggers a non-retryable suback failure. Verify the + * operation gets halted. + */ +static int s_rrc_streaming_operation_subscribe_unretryable_failure_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + + struct rrc_subscribe_handler_context subscribe_context = { + .fixture = &fixture, + .subscribes_received = 0, + }; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success( + &fixture, &client_test_options, allocator, s_rrc_subscribe_terminal_failure_config, &subscribe_context)); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation = s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + // wait an extra amount just for fun + aws_thread_current_sleep(aws_timestamp_convert(2, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_HALTED, + .error_code = AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events)); + + aws_mutex_lock(&fixture.lock); + ASSERT_INT_EQUALS(1, subscribe_context.subscribes_received); + aws_mutex_unlock(&fixture.lock); + + aws_mqtt_rr_client_operation_release(operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_streaming_operation_subscribe_unretryable_failure, + s_rrc_streaming_operation_subscribe_unretryable_failure_fn) + +static void s_rrc_unsubscribe_success_config( + struct aws_mqtt_request_response_client_options *fixture_options, + struct mqtt5_client_test_options *client_test_options) { + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = + aws_mqtt5_mock_server_handle_unsubscribe_unsuback_success; +} + +/* + * Multi-operation variant where we exceed the streaming subscription budget, release everything and then verify + * we can successfully establish a new streaming operation after everything cleans up. + */ +static int s_rrc_streaming_operation_failure_exceeds_subscription_budget_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success( + &fixture, &client_test_options, allocator, s_rrc_unsubscribe_success_config, NULL)); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation1 = + s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + struct aws_byte_cursor record_key2 = aws_byte_cursor_from_c_str("key2"); + struct aws_byte_cursor topic_filter2 = aws_byte_cursor_from_c_str("topic/2"); + struct aws_mqtt_rr_client_operation *operation2 = + s_create_streaming_operation(&fixture, record_key2, topic_filter2); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key2, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_success_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_success_events), expected_success_events)); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_failure_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_HALTED, + .error_code = AWS_ERROR_MQTT_REQUEST_RESPONSE_NO_SUBSCRIPTION_CAPACITY, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key2, AWS_ARRAY_SIZE(expected_failure_events), expected_failure_events)); + + // two publishes on the mqtt client that get reflected into our subscription topic1 + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes(&fixture, record_key1, 2, expected_publishes)); + + // close the first, wait for terminate + aws_mqtt_rr_client_operation_release(operation1); + s_rrc_wait_on_streaming_termination(&fixture, record_key1); + + // close the second, wait for terminate + aws_mqtt_rr_client_operation_release(operation2); + s_rrc_wait_on_streaming_termination(&fixture, record_key2); + + // let the unsubscribe resolve + aws_thread_current_sleep(aws_timestamp_convert(1, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); + + // make a third using topic filter 2 + struct aws_byte_cursor record_key3 = aws_byte_cursor_from_c_str("key3"); + struct aws_mqtt_rr_client_operation *operation3 = + s_create_streaming_operation(&fixture, record_key3, topic_filter2); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key3, 1); + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key3, AWS_ARRAY_SIZE(expected_success_events), expected_success_events)); + + // publish again + struct aws_byte_cursor payload3 = aws_byte_cursor_from_c_str("payload3"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter2, payload3)); + + // verify third operation got the new publish + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key3, 1); + + struct aws_byte_cursor third_expected_publishes[] = { + payload3, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes( + &fixture, record_key3, AWS_ARRAY_SIZE(third_expected_publishes), third_expected_publishes)); + + aws_mqtt_rr_client_operation_release(operation3); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_streaming_operation_failure_exceeds_subscription_budget, + s_rrc_streaming_operation_failure_exceeds_subscription_budget_fn) diff --git a/tests/v5/mqtt5_client_tests.c b/tests/v5/mqtt5_client_tests.c index b876adda..690bf711 100644 --- a/tests/v5/mqtt5_client_tests.c +++ b/tests/v5/mqtt5_client_tests.c @@ -5353,7 +5353,10 @@ static int s_aws_mqtt5_server_send_aliased_publish_sequence( struct aws_mqtt5_packet_subscribe_view *subscribe_view = packet; struct aws_mqtt5_packet_suback_view suback_view = { - .packet_id = subscribe_view->packet_id, .reason_code_count = 1, .reason_codes = s_alias_reason_codes}; + .packet_id = subscribe_view->packet_id, + .reason_code_count = 1, + .reason_codes = s_alias_reason_codes, + }; // just to be thorough, send a suback if (aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view)) { @@ -5548,7 +5551,10 @@ static int s_aws_mqtt5_server_send_aliased_publish_failure( struct aws_mqtt5_packet_subscribe_view *subscribe_view = packet; struct aws_mqtt5_packet_suback_view suback_view = { - .packet_id = subscribe_view->packet_id, .reason_code_count = 1, .reason_codes = s_alias_reason_codes}; + .packet_id = subscribe_view->packet_id, + .reason_code_count = 1, + .reason_codes = s_alias_reason_codes, + }; // just to be thorough, send a suback if (aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view)) { From 991c19b8867be0f566a56183313b8bd21401288d Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 29 Mar 2024 17:41:12 -0700 Subject: [PATCH 087/124] Fixes --- source/request-response/subscription_manager.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index d025d765..84165693 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -579,13 +579,13 @@ static void s_handle_protocol_adapter_request_subscription_event( if (event->error_code == AWS_ERROR_SUCCESS) { record->status = ARRSST_NOT_SUBSCRIBED; - struct aws_rr_subscription_status_event event = { + struct aws_rr_subscription_status_event unsubscribe_event = { .type = ARRSET_UNSUBSCRIBE_COMPLETE, .topic_filter = record->topic_filter_cursor, .operation_id = 0, }; - (*manager->config.subscription_status_callback)(&event, manager->config.userdata); + (*manager->config.subscription_status_callback)(&unsubscribe_event, manager->config.userdata); } } } @@ -617,13 +617,13 @@ static void s_handle_protocol_adapter_streaming_subscription_event( if (event->error_code == AWS_ERROR_SUCCESS) { record->status = ARRSST_NOT_SUBSCRIBED; - struct aws_rr_subscription_status_event event = { + struct aws_rr_subscription_status_event unsubscribe_event = { .type = ARRSET_UNSUBSCRIBE_COMPLETE, .topic_filter = record->topic_filter_cursor, .operation_id = 0, }; - (*manager->config.subscription_status_callback)(&event, manager->config.userdata); + (*manager->config.subscription_status_callback)(&unsubscribe_event, manager->config.userdata); } } } From f7fd38fd2c153a957e1ae49d2665968022f88098 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 29 Mar 2024 17:49:47 -0700 Subject: [PATCH 088/124] unreferenced parameters --- .../request-response/request_response_client_tests.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 12408d99..1633437c 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -1368,6 +1368,8 @@ AWS_TEST_CASE(rrc_streaming_operation_success_starting_offline, s_rrc_streaming_ static void s_rrc_force_clean_session_config( struct aws_mqtt_request_response_client_options *fixture_options, struct mqtt5_client_test_options *client_test_options) { + (void)fixture_options; + client_test_options->client_options.session_behavior = AWS_MQTT5_CSBT_CLEAN; } @@ -1452,6 +1454,8 @@ AWS_TEST_CASE( static void s_rrc_force_resume_session_config( struct aws_mqtt_request_response_client_options *fixture_options, struct mqtt5_client_test_options *client_test_options) { + (void)fixture_options; + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_CONNECT] = aws_mqtt5_mock_server_handle_connect_honor_session_unconditional; client_test_options->client_options.session_behavior = AWS_MQTT5_CSBT_REJOIN_ALWAYS; @@ -1566,6 +1570,8 @@ int s_handle_subscribe_with_initial_timeout( static void s_rrc_initial_subscribe_timeout_config( struct aws_mqtt_request_response_client_options *fixture_options, struct mqtt5_client_test_options *client_test_options) { + (void)fixture_options; + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = s_handle_subscribe_with_initial_timeout; } @@ -1676,6 +1682,8 @@ int s_handle_subscribe_with_initial_retryable_failure( static void s_rrc_initial_subscribe_retryable_failure_config( struct aws_mqtt_request_response_client_options *fixture_options, struct mqtt5_client_test_options *client_test_options) { + (void)fixture_options; + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = s_handle_subscribe_with_initial_retryable_failure; } @@ -1783,6 +1791,8 @@ int s_handle_subscribe_with_terminal_failure( static void s_rrc_subscribe_terminal_failure_config( struct aws_mqtt_request_response_client_options *fixture_options, struct mqtt5_client_test_options *client_test_options) { + (void)fixture_options; + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = s_handle_subscribe_with_terminal_failure; } @@ -1844,6 +1854,8 @@ AWS_TEST_CASE( static void s_rrc_unsubscribe_success_config( struct aws_mqtt_request_response_client_options *fixture_options, struct mqtt5_client_test_options *client_test_options) { + (void)fixture_options; + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = aws_mqtt5_mock_server_handle_unsubscribe_unsuback_success; } From 5b57a82f139e3e70b5870c146238df4d98048411 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sat, 30 Mar 2024 10:25:19 -0700 Subject: [PATCH 089/124] Finish streaming tests --- .../request-response/subscription_manager.h | 11 +- .../request_response_client.c | 8 +- .../request-response/subscription_manager.c | 37 ++- tests/CMakeLists.txt | 4 +- .../request_response_client_tests.c | 242 +++++++++++++++++- .../subscription_manager_tests.c | 20 +- 6 files changed, 279 insertions(+), 43 deletions(-) diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 4d644af2..20e1b57f 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -22,12 +22,12 @@ enum aws_rr_subscription_event_type { /* * A request subscription subscribe succeeded */ - ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + ARRSET_REQUEST_SUBSCRIBE_SUCCESS, /* * A request subscription subscribe failed */ - ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE, + ARRSET_REQUEST_SUBSCRIBE_FAILURE, /* * A previously successful request subscription has ended. @@ -36,7 +36,7 @@ enum aws_rr_subscription_event_type { * * (1) failure to rejoin a session */ - ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED, + ARRSET_REQUEST_SUBSCRIPTION_ENDED, /* * A streaming subscription subscribe succeeded @@ -58,6 +58,11 @@ enum aws_rr_subscription_event_type { */ ARRSET_STREAMING_SUBSCRIPTION_HALTED, + /* + * A subscription has lost its last listener and can be purged + */ + ARRSET_SUBSCRIPTION_EMPTY, + /* * A subscription has been unsubscribed from * diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index a4c3819f..66264104 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -782,7 +782,7 @@ static void s_handle_subscription_status_event_task(struct aws_task *task, void goto done; } - if (event_task->type == ARRSET_UNSUBSCRIBE_COMPLETE) { + if (event_task->type == ARRSET_UNSUBSCRIBE_COMPLETE || event_task->type == ARRSET_SUBSCRIPTION_EMPTY) { s_mqtt_request_response_client_wake_service(event_task->rr_client); goto done; } @@ -796,9 +796,9 @@ static void s_handle_subscription_status_event_task(struct aws_task *task, void struct aws_mqtt_rr_client_operation *operation = element->value; switch (event_task->type) { - case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS: - case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE: - case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED: + case ARRSET_REQUEST_SUBSCRIBE_SUCCESS: + case ARRSET_REQUEST_SUBSCRIBE_FAILURE: + case ARRSET_REQUEST_SUBSCRIPTION_ENDED: /* NYI */ break; diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 84165693..ecf3a6db 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -252,13 +252,25 @@ static void s_remove_listener_from_subscription_record( aws_hash_table_remove(&record->listeners, &listener, NULL, NULL); + size_t listener_count = aws_hash_table_get_entry_count(&record->listeners); + AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, "request-response subscription manager - removed listener %" PRIu64 " from subscription ('" PRInSTR "'), %zu listeners left", operation_id, AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor), - aws_hash_table_get_entry_count(&record->listeners)); + listener_count); + + if (listener_count == 0) { + struct aws_rr_subscription_status_event event = { + .type = ARRSET_SUBSCRIPTION_EMPTY, + .topic_filter = record->topic_filter_cursor, + .operation_id = 0, + }; + + (*manager->config.subscription_status_callback)(&event, manager->config.userdata); + } } static void s_add_listener_to_subscription_record(struct aws_rr_subscription_record *record, uint64_t operation_id) { @@ -314,13 +326,13 @@ static void s_cull_unused_subscriptions(struct aws_rr_subscription_manager *mana static const char *s_rr_subscription_event_type_to_c_str(enum aws_rr_subscription_event_type type) { switch (type) { - case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS: + case ARRSET_REQUEST_SUBSCRIBE_SUCCESS: return "RequestSubscribeSuccess"; - case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE: + case ARRSET_REQUEST_SUBSCRIBE_FAILURE: return "RequestSubscribeFailure"; - case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED: + case ARRSET_REQUEST_SUBSCRIPTION_ENDED: return "RequestSubscriptionEnded"; case ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED: @@ -334,6 +346,9 @@ static const char *s_rr_subscription_event_type_to_c_str(enum aws_rr_subscriptio case ARRSET_UNSUBSCRIBE_COMPLETE: return "UnsubscribeComplete"; + + case ARRSET_SUBSCRIPTION_EMPTY: + return "SubscriptionEmpty"; } return "Unknown"; @@ -343,9 +358,9 @@ static bool s_subscription_type_matches_event_type( enum aws_rr_subscription_type subscription_type, enum aws_rr_subscription_event_type event_type) { switch (event_type) { - case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS: - case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE: - case ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED: + case ARRSET_REQUEST_SUBSCRIBE_SUCCESS: + case ARRSET_REQUEST_SUBSCRIBE_FAILURE: + case ARRSET_REQUEST_SUBSCRIPTION_ENDED: return subscription_type == ARRST_REQUEST_RESPONSE; case ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED: @@ -421,7 +436,7 @@ static int s_rr_activate_idle_subscription( aws_error_debug_str(error_code)); if (record->type == ARRST_REQUEST_RESPONSE) { - s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE); + s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIBE_FAILURE); } else { record->poisoned = true; s_emit_subscription_event(manager, record, ARRSET_STREAMING_SUBSCRIPTION_HALTED); @@ -567,9 +582,9 @@ static void s_handle_protocol_adapter_request_subscription_event( if (event->error_code == AWS_ERROR_SUCCESS) { record->status = ARRSST_SUBSCRIBED; - s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS); + s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIBE_SUCCESS); } else { - s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE); + s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIBE_FAILURE); } } else { AWS_FATAL_ASSERT(event->event_type == AWS_PASET_UNSUBSCRIBE); @@ -677,7 +692,7 @@ static int s_apply_session_lost_wrapper(void *context, struct aws_hash_element * record->status = ARRSST_NOT_SUBSCRIBED; if (record->type == ARRST_REQUEST_RESPONSE) { - s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED); + s_emit_subscription_event(manager, record, ARRSET_REQUEST_SUBSCRIPTION_ENDED); if (record->pending_action != ARRSPAT_UNSUBSCRIBING) { s_aws_rr_subscription_record_destroy(record); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 91c6e310..1ccd9bd6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -544,8 +544,8 @@ add_test_case(rrc_streaming_operation_first_subscribe_times_out_resub_succeeds) add_test_case(rrc_streaming_operation_first_subscribe_retryable_failure_resub_succeeds) add_test_case(rrc_streaming_operation_subscribe_unretryable_failure) add_test_case(rrc_streaming_operation_failure_exceeds_subscription_budget) -#add_test_case(rrc_streaming_operation_success_delayed_by_request_operations) -#add_test_case(rrc_streaming_operation_success_sandwiched_by_request_operations) +add_test_case(rrc_streaming_operation_success_delayed_by_request_operations) +add_test_case(rrc_streaming_operation_success_sandwiched_by_request_operations) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 1633437c..514dfca4 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -46,9 +47,8 @@ struct aws_rr_client_fixture_request_response_record { struct aws_rr_client_test_fixture *fixture; - struct aws_byte_cursor payload_cursor; - - struct aws_byte_buf payload; + struct aws_byte_cursor record_key_cursor; + struct aws_byte_buf record_key; bool completed; int error_code; @@ -65,15 +65,15 @@ struct aws_rr_client_fixture_request_response_record *s_aws_rr_client_fixture_re record->allocator = allocator; record->fixture = fixture; - aws_byte_buf_init_copy_from_cursor(&record->payload, allocator, request_payload); - record->payload_cursor = aws_byte_cursor_from_buf(&record->payload); + aws_byte_buf_init_copy_from_cursor(&record->record_key, allocator, request_payload); + record->record_key_cursor = aws_byte_cursor_from_buf(&record->record_key); return record; } void s_aws_rr_client_fixture_request_response_record_delete( struct aws_rr_client_fixture_request_response_record *record) { - aws_byte_buf_clean_up(&record->payload); + aws_byte_buf_clean_up(&record->record_key); aws_byte_buf_clean_up(&record->response); aws_mem_release(record->allocator, record); @@ -111,11 +111,11 @@ static void s_rrc_fixture_request_completion_callback( static struct aws_rr_client_fixture_request_response_record *s_rrc_fixture_add_request_record( struct aws_rr_client_test_fixture *fixture, - struct aws_byte_cursor request_payload) { + struct aws_byte_cursor record_key) { struct aws_rr_client_fixture_request_response_record *record = - s_aws_rr_client_fixture_request_response_record_new(fixture->allocator, fixture, request_payload); + s_aws_rr_client_fixture_request_response_record_new(fixture->allocator, fixture, record_key); - aws_hash_table_put(&fixture->request_response_records, &record->payload_cursor, record, NULL); + aws_hash_table_put(&fixture->request_response_records, &record->record_key_cursor, record, NULL); return record; } @@ -140,9 +140,9 @@ static bool s_is_request_complete(void *context) { static void s_rrc_wait_on_request_completion( struct aws_rr_client_test_fixture *fixture, - struct aws_byte_cursor request_payload) { + struct aws_byte_cursor record_key) { struct rrc_operation_completion_context context = { - .key = request_payload, + .key = record_key, .fixture = fixture, }; @@ -153,13 +153,13 @@ static void s_rrc_wait_on_request_completion( static int s_rrc_verify_request_completion( struct aws_rr_client_test_fixture *fixture, - struct aws_byte_cursor request_payload, + struct aws_byte_cursor record_key, int expected_error_code, struct aws_byte_cursor *expected_response) { aws_mutex_lock(&fixture->lock); struct aws_hash_element *element = NULL; - aws_hash_table_find(&fixture->request_response_records, &request_payload, &element); + aws_hash_table_find(&fixture->request_response_records, &record_key, &element); AWS_FATAL_ASSERT(element != NULL && element->value != NULL); @@ -1966,3 +1966,219 @@ static int s_rrc_streaming_operation_failure_exceeds_subscription_budget_fn( AWS_TEST_CASE( rrc_streaming_operation_failure_exceeds_subscription_budget, s_rrc_streaming_operation_failure_exceeds_subscription_budget_fn) + +static int s_submit_request_operation_from_prefix( + struct aws_rr_client_test_fixture *fixture, + struct aws_byte_cursor record_key, + struct aws_byte_cursor prefix) { + char accepted_path[128]; + char rejected_path[128]; + char subscription_topic_filter[128]; + char publish_topic[128]; + + snprintf(accepted_path, AWS_ARRAY_SIZE(accepted_path), PRInSTR "/accepted", AWS_BYTE_CURSOR_PRI(prefix)); + snprintf(rejected_path, AWS_ARRAY_SIZE(rejected_path), PRInSTR "/rejected", AWS_BYTE_CURSOR_PRI(prefix)); + snprintf( + subscription_topic_filter, + AWS_ARRAY_SIZE(subscription_topic_filter), + PRInSTR "/+", + AWS_BYTE_CURSOR_PRI(prefix)); + snprintf(publish_topic, AWS_ARRAY_SIZE(publish_topic), PRInSTR "/get", AWS_BYTE_CURSOR_PRI(prefix)); + + char correlation_token[128]; + struct aws_byte_buf correlation_token_buf = + aws_byte_buf_from_empty_array(correlation_token, AWS_ARRAY_SIZE(correlation_token)); + + struct aws_uuid uuid; + aws_uuid_init(&uuid); + aws_uuid_to_str(&uuid, &correlation_token_buf); + + struct aws_mqtt_request_operation_response_path response_paths[] = { + { + .topic = aws_byte_cursor_from_c_str(accepted_path), + .correlation_token_json_path = aws_byte_cursor_from_c_str("client_token"), + }, + { + .topic = aws_byte_cursor_from_c_str(rejected_path), + .correlation_token_json_path = aws_byte_cursor_from_c_str("client_token"), + }, + }; + + struct aws_rr_client_fixture_request_response_record *record = + s_rrc_fixture_add_request_record(fixture, record_key); + + struct aws_mqtt_request_operation_options request = { + .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .response_paths = response_paths, + .response_path_count = AWS_ARRAY_SIZE(response_paths), + .publish_topic = aws_byte_cursor_from_c_str(publish_topic), + .serialized_request = aws_byte_cursor_from_c_str("{}"), + .correlation_token = aws_byte_cursor_from_buf(&correlation_token_buf), + .completion_callback = s_rrc_fixture_request_completion_callback, + .user_data = record, + }; + + return aws_mqtt_request_response_client_submit_request(fixture->rr_client, &request); +} + +/* + * Configure server to only respond to subscribes that match a streaming filter. Submit a couple of + * request-response operations ahead of a streaming operation. Verify they both time out and that the streaming + * operation successfully subscribes and receives publishes. + */ +static int s_rrc_streaming_operation_success_delayed_by_request_operations_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success( + &fixture, &client_test_options, allocator, s_rrc_unsubscribe_success_config, NULL)); + + struct aws_byte_cursor request_key1 = aws_byte_cursor_from_c_str("requestkey1"); + struct aws_byte_cursor request_key2 = aws_byte_cursor_from_c_str("requestkey2"); + + ASSERT_SUCCESS(s_submit_request_operation_from_prefix(&fixture, request_key1, request_key1)); + ASSERT_SUCCESS(s_submit_request_operation_from_prefix(&fixture, request_key2, request_key2)); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation = s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + s_rrc_wait_on_request_completion(&fixture, request_key1); + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, request_key1, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + s_rrc_wait_on_request_completion(&fixture, request_key2); + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, request_key2, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events)); + + // two publishes on the mqtt client that get reflected into our subscription topic + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_publishes), expected_publishes)); + + aws_mqtt_rr_client_operation_release(operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_streaming_operation_success_delayed_by_request_operations, + s_rrc_streaming_operation_success_delayed_by_request_operations_fn) + +/* + * Variant of previous test where we sandwich the streaming operation by multiple request response operations and + * verify all request-response operations fail with a timeout. + */ +static int s_rrc_streaming_operation_success_sandwiched_by_request_operations_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_streaming_operation_success( + &fixture, &client_test_options, allocator, s_rrc_unsubscribe_success_config, NULL)); + + struct aws_byte_cursor request_key1 = aws_byte_cursor_from_c_str("requestkey1"); + struct aws_byte_cursor request_key2 = aws_byte_cursor_from_c_str("requestkey2"); + struct aws_byte_cursor request_key3 = aws_byte_cursor_from_c_str("requestkey3"); + struct aws_byte_cursor request_key4 = aws_byte_cursor_from_c_str("requestkey4"); + + ASSERT_SUCCESS(s_submit_request_operation_from_prefix(&fixture, request_key1, request_key1)); + ASSERT_SUCCESS(s_submit_request_operation_from_prefix(&fixture, request_key2, request_key2)); + + struct aws_byte_cursor record_key1 = aws_byte_cursor_from_c_str("key1"); + struct aws_byte_cursor topic_filter1 = aws_byte_cursor_from_c_str("topic/1"); + struct aws_mqtt_rr_client_operation *operation = s_create_streaming_operation(&fixture, record_key1, topic_filter1); + + ASSERT_SUCCESS(s_submit_request_operation_from_prefix(&fixture, request_key3, request_key3)); + ASSERT_SUCCESS(s_submit_request_operation_from_prefix(&fixture, request_key4, request_key4)); + + s_rrc_wait_on_request_completion(&fixture, request_key1); + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, request_key1, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + s_rrc_wait_on_request_completion(&fixture, request_key2); + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, request_key2, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + + s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); + + struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { + { + .status = ARRSSET_SUBSCRIPTION_ESTABLISHED, + .error_code = AWS_ERROR_SUCCESS, + }, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_events), expected_events)); + + // two publishes on the mqtt client that get reflected into our subscription topic + struct aws_byte_cursor payload1 = aws_byte_cursor_from_c_str("Payload1"); + struct aws_byte_cursor payload2 = aws_byte_cursor_from_c_str("Payload2"); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload1)); + ASSERT_SUCCESS(s_rrc_protocol_client_publish(&fixture, topic_filter1, payload2)); + + s_rrc_wait_for_n_streaming_publishes(&fixture, record_key1, 2); + + struct aws_byte_cursor expected_publishes[] = { + payload1, + payload2, + }; + ASSERT_SUCCESS(s_rrc_verify_streaming_publishes( + &fixture, record_key1, AWS_ARRAY_SIZE(expected_publishes), expected_publishes)); + + s_rrc_wait_on_request_completion(&fixture, request_key3); + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, request_key3, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + s_rrc_wait_on_request_completion(&fixture, request_key4); + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, request_key4, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + + aws_mqtt_rr_client_operation_release(operation); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_streaming_operation_success_sandwiched_by_request_operations, + s_rrc_streaming_operation_success_sandwiched_by_request_operations_fn) + +/* +#add_test_case() +#add_test_case(rrc_streaming_operation_success_sandwiched_by_request_operations) + */ \ No newline at end of file diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 4abd726c..8684d3a4 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -575,7 +575,7 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator struct aws_subscription_status_record expected_subscription_events[] = { { - .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), .operation_id = 1, }, @@ -907,12 +907,12 @@ static int s_rrsm_release_unsubscribes_request_fn(struct aws_allocator *allocato // verify two success callbacks struct aws_subscription_status_record expected_subscription_events[] = { { - .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }, { - .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 2, }}; @@ -1283,7 +1283,7 @@ static int s_rrsm_acquire_request_subscribe_failure_event_fn(struct aws_allocato // verify subscribe failure event emission struct aws_subscription_status_record expected_subscription_event = { - .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE, + .type = ARRSET_REQUEST_SUBSCRIBE_FAILURE, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1360,9 +1360,9 @@ static enum aws_rr_subscription_event_type s_compute_expected_subscription_event bool success) { if (subscription_type == ARRST_REQUEST_RESPONSE) { if (success) { - return ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS; + return ARRSET_REQUEST_SUBSCRIBE_SUCCESS; } else { - return ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_FAILURE; + return ARRSET_REQUEST_SUBSCRIBE_FAILURE; } } else { if (success) { @@ -1602,7 +1602,7 @@ static int s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test( aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { - .type = subscription_type == ARRST_REQUEST_RESPONSE ? ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS + .type = subscription_type == ARRST_REQUEST_RESPONSE ? ARRSET_REQUEST_SUBSCRIBE_SUCCESS : ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, @@ -1714,7 +1714,7 @@ static int s_do_rrsm_acquire_clean_up_test( aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { - .type = subscription_type == ARRST_REQUEST_RESPONSE ? ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS + .type = subscription_type == ARRST_REQUEST_RESPONSE ? ARRSET_REQUEST_SUBSCRIBE_SUCCESS : ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, @@ -1871,7 +1871,7 @@ static int s_rrsm_do_no_session_subscription_ended_test( aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { - .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIBE_SUCCESS, + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; @@ -1921,7 +1921,7 @@ static int s_rrsm_do_no_session_subscription_ended_test( // verify subscription lost emitted if (!offline_while_unsubscribing) { struct aws_subscription_status_record expected_subscription_ended_event = { - .type = ARRSET_REQUEST_SUBSCRIPTION_SUBSCRIPTION_ENDED, + .type = ARRSET_REQUEST_SUBSCRIPTION_ENDED, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), .operation_id = 1, }; From 3068c28fd6470cfec69e1811441bcfbf374e7079 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sat, 30 Mar 2024 12:02:25 -0700 Subject: [PATCH 090/124] Additional tests --- tests/CMakeLists.txt | 3 + .../request_response_client_tests.c | 14 +++ .../subscription_manager_tests.c | 110 ++++++++++++++++++ 3 files changed, 127 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1ccd9bd6..c6b6e74e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -518,6 +518,8 @@ add_test_case(rrsm_offline_acquire_streaming_pending_clean_up_unsubscribe_overri add_test_case(rrsm_acquire_request_success_offline_online_no_session_subscription_ended_can_reacquire) add_test_case(rrsm_request_subscription_ended_while_unsubscribing) add_test_case(rrsm_streaming_subscription_lost_resubscribe_on_no_session) +add_test_case(rrsm_request_subscription_purge_events) +add_test_case(rrsm_streaming_subscription_purge_events) # "rrc" = request response client add_test_case(rrc_mqtt5_create_destroy) @@ -528,6 +530,7 @@ add_test_case(rrc_submit_request_operation_failure_invalid_response_topic) add_test_case(rrc_submit_request_operation_failure_invalid_response_correlation_token_path) add_test_case(rrc_submit_request_operation_failure_no_correlation_token) add_test_case(rrc_submit_request_operation_failure_invalid_publish_topic) +add_test_case(rrc_submit_request_operation_failure_invalid_subscription_topic_filter) add_test_case(rrc_submit_request_operation_failure_empty_request) add_test_case(rrc_submit_streaming_operation_failure_invalid_subscription_topic_filter) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 514dfca4..94df98d9 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -822,6 +822,20 @@ AWS_TEST_CASE( rrc_submit_request_operation_failure_invalid_publish_topic, s_rrc_submit_request_operation_failure_invalid_publish_topic_fn) +static void s_invalid_subscription_topic_filter_mutator(struct aws_mqtt_request_operation_options *request_options) { + request_options->subscription_topic_filter = aws_byte_cursor_from_c_str("a/#/c"); +} + +static int s_rrc_submit_request_operation_failure_invalid_subscription_topic_filter_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_rrc_do_submit_request_operation_failure_test(allocator, s_invalid_subscription_topic_filter_mutator); +} + +AWS_TEST_CASE( + rrc_submit_request_operation_failure_invalid_subscription_topic_filter, + s_rrc_submit_request_operation_failure_invalid_subscription_topic_filter_fn) + static void s_empty_request_mutator(struct aws_mqtt_request_operation_options *request_options) { request_options->serialized_request = aws_byte_cursor_from_c_str(""); } diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 8684d3a4..6d4cf786 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -2069,3 +2069,113 @@ static int s_rrsm_streaming_subscription_lost_resubscribe_on_no_session_fn(struc AWS_TEST_CASE( rrsm_streaming_subscription_lost_resubscribe_on_no_session, s_rrsm_streaming_subscription_lost_resubscribe_on_no_session_fn) + +static int s_do_purge_test(struct aws_allocator *allocator, enum aws_rr_subscription_type subscription_type) { + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = subscription_type, + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + struct aws_protocol_adapter_subscription_event successful_subscription_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription_event); + + struct aws_subscription_status_record expected_empty_subscription_events[] = { + { + .type = ARRSET_SUBSCRIPTION_EMPTY, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 0, + }, + }; + + struct aws_subscription_status_record expected_unsubscribe_events[] = { + { + .type = ARRSET_UNSUBSCRIBE_COMPLETE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 0, + }, + }; + + ASSERT_FALSE(s_contains_subscription_event_records(&fixture, 1, expected_empty_subscription_events)); + ASSERT_FALSE(s_contains_subscription_event_records(&fixture, 1, expected_unsubscribe_events)); + + // verify no unsubscribes + struct aws_protocol_adapter_api_record expected_unsubscribe = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // release once, verify no unsubscribe + struct aws_rr_release_subscription_options release1_options = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .operation_id = 1, + }; + aws_rr_subscription_manager_release_subscription(manager, &release1_options); + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // verify empty event, but no unsubscribe event yet + ASSERT_TRUE(s_contains_subscription_event_records(&fixture, 1, expected_empty_subscription_events)); + ASSERT_FALSE(s_contains_subscription_event_records(&fixture, 1, expected_unsubscribe_events)); + + // unsubscribe is lazy, so we need to trigger it by acquiring something else + struct aws_rr_acquire_subscription_options acquire3_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .operation_id = 3, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); + + // now the unsubscribe should be present + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // complete the unsubscribe + struct aws_protocol_adapter_subscription_event successful_unsubscribe_event = { + .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .event_type = AWS_PASET_UNSUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_unsubscribe_event); + + // verify unsubscribe attempt emission + ASSERT_TRUE(s_contains_subscription_event_records(&fixture, 1, expected_unsubscribe_events)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +static int s_rrsm_request_subscription_purge_events_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_purge_test(allocator, ARRST_REQUEST_RESPONSE); +} + +AWS_TEST_CASE( + rrsm_request_subscription_purge_events, + s_rrsm_request_subscription_purge_events_fn) + +static int s_rrsm_streaming_subscription_purge_events_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + return s_do_purge_test(allocator, ARRST_EVENT_STREAM); +} + +AWS_TEST_CASE( + rrsm_streaming_subscription_purge_events, + s_rrsm_streaming_subscription_purge_events_fn) + From 8fa8c2075b25c7c8f1f468f7adf282a13ff31eca Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sat, 30 Mar 2024 12:03:16 -0700 Subject: [PATCH 091/124] Formatting --- tests/request-response/request_response_client_tests.c | 4 +++- tests/request-response/subscription_manager_tests.c | 9 ++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 94df98d9..1386a658 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -826,7 +826,9 @@ static void s_invalid_subscription_topic_filter_mutator(struct aws_mqtt_request_ request_options->subscription_topic_filter = aws_byte_cursor_from_c_str("a/#/c"); } -static int s_rrc_submit_request_operation_failure_invalid_subscription_topic_filter_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrc_submit_request_operation_failure_invalid_subscription_topic_filter_fn( + struct aws_allocator *allocator, + void *ctx) { (void)ctx; return s_rrc_do_submit_request_operation_failure_test(allocator, s_invalid_subscription_topic_filter_mutator); diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 6d4cf786..4b8f7929 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -2165,9 +2165,7 @@ static int s_rrsm_request_subscription_purge_events_fn(struct aws_allocator *all return s_do_purge_test(allocator, ARRST_REQUEST_RESPONSE); } -AWS_TEST_CASE( - rrsm_request_subscription_purge_events, - s_rrsm_request_subscription_purge_events_fn) +AWS_TEST_CASE(rrsm_request_subscription_purge_events, s_rrsm_request_subscription_purge_events_fn) static int s_rrsm_streaming_subscription_purge_events_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -2175,7 +2173,4 @@ static int s_rrsm_streaming_subscription_purge_events_fn(struct aws_allocator *a return s_do_purge_test(allocator, ARRST_EVENT_STREAM); } -AWS_TEST_CASE( - rrsm_streaming_subscription_purge_events, - s_rrsm_streaming_subscription_purge_events_fn) - +AWS_TEST_CASE(rrsm_streaming_subscription_purge_events, s_rrsm_streaming_subscription_purge_events_fn) From b858a25c23ee24b8c69656596237cddce9540fbb Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sat, 30 Mar 2024 16:32:10 -0700 Subject: [PATCH 092/124] Checkpoint pre-refactor --- include/aws/mqtt/mqtt.h | 1 + source/mqtt.c | 3 + .../request_response_client.c | 66 +++++++++++++++---- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/include/aws/mqtt/mqtt.h b/include/aws/mqtt/mqtt.h index 519378f5..bfdd73ff 100644 --- a/include/aws/mqtt/mqtt.h +++ b/include/aws/mqtt/mqtt.h @@ -87,6 +87,7 @@ enum aws_mqtt_error { AWS_ERROR_MQTT_REQUEST_RESPONSE_NO_SUBSCRIPTION_CAPACITY, AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE, AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR, + AWS_ERROR_MQTT_REQUEST_RESPONSE_PUBLISH_FAILURE, AWS_ERROR_END_MQTT_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_MQTT_PACKAGE_ID), }; diff --git a/source/mqtt.c b/source/mqtt.c index 0e7b0c0e..9a7d7662 100644 --- a/source/mqtt.c +++ b/source/mqtt.c @@ -251,6 +251,9 @@ bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter) AWS_DEFINE_ERROR_INFO_MQTT( AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR, "Request operation failed due to a non-specific internal error within the client."), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_REQUEST_RESPONSE_PUBLISH_FAILURE, + "Request-response operation failed because the associated publish failed synchronously."), }; /* clang-format on */ diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 66264104..d0036367 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -720,6 +720,36 @@ static void s_mqtt_request_response_client_wake_service(struct aws_mqtt_request_ } } +struct aws_rrc_incomplete_publish { + struct aws_allocator *allocator; + + struct aws_mqtt_request_response_client *rr_client; + + uint64_t operation_id; +}; + +static void s_make_mqtt_request( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_rr_client_operation *operation) { + (void)client; + + AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); + + struct aws_mqtt_request_operation_options *request_options = &operation->storage.request_storage.options; + + struct aws_protocol_adapter_publish_options publish_options = { + .topic = request_options->publish_topic, + .payload = request_options->serialized_request, + .ack_timeout_seconds = client->config.operation_timeout_seconds, + .completion_callback_fn = s_??, + .user_data = ??, + }; + + if (aws_mqtt_protocol_adapter_publish(client->client_adapter, &publish_options)) { + s_complete_request_operation_with_failure(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_PUBLISH_FAILURE); + } +} + struct aws_rr_subscription_status_event_task { struct aws_allocator *allocator; @@ -743,6 +773,30 @@ static void s_aws_rr_subscription_status_event_task_delete(struct aws_rr_subscri aws_mem_release(task->allocator, task); } +static void s_on_request_operation_subscription_status_event( + struct aws_mqtt_rr_client_operation *operation, + struct aws_byte_cursor topic_filter, + enum aws_rr_subscription_event_type event_type) { + (void)topic_filter; + + switch (event_type) { + case ARRSET_REQUEST_SUBSCRIBE_FAILURE: + case ARRSET_REQUEST_SUBSCRIPTION_ENDED: + s_complete_request_operation_with_failure(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE); + break; + + case ARRSET_REQUEST_SUBSCRIBE_SUCCESS: + if (operation->state == AWS_MRROS_PENDING_SUBSCRIPTION) { + s_change_operation_state(operation, AWS_MRROS_PENDING_RESPONSE); + s_make_mqtt_request(operation->client_internal_ref, operation); + } + break; + + default: + AWS_FATAL_ASSERT(false); + } +} + static void s_on_streaming_operation_subscription_status_event( struct aws_mqtt_rr_client_operation *operation, struct aws_byte_cursor topic_filter, @@ -799,7 +853,7 @@ static void s_handle_subscription_status_event_task(struct aws_task *task, void case ARRSET_REQUEST_SUBSCRIBE_SUCCESS: case ARRSET_REQUEST_SUBSCRIBE_FAILURE: case ARRSET_REQUEST_SUBSCRIPTION_ENDED: - /* NYI */ + s_on_request_operation_subscription_status_event(operation, aws_byte_cursor_from_buf(&event_task->topic_filter), event_task->type); break; case ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED: @@ -1187,16 +1241,6 @@ static int s_add_operation_to_subscription_topic_filter_table( return AWS_OP_SUCCESS; } -static void s_make_mqtt_request( - struct aws_mqtt_request_response_client *client, - struct aws_mqtt_rr_client_operation *operation) { - (void)client; - - AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); - - // TODO: NYI -} - static void s_handle_operation_subscribe_result( struct aws_mqtt_request_response_client *client, struct aws_mqtt_rr_client_operation *operation, From 84bd2bded22edeec63cfe9b96c3aeb6fc70c4e5e Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sat, 30 Mar 2024 17:40:15 -0700 Subject: [PATCH 093/124] Remove weak ref and replace with intrusive linked list of incomplete operations --- .../request_response_client.h | 23 ++ .../mqtt/private/request-response/weak_ref.h | 66 ----- source/request-response/protocol_adapter.c | 234 +++++++++++------- .../request_response_client.c | 12 +- source/request-response/weak_ref.c | 54 ---- 5 files changed, 171 insertions(+), 218 deletions(-) create mode 100644 include/aws/mqtt/private/request-response/request_response_client.h delete mode 100644 include/aws/mqtt/private/request-response/weak_ref.h delete mode 100644 source/request-response/weak_ref.c diff --git a/include/aws/mqtt/private/request-response/request_response_client.h b/include/aws/mqtt/private/request-response/request_response_client.h new file mode 100644 index 00000000..d0109f24 --- /dev/null +++ b/include/aws/mqtt/private/request-response/request_response_client.h @@ -0,0 +1,23 @@ +#ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H +#define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +struct aws_mqtt_request_response_client; + +AWS_EXTERN_C_BEGIN + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire_internal( + struct aws_mqtt_request_response_client *client); + +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release_internal( + struct aws_mqtt_request_response_client *client); + +AWS_EXTERN_C_END + +#endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H */ \ No newline at end of file diff --git a/include/aws/mqtt/private/request-response/weak_ref.h b/include/aws/mqtt/private/request-response/weak_ref.h deleted file mode 100644 index cff0a2d8..00000000 --- a/include/aws/mqtt/private/request-response/weak_ref.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_WEAK_REF_H -#define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_WEAK_REF_H - -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -#include - -#include - -/* - * This is a simplification of the notion of a weak reference particular to the needs of the request-response - * MQTT service clients. This is not suitable for general use but could be extended - * for general use in the future. Until then, it stays private, here. - * - * This weak reference is a ref-counted object with an opaque value. The opaque value may be cleared or - * queried. These two operations *do not* provide any thread safety. - * - * The primary use is to allow one object to safely use asynchronous callback-driven APIs on a second object, despite - * the fact that the first object may get destroyed unpredictably. The two objects must be exclusive to a single - * event loop (because there's no thread safety or mutual exclusion on the opaque value held by the weak ref). - * - * The initial use is the request-response protocol adapter submitting operations to an MQTT client or an - * eventstream RPC connection. We use a single weak ref to the protocol adapter and zero its opaque value when - * the protocol adapter is destroyed. Operation callbacks that subsequently resolve can then short circuit and do - * nothing rather than call into garbage and crash. - * - * We use this rather than explicitly tracking and zeroing all pending operations (like the 3-to-5 adapter does) - * because this approach is simpler and our usage does not require any of these callbacks to be invoked once the - * request-response client is destroyed. - */ -struct aws_weak_ref; - -AWS_EXTERN_C_BEGIN - -/* - * Creates a new weak reference to an opaque value. - */ -AWS_MQTT_API struct aws_weak_ref *aws_weak_ref_new(struct aws_allocator *allocator, void *referenced); - -/* - * Acquires a reference to the weak ref object. - */ -AWS_MQTT_API struct aws_weak_ref *aws_weak_ref_acquire(struct aws_weak_ref *weak_ref); - -/* - * Removes a reference to the weak ref object. When the last reference is removed, the weak ref object will be - * destroyed. This has no effect on the opaque value held by the weak ref. - */ -AWS_MQTT_API struct aws_weak_ref *aws_weak_ref_release(struct aws_weak_ref *weak_ref); - -/* - * Gets the current value of the opaque data held by the weak ref. - */ -AWS_MQTT_API void *aws_weak_ref_get_reference(struct aws_weak_ref *weak_ref); - -/* - * Clears the opaque data held by the weak ref. - */ -AWS_MQTT_API void aws_weak_ref_zero_reference(struct aws_weak_ref *weak_ref); - -AWS_EXTERN_C_END - -#endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_WEAK_REF_H */ diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index fefa7000..8aa244cb 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -35,64 +34,84 @@ * Retries, when appropriate, are the responsibility of the caller. */ -// used by both subscribe and unsubscribe -struct aws_mqtt_protocol_adapter_subscription_op_data { - struct aws_allocator *allocator; +enum aws_mqtt_protocol_adapter_operation_type { + AMPAOT_SUBSCRIBE_UNSUBSCRIBE, + AMPAOT_PUBLISH, +}; +struct aws_mqtt_protocol_adapter_sub_unsub_data { struct aws_byte_buf topic_filter; - struct aws_weak_ref *callback_ref; }; -static struct aws_mqtt_protocol_adapter_subscription_op_data *s_aws_mqtt_protocol_adapter_subscription_op_data_new( +struct aws_mqtt_protocol_adapter_publish_data { + void (*completion_callback_fn)(int, void *); + void *user_data; +}; + +struct aws_mqtt_protocol_adapter_operation_userdata { + struct aws_allocator *allocator; + + struct aws_linked_list_node node; + void *adapter; + + enum aws_mqtt_protocol_adapter_operation_type operation_type; + + union { + struct aws_mqtt_protocol_adapter_sub_unsub_data sub_unsub_data; + struct aws_mqtt_protocol_adapter_publish_data publish_data; + } operation_data; +}; + +static struct aws_mqtt_protocol_adapter_operation_userdata *s_aws_mqtt_protocol_adapter_sub_unsub_data_new( struct aws_allocator *allocator, struct aws_byte_cursor topic_filter, - struct aws_weak_ref *callback_ref) { - struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = - aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_subscription_op_data)); + void *adapter) { + + struct aws_mqtt_protocol_adapter_operation_userdata *subscribe_data = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_operation_userdata)); subscribe_data->allocator = allocator; - subscribe_data->callback_ref = aws_weak_ref_acquire(callback_ref); - aws_byte_buf_init_copy_from_cursor(&subscribe_data->topic_filter, allocator, topic_filter); + subscribe_data->operation_type = AMPAOT_SUBSCRIBE_UNSUBSCRIBE; + subscribe_data->adapter = adapter; + aws_byte_buf_init_copy_from_cursor( + &subscribe_data->operation_data.sub_unsub_data.topic_filter, allocator, topic_filter); return subscribe_data; } -static void s_aws_mqtt_protocol_adapter_subscription_op_data_destroy( - struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data) { - aws_weak_ref_release(subscribe_data->callback_ref); - aws_byte_buf_clean_up(&subscribe_data->topic_filter); - - aws_mem_release(subscribe_data->allocator, subscribe_data); -} - -struct aws_mqtt_protocol_adapter_publish_op_data { - struct aws_allocator *allocator; - struct aws_weak_ref *callback_ref; - - void (*completion_callback_fn)(int, void *); - void *user_data; -}; - -static struct aws_mqtt_protocol_adapter_publish_op_data *s_aws_mqtt_protocol_adapter_publish_op_data_new( +static struct aws_mqtt_protocol_adapter_operation_userdata *s_aws_mqtt_protocol_adapter_publish_data_new( struct aws_allocator *allocator, const struct aws_protocol_adapter_publish_options *publish_options, - struct aws_weak_ref *callback_ref) { - struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = - aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_publish_op_data)); + void *adapter) { + + struct aws_mqtt_protocol_adapter_operation_userdata *publish_data = + aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_protocol_adapter_operation_userdata)); publish_data->allocator = allocator; - publish_data->callback_ref = aws_weak_ref_acquire(callback_ref); - publish_data->completion_callback_fn = publish_options->completion_callback_fn; - publish_data->user_data = publish_options->user_data; + publish_data->operation_type = AMPAOT_PUBLISH; + publish_data->adapter = adapter; + + publish_data->operation_data.publish_data.completion_callback_fn = publish_options->completion_callback_fn; + publish_data->operation_data.publish_data.user_data = publish_options->user_data; return publish_data; } -static void s_aws_mqtt_protocol_adapter_publish_op_data_destroy( - struct aws_mqtt_protocol_adapter_publish_op_data *publish_data) { - aws_weak_ref_release(publish_data->callback_ref); +static void s_aws_mqtt_protocol_adapter_operation_user_data_destroy( + struct aws_mqtt_protocol_adapter_operation_userdata *userdata) { + if (userdata == NULL) { + return; + } + + if (aws_linked_list_node_next_is_valid(&userdata->node) && aws_linked_list_node_prev_is_valid(&userdata->node)) { + aws_linked_list_remove(&userdata->node); + } - aws_mem_release(publish_data->allocator, publish_data); + if (userdata->operation_type == AMPAOT_SUBSCRIBE_UNSUBSCRIBE) { + aws_byte_buf_clean_up(&userdata->operation_data.sub_unsub_data.topic_filter); + } + + aws_mem_release(userdata->allocator, userdata); } /*****************************************************************************************************************/ @@ -100,7 +119,8 @@ static void s_aws_mqtt_protocol_adapter_publish_op_data_destroy( struct aws_mqtt_protocol_adapter_311_impl { struct aws_allocator *allocator; struct aws_mqtt_protocol_adapter base; - struct aws_weak_ref *callback_ref; + + struct aws_linked_list incomplete_operations; struct aws_mqtt_protocol_adapter_options config; struct aws_event_loop *loop; @@ -128,8 +148,8 @@ static void s_protocol_adapter_311_subscribe_completion( (void)topic; (void)packet_id; - struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = userdata; - struct aws_mqtt_protocol_adapter_311_impl *adapter = aws_weak_ref_get_reference(subscribe_data->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *subscribe_data = userdata; + struct aws_mqtt_protocol_adapter_311_impl *adapter = subscribe_data->adapter; if (adapter == NULL) { goto done; @@ -142,7 +162,7 @@ static void s_protocol_adapter_311_subscribe_completion( } struct aws_protocol_adapter_subscription_event subscribe_event = { - .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), + .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->operation_data.sub_unsub_data.topic_filter), .event_type = AWS_PASET_SUBSCRIBE, .error_code = error_code, .retryable = true, @@ -152,16 +172,17 @@ static void s_protocol_adapter_311_subscribe_completion( done: - s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(subscribe_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(subscribe_data); } int s_aws_mqtt_protocol_adapter_311_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; struct aws_mqtt_client_connection_311_impl *connection_impl = adapter->connection->impl; - struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = - s_aws_mqtt_protocol_adapter_subscription_op_data_new( - adapter->allocator, options->topic_filter, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *subscribe_data = + s_aws_mqtt_protocol_adapter_sub_unsub_data_new(adapter->allocator, options->topic_filter, adapter); + + aws_linked_list_push_back(&adapter->incomplete_operations, &subscribe_data->node); uint64_t timeout_nanos = aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); @@ -182,7 +203,7 @@ int s_aws_mqtt_protocol_adapter_311_subscribe(void *impl, struct aws_protocol_ad error: - s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(subscribe_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(subscribe_data); return AWS_OP_ERR; } @@ -201,15 +222,15 @@ static void s_protocol_adapter_311_unsubscribe_completion( (void)connection; (void)packet_id; - struct aws_mqtt_protocol_adapter_subscription_op_data *unsubscribe_data = userdata; - struct aws_mqtt_protocol_adapter_311_impl *adapter = aws_weak_ref_get_reference(unsubscribe_data->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *unsubscribe_data = userdata; + struct aws_mqtt_protocol_adapter_311_impl *adapter = unsubscribe_data->adapter; if (adapter == NULL) { goto done; } struct aws_protocol_adapter_subscription_event unsubscribe_event = { - .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), + .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->operation_data.sub_unsub_data.topic_filter), .event_type = AWS_PASET_UNSUBSCRIBE, .error_code = error_code, .retryable = s_is_retryable_unsubscribe311(error_code), @@ -219,16 +240,17 @@ static void s_protocol_adapter_311_unsubscribe_completion( done: - s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(unsubscribe_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(unsubscribe_data); } int s_aws_mqtt_protocol_adapter_311_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; struct aws_mqtt_client_connection_311_impl *connection_impl = adapter->connection->impl; - struct aws_mqtt_protocol_adapter_subscription_op_data *unsubscribe_data = - s_aws_mqtt_protocol_adapter_subscription_op_data_new( - adapter->allocator, options->topic_filter, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *unsubscribe_data = + s_aws_mqtt_protocol_adapter_sub_unsub_data_new(adapter->allocator, options->topic_filter, adapter); + + aws_linked_list_push_back(&adapter->incomplete_operations, &unsubscribe_data->node); uint64_t timeout_nanos = aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); @@ -245,7 +267,7 @@ int s_aws_mqtt_protocol_adapter_311_unsubscribe(void *impl, struct aws_protocol_ error: - s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(unsubscribe_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(unsubscribe_data); return AWS_OP_ERR; } @@ -261,26 +283,29 @@ static void s_protocol_adapter_311_publish_completion( (void)connection; (void)packet_id; - struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = userdata; - struct aws_mqtt_protocol_adapter_311_impl *adapter = aws_weak_ref_get_reference(publish_data->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *publish_data = userdata; + struct aws_mqtt_protocol_adapter_311_impl *adapter = publish_data->adapter; if (adapter == NULL) { goto done; } - (*publish_data->completion_callback_fn)(error_code, publish_data->user_data); + (*publish_data->operation_data.publish_data.completion_callback_fn)( + error_code, publish_data->operation_data.publish_data.user_data); done: - s_aws_mqtt_protocol_adapter_publish_op_data_destroy(publish_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(publish_data); } int s_aws_mqtt_protocol_adapter_311_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; struct aws_mqtt_client_connection_311_impl *connection_impl = adapter->connection->impl; - struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = - s_aws_mqtt_protocol_adapter_publish_op_data_new(adapter->allocator, options, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *publish_data = + s_aws_mqtt_protocol_adapter_publish_data_new(adapter->allocator, options, adapter); + + aws_linked_list_push_back(&adapter->incomplete_operations, &publish_data->node); uint64_t timeout_nanos = aws_timestamp_convert(options->ack_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); @@ -300,7 +325,7 @@ int s_aws_mqtt_protocol_adapter_311_publish(void *impl, struct aws_protocol_adap error: - s_aws_mqtt_protocol_adapter_publish_op_data_destroy(publish_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(publish_data); return AWS_OP_ERR; } @@ -390,14 +415,35 @@ static bool s_aws_mqtt_protocol_adapter_311_is_connected(void *impl) { return current_state == AWS_MQTT_CLIENT_STATE_CONNECTED; } +static void s_release_incomplete_operations(struct aws_linked_list *incomplete_operations) { + struct aws_linked_list dummy_list; + aws_linked_list_init(&dummy_list); + aws_linked_list_swap_contents(incomplete_operations, &dummy_list); + + while (!aws_linked_list_empty(&dummy_list)) { + struct aws_linked_list_node *head = aws_linked_list_pop_front(&dummy_list); + struct aws_mqtt_protocol_adapter_operation_userdata *userdata = + AWS_CONTAINER_OF(head, struct aws_mqtt_protocol_adapter_operation_userdata, node); + + userdata->adapter = NULL; + + if (userdata->operation_type == AMPAOT_PUBLISH) { + struct aws_mqtt_protocol_adapter_publish_data *publish_data = &userdata->operation_data.publish_data; + if (publish_data->completion_callback_fn != NULL) { + (*userdata->operation_data.publish_data.completion_callback_fn)( + AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, publish_data->user_data); + } + } + } +} + static void s_protocol_adapter_mqtt311_listener_termination_callback(void *user_data) { struct aws_mqtt_protocol_adapter_311_impl *adapter = user_data; struct aws_mqtt_client_connection_311_impl *impl = adapter->connection->impl; AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(impl->loop)); - aws_weak_ref_zero_reference(adapter->callback_ref); - aws_weak_ref_release(adapter->callback_ref); + s_release_incomplete_operations(&adapter->incomplete_operations); aws_mqtt_client_connection_release(adapter->connection); @@ -442,7 +488,7 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( adapter->allocator = allocator; adapter->base.impl = adapter; adapter->base.vtable = &s_protocol_adapter_mqtt311_vtable; - adapter->callback_ref = aws_weak_ref_new(allocator, adapter); + aws_linked_list_init(&adapter->incomplete_operations); adapter->config = *options; adapter->loop = impl->loop; adapter->connection = aws_mqtt_client_connection_acquire(connection); @@ -471,7 +517,7 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( struct aws_mqtt_protocol_adapter_5_impl { struct aws_allocator *allocator; struct aws_mqtt_protocol_adapter base; - struct aws_weak_ref *callback_ref; + struct aws_linked_list incomplete_operations; struct aws_mqtt_protocol_adapter_options config; struct aws_event_loop *loop; @@ -514,8 +560,8 @@ static void s_protocol_adapter_5_subscribe_completion( const struct aws_mqtt5_packet_suback_view *suback, int error_code, void *complete_ctx) { - struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = complete_ctx; - struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(subscribe_data->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *subscribe_data = complete_ctx; + struct aws_mqtt_protocol_adapter_5_impl *adapter = subscribe_data->adapter; if (adapter == NULL) { goto done; @@ -534,7 +580,7 @@ static void s_protocol_adapter_5_subscribe_completion( } struct aws_protocol_adapter_subscription_event subscribe_event = { - .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->topic_filter), + .topic_filter = aws_byte_cursor_from_buf(&subscribe_data->operation_data.sub_unsub_data.topic_filter), .event_type = AWS_PASET_SUBSCRIBE, .error_code = error_code, .retryable = is_retryable, @@ -544,15 +590,16 @@ static void s_protocol_adapter_5_subscribe_completion( done: - s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(subscribe_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(subscribe_data); } int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adapter_subscribe_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_subscription_op_data *subscribe_data = - s_aws_mqtt_protocol_adapter_subscription_op_data_new( - adapter->allocator, options->topic_filter, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *subscribe_data = + s_aws_mqtt_protocol_adapter_sub_unsub_data_new(adapter->allocator, options->topic_filter, adapter); + + aws_linked_list_push_back(&adapter->incomplete_operations, &subscribe_data->node); struct aws_mqtt5_subscription_view subscription_view = { .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, @@ -578,7 +625,7 @@ int s_aws_mqtt_protocol_adapter_5_subscribe(void *impl, struct aws_protocol_adap error: - s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(subscribe_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(subscribe_data); return AWS_OP_ERR; } @@ -607,8 +654,8 @@ static void s_protocol_adapter_5_unsubscribe_completion( const struct aws_mqtt5_packet_unsuback_view *unsuback, int error_code, void *complete_ctx) { - struct aws_mqtt_protocol_adapter_subscription_op_data *unsubscribe_data = complete_ctx; - struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(unsubscribe_data->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *unsubscribe_data = complete_ctx; + struct aws_mqtt_protocol_adapter_5_impl *adapter = unsubscribe_data->adapter; if (adapter == NULL) { goto done; @@ -628,7 +675,7 @@ static void s_protocol_adapter_5_unsubscribe_completion( } struct aws_protocol_adapter_subscription_event unsubscribe_event = { - .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->topic_filter), + .topic_filter = aws_byte_cursor_from_buf(&unsubscribe_data->operation_data.sub_unsub_data.topic_filter), .event_type = AWS_PASET_UNSUBSCRIBE, .error_code = error_code, .retryable = is_retryable, @@ -638,15 +685,16 @@ static void s_protocol_adapter_5_unsubscribe_completion( done: - s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(unsubscribe_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(unsubscribe_data); } int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_adapter_unsubscribe_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_subscription_op_data *unsubscribe_data = - s_aws_mqtt_protocol_adapter_subscription_op_data_new( - adapter->allocator, options->topic_filter, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *unsubscribe_data = + s_aws_mqtt_protocol_adapter_sub_unsub_data_new(adapter->allocator, options->topic_filter, adapter); + + aws_linked_list_push_back(&adapter->incomplete_operations, &unsubscribe_data->node); struct aws_mqtt5_packet_unsubscribe_view unsubscribe_view = { .topic_filters = &options->topic_filter, @@ -667,7 +715,7 @@ int s_aws_mqtt_protocol_adapter_5_unsubscribe(void *impl, struct aws_protocol_ad error: - s_aws_mqtt_protocol_adapter_subscription_op_data_destroy(unsubscribe_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(unsubscribe_data); return AWS_OP_ERR; } @@ -679,8 +727,8 @@ static void s_protocol_adapter_5_publish_completion( const void *packet, int error_code, void *complete_ctx) { - struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = complete_ctx; - struct aws_mqtt_protocol_adapter_5_impl *adapter = aws_weak_ref_get_reference(publish_data->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *publish_data = complete_ctx; + struct aws_mqtt_protocol_adapter_5_impl *adapter = publish_data->adapter; if (adapter == NULL) { goto done; @@ -693,17 +741,20 @@ static void s_protocol_adapter_5_publish_completion( } } - (*publish_data->completion_callback_fn)(error_code, publish_data->user_data); + (*publish_data->operation_data.publish_data.completion_callback_fn)( + error_code, publish_data->operation_data.publish_data.user_data); done: - s_aws_mqtt_protocol_adapter_publish_op_data_destroy(publish_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(publish_data); } int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapter_publish_options *options) { struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; - struct aws_mqtt_protocol_adapter_publish_op_data *publish_data = - s_aws_mqtt_protocol_adapter_publish_op_data_new(adapter->allocator, options, adapter->callback_ref); + struct aws_mqtt_protocol_adapter_operation_userdata *publish_data = + s_aws_mqtt_protocol_adapter_publish_data_new(adapter->allocator, options, adapter); + + aws_linked_list_push_back(&adapter->incomplete_operations, &publish_data->node); struct aws_mqtt5_packet_publish_view publish_view = { .topic = options->topic, .qos = AWS_MQTT5_QOS_AT_LEAST_ONCE, .payload = options->payload}; @@ -722,7 +773,7 @@ int s_aws_mqtt_protocol_adapter_5_publish(void *impl, struct aws_protocol_adapte error: - s_aws_mqtt_protocol_adapter_publish_op_data_destroy(publish_data); + s_aws_mqtt_protocol_adapter_operation_user_data_destroy(publish_data); return AWS_OP_ERR; } @@ -783,8 +834,7 @@ static void s_protocol_adapter_mqtt5_listener_termination_callback(void *user_da AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(adapter->client->loop)); - aws_weak_ref_zero_reference(adapter->callback_ref); - aws_weak_ref_release(adapter->callback_ref); + s_release_incomplete_operations(&adapter->incomplete_operations); aws_mqtt5_client_release(adapter->client); @@ -822,7 +872,7 @@ struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( adapter->allocator = allocator; adapter->base.impl = adapter; adapter->base.vtable = &s_protocol_adapter_mqtt5_vtable; - adapter->callback_ref = aws_weak_ref_new(allocator, adapter); + aws_linked_list_init(&adapter->incomplete_operations); adapter->config = *options; adapter->loop = client->loop; adapter->client = aws_mqtt5_client_acquire(client); diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 66264104..c8d49658 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -487,7 +487,7 @@ struct aws_mqtt_request_response_client { struct aws_hash_table operation_lists_by_subscription_filter; }; -struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_acquire_internal( +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire_internal( struct aws_mqtt_request_response_client *client) { if (client != NULL) { aws_ref_count_acquire(&client->internal_ref_count); @@ -496,7 +496,7 @@ struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_acqu return client; } -struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_client_release_internal( +struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release_internal( struct aws_mqtt_request_response_client *client) { if (client != NULL) { aws_ref_count_release(&client->internal_ref_count); @@ -738,7 +738,7 @@ static void s_aws_rr_subscription_status_event_task_delete(struct aws_rr_subscri } aws_byte_buf_clean_up(&task->topic_filter); - s_aws_mqtt_request_response_client_release_internal(task->rr_client); + aws_mqtt_request_response_client_release_internal(task->rr_client); aws_mem_release(task->allocator, task); } @@ -828,7 +828,7 @@ static struct aws_rr_subscription_status_event_task *s_aws_rr_subscription_statu task->allocator = allocator; task->type = event->type; task->operation_id = event->operation_id; - task->rr_client = s_aws_mqtt_request_response_client_acquire_internal(rr_client); + task->rr_client = aws_mqtt_request_response_client_acquire_internal(rr_client); aws_byte_buf_init_copy_from_cursor(&task->topic_filter, allocator, event->topic_filter); @@ -1621,7 +1621,7 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, */ - s_aws_mqtt_request_response_client_release_internal(operation->client_internal_ref); + aws_mqtt_request_response_client_release_internal(operation->client_internal_ref); if (operation->type == AWS_MRROT_STREAMING) { s_aws_mqtt_streaming_operation_storage_clean_up(&operation->storage.streaming_storage); @@ -1661,7 +1661,7 @@ static void s_aws_mqtt_rr_client_operation_init_shared( */ aws_mqtt_rr_client_operation_acquire(operation); - operation->client_internal_ref = s_aws_mqtt_request_response_client_acquire_internal(client); + operation->client_internal_ref = aws_mqtt_request_response_client_acquire_internal(client); operation->id = s_aws_mqtt_request_response_client_allocate_operation_id(client); s_change_operation_state(operation, AWS_MRROS_NONE); diff --git a/source/request-response/weak_ref.c b/source/request-response/weak_ref.c deleted file mode 100644 index 949014e7..00000000 --- a/source/request-response/weak_ref.c +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -#include - -#include - -struct aws_weak_ref { - struct aws_allocator *allocator; - struct aws_ref_count refcount; - void *referenced; -}; - -static void s_destroy_weak_ref(void *value) { - struct aws_weak_ref *weak_ref = value; - - aws_mem_release(weak_ref->allocator, weak_ref); -} - -struct aws_weak_ref *aws_weak_ref_new(struct aws_allocator *allocator, void *referenced) { - struct aws_weak_ref *weak_ref = aws_mem_calloc(allocator, 1, sizeof(struct aws_weak_ref)); - - aws_ref_count_init(&weak_ref->refcount, weak_ref, s_destroy_weak_ref); - weak_ref->allocator = allocator; - weak_ref->referenced = referenced; - - return weak_ref; -} - -struct aws_weak_ref *aws_weak_ref_acquire(struct aws_weak_ref *weak_ref) { - if (NULL != weak_ref) { - aws_ref_count_acquire(&weak_ref->refcount); - } - - return weak_ref; -} - -struct aws_weak_ref *aws_weak_ref_release(struct aws_weak_ref *weak_ref) { - if (NULL != weak_ref) { - aws_ref_count_release(&weak_ref->refcount); - } - - return NULL; -} - -void *aws_weak_ref_get_reference(struct aws_weak_ref *weak_ref) { - return weak_ref->referenced; -} - -void aws_weak_ref_zero_reference(struct aws_weak_ref *weak_ref) { - weak_ref->referenced = NULL; -} From fa8eff2f8ee4cb51852f99495b8b1df48c011c0a Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 1 Apr 2024 09:11:02 -0700 Subject: [PATCH 094/124] Wording --- .../aws/mqtt/private/request-response/subscription_manager.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 20e1b57f..8118d1a4 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -60,13 +60,15 @@ enum aws_rr_subscription_event_type { /* * A subscription has lost its last listener and can be purged + * + * This event is global; operation_id will always be zero. */ ARRSET_SUBSCRIPTION_EMPTY, /* * A subscription has been unsubscribed from * - * This particular event is global, with an operation id of 0. + * This event is global; operation_id will always be zero. */ ARRSET_UNSUBSCRIBE_COMPLETE, }; From c4313c89848e9fb1556d3c9c11e42fe1e16f2594 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 1 Apr 2024 14:10:39 -0700 Subject: [PATCH 095/124] Request publish support --- .../request_response_client.c | 243 ++++++++++++++++-- 1 file changed, 221 insertions(+), 22 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index ba8afb29..ac3eb7eb 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -17,6 +17,7 @@ #include #define MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE 50 +#define MQTT_RR_CLIENT_RESPONSE_TABLE_DEFAULT_SIZE 50 enum aws_mqtt_request_response_operation_type { AWS_MRROT_REQUEST, @@ -152,6 +153,48 @@ static void s_aws_rr_operation_list_topic_filter_entry_hash_element_destroy(void s_aws_rr_operation_list_topic_filter_entry_destroy(value); } +struct aws_rr_response_path_entry { + struct aws_allocator *allocator; + + size_t ref_count; + + struct aws_byte_cursor topic_cursor; + struct aws_byte_buf topic; + + struct aws_byte_buf correlation_token_json_path; +}; + +static struct aws_rr_response_path_entry *s_aws_rr_response_path_entry_new( + struct aws_allocator *allocator, + struct aws_byte_cursor topic, + struct aws_byte_cursor correlation_token_json_path) { + struct aws_rr_response_path_entry *entry = aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_response_path_entry)); + + entry->allocator = allocator; + entry->ref_count = 1; + aws_byte_buf_init_copy_from_cursor(&entry->topic, allocator, topic); + entry->topic_cursor = aws_byte_cursor_from_buf(&entry->topic); + + aws_byte_buf_init_copy_from_cursor(&entry->correlation_token_json_path, allocator, correlation_token_json_path); + + return entry; +} + +static void s_aws_rr_response_path_entry_destroy(struct aws_rr_response_path_entry *entry) { + if (entry == NULL) { + return; + } + + aws_byte_buf_clean_up(&entry->topic); + aws_byte_buf_clean_up(&entry->correlation_token_json_path); + + aws_mem_release(entry->allocator, entry); +} + +static void s_aws_rr_response_path_table_hash_element_destroy(void *value) { + s_aws_rr_response_path_entry_destroy(value); +} + /* All operations have an internal ref to the client they are a part of */ /* @@ -484,7 +527,18 @@ struct aws_mqtt_request_response_client { */ struct aws_priority_queue operations_by_timeout; - struct aws_hash_table operation_lists_by_subscription_filter; + /* + * Map from cursor (topic filter) -> list of streaming operations using that filter + */ + struct aws_hash_table streaming_operation_subscription_lists; + + /* + * Map from cursor (topic) -> request response path (topic, correlation token json path) + * + * We don't garbage collect this table over the course of normal client operation. We only clean it up + * when the client is shutting down. + */ + struct aws_hash_table request_response_paths; }; struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire_internal( @@ -527,7 +581,8 @@ static void s_mqtt_request_response_client_final_destroy(struct aws_mqtt_request aws_hash_table_clean_up(&client->operations); aws_priority_queue_clean_up(&client->operations_by_timeout); - aws_hash_table_clean_up(&client->operation_lists_by_subscription_filter); + aws_hash_table_clean_up(&client->streaming_operation_subscription_lists); + aws_hash_table_clean_up(&client->request_response_paths); aws_mem_release(client->allocator, client); @@ -728,6 +783,41 @@ struct aws_rrc_incomplete_publish { uint64_t operation_id; }; +static void s_aws_rrc_incomplete_publish_destroy(struct aws_rrc_incomplete_publish *user_data) { + if (user_data == NULL) { + return; + } + + aws_mqtt_request_response_client_release_internal(user_data->rr_client); + + aws_mem_release(user_data->allocator, user_data); +} + +static void s_on_request_publish_completion(int error_code, void *userdata) { + struct aws_rrc_incomplete_publish *publish_user_data = userdata; + + if (error_code != AWS_ERROR_SUCCESS) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response operation %" PRIu64 " failed publish step due to error %d(%s)", + (void *)publish_user_data->rr_client, + publish_user_data->operation_id, + error_code, + aws_error_debug_str(error_code)); + + struct aws_hash_element *element = NULL; + if (aws_hash_table_find( + &publish_user_data->rr_client->operations, &publish_user_data->operation_id, &element) == + AWS_OP_SUCCESS && + element != NULL) { + struct aws_mqtt_rr_client_operation *operation = element->value; + s_complete_request_operation_with_failure(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_PUBLISH_FAILURE); + } + } + + s_aws_rrc_incomplete_publish_destroy(publish_user_data); +} + static void s_make_mqtt_request( struct aws_mqtt_request_response_client *client, struct aws_mqtt_rr_client_operation *operation) { @@ -737,17 +827,39 @@ static void s_make_mqtt_request( struct aws_mqtt_request_operation_options *request_options = &operation->storage.request_storage.options; + struct aws_rrc_incomplete_publish *publish_user_data = + aws_mem_calloc(client->allocator, 1, sizeof(struct aws_rrc_incomplete_publish)); + publish_user_data->allocator = client->allocator; + publish_user_data->rr_client = aws_mqtt_request_response_client_acquire_internal(client); + publish_user_data->operation_id = operation->id; + struct aws_protocol_adapter_publish_options publish_options = { .topic = request_options->publish_topic, .payload = request_options->serialized_request, .ack_timeout_seconds = client->config.operation_timeout_seconds, - .completion_callback_fn = s_??, - .user_data = ??, + .completion_callback_fn = s_on_request_publish_completion, + .user_data = publish_user_data, }; if (aws_mqtt_protocol_adapter_publish(client->client_adapter, &publish_options)) { + int error_code = aws_last_error(); + + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response operation %" PRIu64 " synchronously failed publish step due to error %d(%s)", + (void *)publish_user_data->rr_client, + publish_user_data->operation_id, + error_code, + aws_error_debug_str(error_code)); s_complete_request_operation_with_failure(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_PUBLISH_FAILURE); + goto error; } + + return; + +error: + + s_aws_rrc_incomplete_publish_destroy(publish_user_data); } struct aws_rr_subscription_status_event_task { @@ -853,7 +965,8 @@ static void s_handle_subscription_status_event_task(struct aws_task *task, void case ARRSET_REQUEST_SUBSCRIBE_SUCCESS: case ARRSET_REQUEST_SUBSCRIBE_FAILURE: case ARRSET_REQUEST_SUBSCRIPTION_ENDED: - s_on_request_operation_subscription_status_event(operation, aws_byte_cursor_from_buf(&event_task->topic_filter), event_task->type); + s_on_request_operation_subscription_status_event( + operation, aws_byte_cursor_from_buf(&event_task->topic_filter), event_task->type); break; case ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED: @@ -982,7 +1095,7 @@ static void s_aws_rr_client_protocol_adapter_incoming_publish_callback( /* Streaming operation handling */ struct aws_hash_element *subscription_filter_element = NULL; if (aws_hash_table_find( - &rr_client->operation_lists_by_subscription_filter, &publish_event->topic, &subscription_filter_element) == + &rr_client->streaming_operation_subscription_lists, &publish_event->topic, &subscription_filter_element) == AWS_OP_SUCCESS) { if (subscription_filter_element != NULL) { AWS_LOGF_DEBUG( @@ -1093,7 +1206,7 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie s_compare_rr_operation_timeouts); aws_hash_table_init( - &rr_client->operation_lists_by_subscription_filter, + &rr_client->streaming_operation_subscription_lists, allocator, MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE, aws_hash_byte_cursor_ptr, @@ -1101,6 +1214,15 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie NULL, s_aws_rr_operation_list_topic_filter_entry_hash_element_destroy); + aws_hash_table_init( + &rr_client->request_response_paths, + allocator, + MQTT_RR_CLIENT_RESPONSE_TABLE_DEFAULT_SIZE, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + s_aws_rr_response_path_table_hash_element_destroy); + aws_linked_list_init(&rr_client->operation_queue); aws_task_init( @@ -1198,24 +1320,24 @@ static bool s_is_operation_in_list(const struct aws_mqtt_rr_client_operation *op return aws_linked_list_node_prev_is_valid(&operation->node) && aws_linked_list_node_next_is_valid(&operation->node); } -static int s_add_operation_to_subscription_topic_filter_table( +static int s_add_streaming_operation_to_subscription_topic_filter_table( struct aws_mqtt_request_response_client *client, struct aws_mqtt_rr_client_operation *operation) { struct aws_byte_cursor topic_filter_cursor = s_aws_mqtt_rr_operation_get_subscription_topic_filter(operation); struct aws_hash_element *element = NULL; - if (aws_hash_table_find(&client->operation_lists_by_subscription_filter, &topic_filter_cursor, &element)) { + if (aws_hash_table_find(&client->streaming_operation_subscription_lists, &topic_filter_cursor, &element)) { return aws_raise_error(AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR); } struct aws_rr_operation_list_topic_filter_entry *entry = NULL; if (element == NULL) { entry = s_aws_rr_operation_list_topic_filter_entry_new(client->allocator, topic_filter_cursor); - aws_hash_table_put(&client->operation_lists_by_subscription_filter, &entry->topic_filter_cursor, entry, NULL); + aws_hash_table_put(&client->streaming_operation_subscription_lists, &entry->topic_filter_cursor, entry, NULL); AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client adding topic filter '" PRInSTR "' to subscriptions table", + "id=%p: request-response client adding topic filter '" PRInSTR "' to streaming subscriptions table", (void *)client, AWS_BYTE_CURSOR_PRI(topic_filter_cursor)); } else { @@ -1230,8 +1352,8 @@ static int s_add_operation_to_subscription_topic_filter_table( AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client adding operation %" PRIu64 " to subscription table with topic_filter '" PRInSTR - "'", + "id=%p: request-response client adding streaming operation %" PRIu64 + " to streaming subscription table with topic_filter '" PRInSTR "'", (void *)client, operation->id, AWS_BYTE_CURSOR_PRI(topic_filter_cursor)); @@ -1241,6 +1363,47 @@ static int s_add_operation_to_subscription_topic_filter_table( return AWS_OP_SUCCESS; } +static int s_add_request_operation_to_response_path_table( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_rr_client_operation *operation) { + + struct aws_array_list *paths = &operation->storage.request_storage.operation_response_paths; + size_t path_count = aws_array_list_length(paths); + for (size_t i = 0; i < path_count; ++i) { + struct aws_mqtt_request_operation_response_path path; + aws_array_list_get_at(paths, &path, i); + + struct aws_hash_element *element = NULL; + if (aws_hash_table_find(&client->request_response_paths, &path.topic, &element)) { + return aws_raise_error(AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR); + } + + if (element != NULL) { + struct aws_rr_response_path_entry *entry = element->value; + ++entry->ref_count; + continue; + } + + struct aws_rr_response_path_entry *entry = + s_aws_rr_response_path_entry_new(client->allocator, path.topic, path.correlation_token_json_path); + if (aws_hash_table_put(&client->request_response_paths, &entry->topic_cursor, entry, NULL)) { + return aws_raise_error(AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR); + } + } + + return AWS_OP_SUCCESS; +} + +static int s_add_in_progress_operation_to_tracking_tables( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_rr_client_operation *operation) { + if (operation->type == AWS_MRROT_STREAMING) { + return s_add_streaming_operation_to_subscription_topic_filter_table(client, operation); + } else { + return s_add_request_operation_to_response_path_table(client, operation); + } +} + static void s_handle_operation_subscribe_result( struct aws_mqtt_request_response_client *client, struct aws_mqtt_rr_client_operation *operation, @@ -1253,7 +1416,7 @@ static void s_handle_operation_subscribe_result( return; } - if (s_add_operation_to_subscription_topic_filter_table(client, operation)) { + if (s_add_in_progress_operation_to_tracking_tables(client, operation)) { s_request_response_fail_operation(operation, AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR); return; } @@ -1598,8 +1761,6 @@ static void s_mqtt_rr_client_submit_operation(struct aws_task *task, void *arg, // add appropriate client table entries aws_hash_table_put(&client->operations, &operation->id, operation, NULL); - // NYI other tables - // add to timeout priority queue if (operation->type == AWS_MRROT_REQUEST) { aws_priority_queue_push_ref( @@ -1636,6 +1797,49 @@ static void s_aws_mqtt_request_operation_storage_clean_up(struct aws_mqtt_reques aws_byte_buf_clean_up(&storage->operation_data); } +static void s_remove_request_operation_from_response_path_table(struct aws_mqtt_rr_client_operation *operation) { + if (operation->type != AWS_MRROT_REQUEST) { + return; + } + + struct aws_mqtt_request_response_client *client = operation->client_internal_ref; + struct aws_array_list *paths = &operation->storage.request_storage.operation_response_paths; + size_t path_count = aws_array_list_length(paths); + for (size_t i = 0; i < path_count; ++i) { + struct aws_mqtt_request_operation_response_path path; + aws_array_list_get_at(paths, &path, i); + + struct aws_hash_element *element = NULL; + if (aws_hash_table_find(&client->request_response_paths, &path.topic, &element) || element == NULL) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: internal state error removing reference to response path for topic " PRInSTR, + (void *)client, + AWS_BYTE_CURSOR_PRI(path.topic)); + continue; + } + + struct aws_rr_response_path_entry *entry = element->value; + --entry->ref_count; + + if (entry->ref_count == 0) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: removing last reference to response path for topic " PRInSTR, + (void *)client, + AWS_BYTE_CURSOR_PRI(path.topic)); + aws_hash_table_remove(&client->request_response_paths, &path.topic, NULL, NULL); + } else { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: removing reference to response path for topic " PRInSTR ", %zu references remain", + (void *)client, + AWS_BYTE_CURSOR_PRI(path.topic), + entry->ref_count); + } + } +} + static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, enum aws_task_status status) { (void)task; (void)status; @@ -1658,12 +1862,7 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, aws_rr_subscription_manager_release_subscription(&client->subscription_manager, &release_options); } - /* - NYI: - - Remove from correlation token table - - */ + s_remove_request_operation_from_response_path_table(operation); aws_mqtt_request_response_client_release_internal(operation->client_internal_ref); From e302be52927f9341e36b74f9c6acd7bf8cbfb5f8 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 1 Apr 2024 14:45:42 -0700 Subject: [PATCH 096/124] Checkpoint --- .../request_response_client.c | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index ac3eb7eb..0af3fcc2 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -1080,6 +1080,13 @@ static void s_apply_publish_to_streaming_operation_list( } } +static void s_apply_publish_to_response_path_entry( + struct aws_rr_response_path_entry *entry, + const struct aws_protocol_adapter_incoming_publish_event *publish_event) { + (void)entry; + (void)publish_event; +} + static void s_aws_rr_client_protocol_adapter_incoming_publish_callback( const struct aws_protocol_adapter_incoming_publish_event *publish_event, void *user_data) { @@ -1096,19 +1103,30 @@ static void s_aws_rr_client_protocol_adapter_incoming_publish_callback( struct aws_hash_element *subscription_filter_element = NULL; if (aws_hash_table_find( &rr_client->streaming_operation_subscription_lists, &publish_event->topic, &subscription_filter_element) == - AWS_OP_SUCCESS) { - if (subscription_filter_element != NULL) { - AWS_LOGF_DEBUG( - AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client incoming publish on topic '" PRInSTR "'", - (void *)rr_client, - AWS_BYTE_CURSOR_PRI(publish_event->topic)); + AWS_OP_SUCCESS && + subscription_filter_element != NULL) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on topic '" PRInSTR "' matches streaming topic", + (void *)rr_client, + AWS_BYTE_CURSOR_PRI(publish_event->topic)); - s_apply_publish_to_streaming_operation_list(subscription_filter_element->value, publish_event); - } + s_apply_publish_to_streaming_operation_list(subscription_filter_element->value, publish_event); } - /* Request-Response handling NYI */ + /* Request-Response handling */ + struct aws_hash_element *response_path_element = NULL; + if (aws_hash_table_find(&rr_client->request_response_paths, &publish_event->topic, &response_path_element) == + AWS_OP_SUCCESS && + response_path_element != NULL) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on topic '" PRInSTR "' matches response path", + (void *)rr_client, + AWS_BYTE_CURSOR_PRI(publish_event->topic)); + + s_apply_publish_to_response_path_entry(response_path_element->value, publish_event); + } } static void s_aws_rr_client_protocol_adapter_terminate_callback(void *user_data) { From 4e6c21d5786114c2824c03443bf025d37e0d0c83 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 2 Apr 2024 12:20:42 -0700 Subject: [PATCH 097/124] Relax validation on correlation token and path to support identity service; correlation token tracking; fix potential issue where we could remove operations from tracking tables when they hadn't been added --- .../request_response_client.c | 109 +++++++++++++----- tests/CMakeLists.txt | 2 - .../request_response_client_tests.c | 31 ----- 3 files changed, 78 insertions(+), 64 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 0af3fcc2..f66fe502 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -93,22 +93,17 @@ Client Tables/Lookups (Authoritative operation container) 1. &operation.id -> &operation // added on in-thread enqueue, removed on operation completion/destruction - (Response topic -> Correlation token extraction info) - 2. &topic -> &{topic, topic_buffer, correlation token json path buffer} // per-message-path add/replace on in-thread -enqueue, removed on client destruction + (Response path topic -> Correlation token extraction info) + 2. &topic -> &{topic, topic_buffer, correlation token json path buffer} // ref-counted, per-message-path add on + request dequeue into subscribing/subscribed state, decref/removed on operation completion/destruction - (Correlation token -> request operation) - 3. &operation.correlation token -> (request) &operation // added on in-thread request op move to awaiting response + (Request correlation token -> request operation) + 3. &operation.correlation token -> (request) &operation // added on request dequeue into subscribing/subscribed state, removed on operation completion/destruction - (Subscription filter -> all operations using that filter) - 4. &topic_filter -> &{topic_filter, linked_list} // added on in-thread pop from queue, removed from list on -operation completion/destruction also checked for empty and removed from table - - Note: 4 tracks both streaming and request-response operations but each uses the table in different ways. Both use - the table to react to subscription status events to move the operation forward state-wise. Additionally, - streaming operations use the table to map incoming publishes to listening streaming operations. OTOH, request - operations use table 2 and then 3 to map incoming publishes to operations. + (Streaming subscription filter -> list of all operations using that filter) + 4. &topic_filter -> &{topic_filter, linked_list} // added on request dequeue into subscribing/subscribed state, + removed from list on operation completion/destruction also checked for empty and removed from table */ @@ -446,6 +441,8 @@ struct aws_mqtt_rr_client_operation { enum aws_mqtt_request_response_operation_state state; + bool in_client_tables; + struct aws_task submit_task; struct aws_task destroy_task; }; @@ -539,6 +536,11 @@ struct aws_mqtt_request_response_client { * when the client is shutting down. */ struct aws_hash_table request_response_paths; + + /* + * Map from cursor (correlation token) -> request operation + */ + struct aws_hash_table operations_by_correlation_tokens; }; struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire_internal( @@ -583,6 +585,7 @@ static void s_mqtt_request_response_client_final_destroy(struct aws_mqtt_request aws_priority_queue_clean_up(&client->operations_by_timeout); aws_hash_table_clean_up(&client->streaming_operation_subscription_lists); aws_hash_table_clean_up(&client->request_response_paths); + aws_hash_table_clean_up(&client->operations_by_correlation_tokens); aws_mem_release(client->allocator, client); @@ -1241,6 +1244,15 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie NULL, s_aws_rr_response_path_table_hash_element_destroy); + aws_hash_table_init( + &rr_client->operations_by_correlation_tokens, + allocator, + MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE, + aws_hash_byte_cursor_ptr, + aws_mqtt_byte_cursor_hash_equality, + NULL, + NULL); + aws_linked_list_init(&rr_client->operation_queue); aws_task_init( @@ -1412,14 +1424,37 @@ static int s_add_request_operation_to_response_path_table( return AWS_OP_SUCCESS; } +static int s_add_request_operation_to_correlation_token_table( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_rr_client_operation *operation) { + + return aws_hash_table_put( + &client->operations_by_correlation_tokens, + &operation->storage.request_storage.options.correlation_token, + operation, + NULL); +} + static int s_add_in_progress_operation_to_tracking_tables( struct aws_mqtt_request_response_client *client, struct aws_mqtt_rr_client_operation *operation) { if (operation->type == AWS_MRROT_STREAMING) { - return s_add_streaming_operation_to_subscription_topic_filter_table(client, operation); + if (s_add_streaming_operation_to_subscription_topic_filter_table(client, operation)) { + return AWS_OP_ERR; + } } else { - return s_add_request_operation_to_response_path_table(client, operation); + if (s_add_request_operation_to_response_path_table(client, operation)) { + return AWS_OP_ERR; + } + + if (s_add_request_operation_to_correlation_token_table(client, operation)) { + return AWS_OP_ERR; + } } + + operation->in_client_tables = true; + + return AWS_OP_SUCCESS; } static void s_handle_operation_subscribe_result( @@ -1462,12 +1497,34 @@ static enum aws_rr_subscription_type s_rr_operation_type_to_subscription_type( return ARRST_EVENT_STREAM; } +static bool s_can_operation_dequeue( + struct aws_mqtt_request_response_client *client, + struct aws_mqtt_rr_client_operation *operation) { + if (operation->type != AWS_MRROT_REQUEST) { + return true; + } + + struct aws_hash_element *token_element = NULL; + if (aws_hash_table_find( + &client->operations_by_correlation_tokens, + &operation->storage.request_storage.options.correlation_token, + &token_element)) { + return false; + } + + return token_element == NULL; +} + static void s_process_queued_operations(struct aws_mqtt_request_response_client *client) { while (!aws_linked_list_empty(&client->operation_queue)) { struct aws_linked_list_node *head = aws_linked_list_front(&client->operation_queue); struct aws_mqtt_rr_client_operation *head_operation = AWS_CONTAINER_OF(head, struct aws_mqtt_rr_client_operation, node); + if (!s_can_operation_dequeue(client, head_operation)) { + break; + } + struct aws_rr_acquire_subscription_options subscribe_options = { .topic_filter = s_aws_mqtt_rr_operation_get_subscription_topic_filter(head_operation), .operation_id = head_operation->id, @@ -1692,20 +1749,6 @@ static bool s_are_request_operation_options_valid( AWS_BYTE_CURSOR_PRI(path->topic)); return false; } - - if (path->correlation_token_json_path.len == 0) { - AWS_LOGF_ERROR( - AWS_LS_MQTT_REQUEST_RESPONSE, - "(%p) rr client request options - empty correlation token json path", - (void *)client); - return false; - } - } - - if (request_options->correlation_token.len == 0) { - AWS_LOGF_ERROR( - AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - empty correlation token", (void *)client); - return false; } if (!aws_mqtt_is_valid_topic(&request_options->publish_topic)) { @@ -1815,11 +1858,15 @@ static void s_aws_mqtt_request_operation_storage_clean_up(struct aws_mqtt_reques aws_byte_buf_clean_up(&storage->operation_data); } -static void s_remove_request_operation_from_response_path_table(struct aws_mqtt_rr_client_operation *operation) { +static void s_remove_operation_from_client_tables(struct aws_mqtt_rr_client_operation *operation) { if (operation->type != AWS_MRROT_REQUEST) { return; } + if (!operation->in_client_tables) { + return; + } + struct aws_mqtt_request_response_client *client = operation->client_internal_ref; struct aws_array_list *paths = &operation->storage.request_storage.operation_response_paths; size_t path_count = aws_array_list_length(paths); @@ -1880,7 +1927,7 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, aws_rr_subscription_manager_release_subscription(&client->subscription_manager, &release_options); } - s_remove_request_operation_from_response_path_table(operation); + s_remove_operation_from_client_tables(operation); aws_mqtt_request_response_client_release_internal(operation->client_internal_ref); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c6b6e74e..8b4b7f53 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -527,8 +527,6 @@ add_test_case(rrc_mqtt311_create_destroy) add_test_case(rrc_submit_request_operation_failure_no_response_paths) add_test_case(rrc_submit_request_operation_failure_invalid_response_topic) -add_test_case(rrc_submit_request_operation_failure_invalid_response_correlation_token_path) -add_test_case(rrc_submit_request_operation_failure_no_correlation_token) add_test_case(rrc_submit_request_operation_failure_invalid_publish_topic) add_test_case(rrc_submit_request_operation_failure_invalid_subscription_topic_filter) add_test_case(rrc_submit_request_operation_failure_empty_request) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 1386a658..5f375e15 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -777,37 +777,6 @@ AWS_TEST_CASE( rrc_submit_request_operation_failure_invalid_response_topic, s_rrc_submit_request_operation_failure_invalid_response_topic_fn) -static void s_invalid_response_correlation_token_path_mutator( - struct aws_mqtt_request_operation_options *request_options) { - request_options->response_paths[0].correlation_token_json_path = aws_byte_cursor_from_c_str(""); -} - -static int s_rrc_submit_request_operation_failure_invalid_response_correlation_token_path_fn( - struct aws_allocator *allocator, - void *ctx) { - (void)ctx; - - return s_rrc_do_submit_request_operation_failure_test(allocator, s_invalid_response_correlation_token_path_mutator); -} - -AWS_TEST_CASE( - rrc_submit_request_operation_failure_invalid_response_correlation_token_path, - s_rrc_submit_request_operation_failure_invalid_response_correlation_token_path_fn) - -static void s_no_correlation_token_mutator(struct aws_mqtt_request_operation_options *request_options) { - request_options->correlation_token = aws_byte_cursor_from_c_str(""); -} - -static int s_rrc_submit_request_operation_failure_no_correlation_token_fn(struct aws_allocator *allocator, void *ctx) { - (void)ctx; - - return s_rrc_do_submit_request_operation_failure_test(allocator, s_no_correlation_token_mutator); -} - -AWS_TEST_CASE( - rrc_submit_request_operation_failure_no_correlation_token, - s_rrc_submit_request_operation_failure_no_correlation_token_fn) - static void s_invalid_publish_topic_mutator(struct aws_mqtt_request_operation_options *request_options) { request_options->publish_topic = aws_byte_cursor_from_c_str("a/b/#"); } From 33621bf09a248ea869203439cf21e724d5ad3c43 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 5 Apr 2024 11:43:06 -0700 Subject: [PATCH 098/124] Response correlation --- .../request_response_client.h | 7 +- .../request_response_client.c | 135 +++++++++++++++++- .../request_response_client_tests.c | 12 +- 3 files changed, 144 insertions(+), 10 deletions(-) diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 5cad26b7..18b01843 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -19,8 +19,11 @@ struct aws_mqtt_request_operation_response_path { struct aws_byte_cursor correlation_token_json_path; }; -typedef void( - aws_mqtt_request_operation_completion_fn)(struct aws_byte_cursor *payload, int error_code, void *user_data); +typedef void(aws_mqtt_request_operation_completion_fn)( + const struct aws_byte_cursor *response_topic, + const struct aws_byte_cursor *payload, + int error_code, + void *user_data); struct aws_mqtt_request_operation_options { struct aws_byte_cursor subscription_topic_filter; diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index f66fe502..209f9a39 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -656,7 +657,7 @@ static void s_complete_request_operation_with_failure(struct aws_mqtt_rr_client_ void *user_data = operation->storage.request_storage.options.user_data; if (completion_callback != NULL) { - (*completion_callback)(NULL, error_code, user_data); + (*completion_callback)(NULL, NULL, error_code, user_data); } s_change_operation_state(operation, AWS_MRROS_PENDING_DESTROY); @@ -1083,11 +1084,137 @@ static void s_apply_publish_to_streaming_operation_list( } } +static void s_complete_operation_with_correlation_token( + struct aws_mqtt_request_response_client *rr_client, + struct aws_byte_cursor correlation_token, + const struct aws_protocol_adapter_incoming_publish_event *publish_event) { + struct aws_hash_element *hash_element = NULL; + + if (aws_hash_table_find(&rr_client->operations_by_correlation_tokens, &correlation_token, &hash_element)) { + return; + } + + if (hash_element == NULL) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on response path topic '" PRInSTR + "' and correlation token '" PRInSTR "' does not have an originating request entry", + (void *)rr_client, + AWS_BYTE_CURSOR_PRI(publish_event->topic), + AWS_BYTE_CURSOR_PRI(correlation_token)); + return; + } + + struct aws_mqtt_rr_client_operation *operation = hash_element->value; + AWS_FATAL_ASSERT(operation->type == AWS_MRROT_REQUEST); + + if (operation->state == AWS_MRROS_PENDING_DESTROY) { + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response operation %" PRIu64 " cannot be completed, already in pending destruction state", + (void *)operation->client_internal_ref, + operation->id); + return; + } + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response operation %" PRIu64 " completed successfully", + (void *)operation->client_internal_ref, + operation->id); + + aws_mqtt_request_operation_completion_fn *completion_callback = + operation->storage.request_storage.options.completion_callback; + void *user_data = operation->storage.request_storage.options.user_data; + + if (completion_callback != NULL) { + (*completion_callback)(&publish_event->topic, &publish_event->payload, AWS_ERROR_SUCCESS, user_data); + } + + s_change_operation_state(operation, AWS_MRROS_PENDING_DESTROY); + + aws_mqtt_rr_client_operation_release(operation); +} + static void s_apply_publish_to_response_path_entry( + struct aws_mqtt_request_response_client *rr_client, struct aws_rr_response_path_entry *entry, const struct aws_protocol_adapter_incoming_publish_event *publish_event) { - (void)entry; - (void)publish_event; + + struct aws_json_value *json_payload = NULL; + + struct aws_byte_cursor correlation_token; + AWS_ZERO_STRUCT(correlation_token); + struct aws_byte_cursor correlation_token_json_path = aws_byte_cursor_from_buf(&entry->correlation_token_json_path); + if (correlation_token_json_path.len > 0) { + json_payload = aws_json_value_new_from_string(rr_client->allocator, publish_event->payload); + if (json_payload == NULL) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on response path topic '" PRInSTR + "' could not be deserialized into JSON", + (void *)rr_client, + AWS_BYTE_CURSOR_PRI(publish_event->topic)); + return; + } + + struct aws_byte_cursor segment; + AWS_ZERO_STRUCT(segment); + + struct aws_json_value *correlation_token_entry = json_payload; + while (aws_byte_cursor_next_split(&correlation_token_json_path, '.', &segment)) { + if (!aws_json_value_is_object(correlation_token_entry)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on response path topic '" PRInSTR + "' unable to walk correlation token path '" PRInSTR "'", + (void *)rr_client, + AWS_BYTE_CURSOR_PRI(publish_event->topic), + AWS_BYTE_CURSOR_PRI(correlation_token_json_path)); + goto done; + } + + correlation_token_entry = aws_json_value_get_from_object(correlation_token_entry, segment); + if (correlation_token_entry == NULL) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on response path topic '" PRInSTR + "' could not find path segment '" PRInSTR "'", + (void *)rr_client, + AWS_BYTE_CURSOR_PRI(publish_event->topic), + AWS_BYTE_CURSOR_PRI(segment)); + goto done; + } + } + + if (!aws_json_value_is_string(correlation_token_entry)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on response path topic '" PRInSTR + "' token entry is not a string", + (void *)rr_client, + AWS_BYTE_CURSOR_PRI(publish_event->topic)); + goto done; + } + + if (aws_json_value_get_string(correlation_token_entry, &correlation_token)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client incoming publish on response path topic '" PRInSTR + "' failed to extract string from token entry", + (void *)rr_client, + AWS_BYTE_CURSOR_PRI(publish_event->topic)); + goto done; + } + } + + s_complete_operation_with_correlation_token(rr_client, correlation_token, publish_event); + +done: + + if (json_payload != NULL) { + aws_json_value_destroy(json_payload); + } } static void s_aws_rr_client_protocol_adapter_incoming_publish_callback( @@ -1128,7 +1255,7 @@ static void s_aws_rr_client_protocol_adapter_incoming_publish_callback( (void *)rr_client, AWS_BYTE_CURSOR_PRI(publish_event->topic)); - s_apply_publish_to_response_path_entry(response_path_element->value, publish_event); + s_apply_publish_to_response_path_entry(rr_client, response_path_element->value, publish_event); } } diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 5f375e15..6d815f35 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -53,6 +53,7 @@ struct aws_rr_client_fixture_request_response_record { bool completed; int error_code; struct aws_byte_buf response; + struct aws_byte_buf response_topic; }; struct aws_rr_client_fixture_request_response_record *s_aws_rr_client_fixture_request_response_record_new( @@ -75,6 +76,7 @@ void s_aws_rr_client_fixture_request_response_record_delete( struct aws_rr_client_fixture_request_response_record *record) { aws_byte_buf_clean_up(&record->record_key); aws_byte_buf_clean_up(&record->response); + aws_byte_buf_clean_up(&record->response_topic); aws_mem_release(record->allocator, record); } @@ -86,7 +88,8 @@ static void s_aws_rr_client_fixture_request_response_record_hash_destroy(void *e } static void s_rrc_fixture_request_completion_callback( - struct aws_byte_cursor *payload, + const struct aws_byte_cursor *topic, + const struct aws_byte_cursor *payload, int error_code, void *user_data) { struct aws_rr_client_fixture_request_response_record *record = user_data; @@ -94,12 +97,13 @@ static void s_rrc_fixture_request_completion_callback( aws_mutex_lock(&fixture->lock); - if (payload != NULL) { - AWS_FATAL_ASSERT(error_code == AWS_ERROR_SUCCESS); + if (error_code == AWS_ERROR_SUCCESS) { + AWS_FATAL_ASSERT(topic != NULL && payload != NULL); aws_byte_buf_init_copy_from_cursor(&record->response, fixture->allocator, *payload); + aws_byte_buf_init_copy_from_cursor(&record->response_topic, fixture->allocator, *topic); } else { - AWS_FATAL_ASSERT(error_code != AWS_ERROR_SUCCESS); + AWS_FATAL_ASSERT(topic == NULL && payload == NULL); record->error_code = error_code; } From 945d91a07bb881231a4afed47987004de87f7a51 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sat, 6 Apr 2024 13:10:24 -0700 Subject: [PATCH 099/124] First request success test --- tests/CMakeLists.txt | 11 + .../request_response_client_tests.c | 199 +++++++++++++++++- 2 files changed, 206 insertions(+), 4 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8b4b7f53..21919971 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -548,6 +548,17 @@ add_test_case(rrc_streaming_operation_failure_exceeds_subscription_budget) add_test_case(rrc_streaming_operation_success_delayed_by_request_operations) add_test_case(rrc_streaming_operation_success_sandwiched_by_request_operations) +add_test_case(rrc_request_response_success_response_path1) +#add_test_case(rrc_request_response_success_response_path2) +#add_test_case(rrc_request_response_success_empty_correlation_token) +#add_test_case(rrc_request_response_success_empty_correlation_token_sequence) +#add_test_case(rrc_request_response_failure_puback_reason_code) +#add_test_case(rrc_request_response_failure_invalid_payload) +#add_test_case(rrc_request_response_failure_invalid_correlation_token_path) +#add_test_case(rrc_request_response_failure_invalid_correlation_token_type) +#add_test_case(rrc_request_response_failure_non_matching_correlation_token) +#add_test_case(rrc_request_response_multi_operation_sequence1) +#add_test_case(rrc_request_response_multi_operation_sequence2) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 6d815f35..1f850ce7 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -2167,7 +2168,197 @@ AWS_TEST_CASE( rrc_streaming_operation_success_sandwiched_by_request_operations, s_rrc_streaming_operation_success_sandwiched_by_request_operations_fn) -/* -#add_test_case() -#add_test_case(rrc_streaming_operation_success_sandwiched_by_request_operations) - */ \ No newline at end of file +int aws_mqtt5_mock_server_handle_publish_json_request( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data) { + (void)user_data; + + int result = AWS_OP_ERR; + struct aws_json_value *payload_json = NULL; + struct aws_json_value *response_json = NULL; + struct aws_allocator *allocator = connection->allocator; + + struct aws_mqtt5_packet_publish_view *publish_view = packet; + + /* send a PUBACK? */ + if (publish_view->qos == AWS_MQTT5_QOS_AT_LEAST_ONCE) { + struct aws_mqtt5_packet_puback_view puback_view = { + .packet_id = publish_view->packet_id, + .reason_code = AWS_MQTT5_PARC_SUCCESS, + }; + + if (aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_PUBACK, &puback_view)) { + return AWS_OP_ERR; + } + } + + /* unmarshal the payload as json */ + payload_json = aws_json_value_new_from_string(allocator, publish_view->payload); + if (payload_json == NULL) { + goto done; + } + + /* 'topic' field is where we should publish to */ + struct aws_json_value *topic_value = + aws_json_value_get_from_object(payload_json, aws_byte_cursor_from_c_str("topic")); + if (topic_value == NULL || !aws_json_value_is_string(topic_value)) { + goto done; + } + + struct aws_byte_cursor topic; + AWS_ZERO_STRUCT(topic); + if (aws_json_value_get_string(topic_value, &topic)) { + goto done; + } + + /* 'token' field is the correlation token we should reflect */ + struct aws_json_value *token_value = + aws_json_value_get_from_object(payload_json, aws_byte_cursor_from_c_str("token")); + if (token_value == NULL || !aws_json_value_is_string(token_value)) { + goto done; + } + + struct aws_byte_cursor token; + AWS_ZERO_STRUCT(token); + if (aws_json_value_get_string(token_value, &token)) { + goto done; + } + + /* build the json response blob */ + char response_buffer[512]; + snprintf( + response_buffer, AWS_ARRAY_SIZE(response_buffer), "{\"token\":\"" PRInSTR "\"}", AWS_BYTE_CURSOR_PRI(token)); + + /* build the response publish packet */ + struct aws_mqtt5_packet_publish_view response_publish_view = { + .qos = AWS_MQTT5_QOS_AT_MOST_ONCE, + .topic = topic, + .payload = aws_byte_cursor_from_c_str(response_buffer), + }; + + result = aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_PUBLISH, &response_publish_view); + +done: + + aws_json_value_destroy(payload_json); + aws_json_value_destroy(response_json); + + if (result == AWS_OP_ERR) { + aws_raise_error(AWS_ERROR_UNKNOWN); + } + + return result; +} + +static int s_init_fixture_request_operation_success( + struct aws_rr_client_test_fixture *fixture, + struct mqtt5_client_test_options *client_test_options, + struct aws_allocator *allocator, + modify_fixture_options_fn *config_modifier, + void *user_data) { + + aws_mqtt5_client_test_init_default_options(client_test_options); + + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + aws_mqtt5_server_send_suback_on_subscribe; + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = + aws_mqtt5_mock_server_handle_publish_json_request; + + struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { + .client_options = &client_test_options->client_options, + .server_function_table = &client_test_options->server_function_table, + .mock_server_user_data = user_data, + }; + + struct aws_mqtt_request_response_client_options rr_client_options = { + .max_subscriptions = 2, + .operation_timeout_seconds = 20000, + }; + + if (config_modifier != NULL) { + (*config_modifier)(&rr_client_options, client_test_options); + } + + ASSERT_SUCCESS(s_aws_rr_client_test_fixture_init_from_mqtt5( + fixture, allocator, &rr_client_options, &client_test_fixture_options, NULL)); + + return AWS_OP_SUCCESS; +} + +static int s_rrc_test_submit_test_request( + struct aws_rr_client_test_fixture *fixture, + const char *topic_prefix, + struct aws_byte_cursor record_key, + const char *response_topic, + const char *token) { + + char path1_buffer[128]; + snprintf(path1_buffer, AWS_ARRAY_SIZE(path1_buffer), "%s/accepted", topic_prefix); + char path2_buffer[128]; + snprintf(path2_buffer, AWS_ARRAY_SIZE(path2_buffer), "%s/rejected", topic_prefix); + + struct aws_mqtt_request_operation_response_path response_paths[] = { + { + .topic = aws_byte_cursor_from_c_str(path1_buffer), + .correlation_token_json_path = aws_byte_cursor_from_c_str("token"), + }, + { + .topic = aws_byte_cursor_from_c_str(path2_buffer), + .correlation_token_json_path = aws_byte_cursor_from_c_str("token"), + }, + }; + + char subscription_buffer[128]; + snprintf(subscription_buffer, AWS_ARRAY_SIZE(subscription_buffer), "%s/+", topic_prefix); + + char publish_topic_buffer[128]; + snprintf(publish_topic_buffer, AWS_ARRAY_SIZE(publish_topic_buffer), "%s/publish", topic_prefix); + + char request_buffer[512]; + snprintf( + request_buffer, AWS_ARRAY_SIZE(request_buffer), "{\"token\":\"%s\",\"topic\":\"%s\"}", token, response_topic); + + struct aws_mqtt_request_operation_options request = { + .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_buffer), + .response_paths = response_paths, + .response_path_count = AWS_ARRAY_SIZE(response_paths), + .publish_topic = aws_byte_cursor_from_c_str(publish_topic_buffer), + .serialized_request = aws_byte_cursor_from_c_str(request_buffer), + .correlation_token = aws_byte_cursor_from_c_str(token), + }; + + struct aws_rr_client_fixture_request_response_record *record = + s_rrc_fixture_add_request_record(fixture, record_key); + + request.completion_callback = s_rrc_fixture_request_completion_callback; + request.user_data = record; + + return aws_mqtt_request_response_client_submit_request(fixture->rr_client, &request); +} + +static int s_rrc_request_response_success_response_path1_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request(&fixture, "test", record_key, "test/accepted", "token1")); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + struct aws_byte_cursor expected_payload = aws_byte_cursor_from_c_str("{\"token\":\"token1\"}"); + ASSERT_SUCCESS(s_rrc_verify_request_completion(&fixture, record_key, AWS_ERROR_SUCCESS, &expected_payload)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_request_response_success_response_path1, s_rrc_request_response_success_response_path1_fn) From 135165bae6819576cf0fbc595004f8044f68a0bf Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 8 Apr 2024 16:27:37 -0700 Subject: [PATCH 100/124] Checkpoint --- .../request-response/subscription_manager.h | 5 + .../request_response_client.c | 248 ++-------- .../request-response/subscription_manager.c | 12 +- tests/CMakeLists.txt | 18 +- .../request_response_client_tests.c | 439 +++++++++++++++--- .../subscription_manager_tests.c | 9 +- 6 files changed, 437 insertions(+), 294 deletions(-) diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 8118d1a4..072c580c 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -194,6 +194,11 @@ AWS_MQTT_API void aws_rr_subscription_manager_init( */ AWS_MQTT_API void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager); +/* + * Requests the the subscription manager unsubscribe from all currently-unused subscriptions + */ +AWS_MQTT_API void aws_rr_subscription_manager_purge_unused(struct aws_rr_subscription_manager *manager); + /* * Signals to the subscription manager that the native request-response client is processing an operation that * needs a subscription to a particular topic. Return value indicates to the request-response client how it should diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 209f9a39..dc13b273 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -26,15 +26,26 @@ enum aws_mqtt_request_response_operation_type { }; enum aws_mqtt_request_response_operation_state { - AWS_MRROS_NONE, // creation -> in event loop enqueue - AWS_MRROS_QUEUED, // in event loop queue -> non blocked response from subscription manager - AWS_MRROS_PENDING_SUBSCRIPTION, // subscribing response from sub manager -> subscription success/failure event - AWS_MRROS_PENDING_RESPONSE, // (request only) subscription success -> (publish failure OR correlated response - // received) - AWS_MRROS_SUBSCRIBED, // (streaming only) subscription success -> (operation finished OR subscription ended event) - AWS_MRROS_TERMINAL, // (streaming only) (subscription failure OR subscription ended) -> operation close/terminate - AWS_MRROS_PENDING_DESTROY, // (request only) the operation's destroy task has been scheduled but not yet - // executed + /* creation -> in event loop enqueue */ + AWS_MRROS_NONE, + + /* in event loop queue -> non blocked response from subscription manager */ + AWS_MRROS_QUEUED, + + /* subscribing response from sub manager -> subscription success/failure event */ + AWS_MRROS_PENDING_SUBSCRIPTION, + + /* (request only) subscription success -> (publish failure OR correlated response received) */ + AWS_MRROS_PENDING_RESPONSE, + + /* (streaming only) subscription success -> (operation finished OR subscription ended event) */ + AWS_MRROS_SUBSCRIBED, + + /* (streaming only) (subscription failure OR subscription ended) -> operation close/terminate */ + AWS_MRROS_TERMINAL, + + /* (request only) the operation's destroy task has been scheduled but not yet executed */ + AWS_MRROS_PENDING_DESTROY, }; const char *s_aws_mqtt_request_response_operation_state_to_c_str(enum aws_mqtt_request_response_operation_state state) { @@ -98,7 +109,7 @@ Client Tables/Lookups 2. &topic -> &{topic, topic_buffer, correlation token json path buffer} // ref-counted, per-message-path add on request dequeue into subscribing/subscribed state, decref/removed on operation completion/destruction - (Request correlation token -> request operation) + (CorrelationToken -> request operation) 3. &operation.correlation token -> (request) &operation // added on request dequeue into subscribing/subscribed state, removed on operation completion/destruction @@ -191,214 +202,6 @@ static void s_aws_rr_response_path_table_hash_element_destroy(void *value) { s_aws_rr_response_path_entry_destroy(value); } -/* All operations have an internal ref to the client they are a part of */ - -/* - SubmitRequestOperation(options) [Anywhere]: - - Validate options - Allocate id - Create operation with ref count == 2 - Submit cross-thread task - - */ - -/* - CreateStreamingOperation(options) [Anywhere]: - - Validate options - Allocate id - Create operation with ref count == 2 - Submit cross-thread task - Return (ref-counted) operation - - */ - -/* - OperationSubmissionThreadTask(operation) [Event Loop, top-level task]: - - Add to client.operations table - (Request) Add message paths to client.paths table if no exist or different value - Add to client's timeout priority queue - Add operation to end of client.operation_queue list - operation.state <- QUEUED - WakeServiceTask - operation.decref (2 -> 1) - - */ - -/* - CompleteRequestOperation(operation, payload, error_code) [Event Loop]: - - if operation.state != PENDING_DESTROY - CompletionCallback(payload, error_code) - operation.state <- PENDING_DESTROY - operation.decref // schedules destroy task - */ - -/* - OnOperationZeroRefCount(operation) [Anywhere]: - - Schedule operation's destroy task on client event loop - */ - -/* - WakeServiceTask(client) [Event Loop]: - - If client.state == ACTIVE && client.connected - RescheduleServiceTask(now) - */ - -/* - OperationDestroyTask(operation) [Event Loop, top-level task]: - - Remove from client.operations - Remove from (client) intrusive list - Remove from client's timeout priority queue - if operation.type == REQUEST - Remove from client's correlation token table - Zero publish completion weak ref wrapper around operation - dec-ref weak-ref-operation-wrapper - Check client's topic filter table entry for empty list, remove entry if so. (intrusive list removal already unlinked it - from table) If client is not shutting down remove from subscription manager (otherwise it's already been cleaned up) - - client.subscription_manager.release_subscription(operation.topic_filter) - WakeServiceTask // queue may now be unblocked, does nothing if shutting down - (Streaming) Invoke termination callback - Release client internal ref - - */ - -/* - OnIncomingPublish(publish) [Event Loop]: - - if client.state != ACTIVE - // If shutting down, request operations are all in PENDING_DESTROY - // If initializing, publish cannot be relevant - return - - If publish.topic in client's topic filter table - for all streaming operations in list - if operation.state == SUBSCRIBED - invoke publish received callback - - If publish.topic in paths table: - If correlation token extraction success - If operation entry exists in correlation token table - CompleteRequestOperation(operation, payload) // Complete does nothing if the operation is being killed - */ - -/* - OnProtocolAdapterConnectionEvent(event) [Event Loop]: - - client.connected <- event.connected - client.subscription_manager.notify(event) - WakeServiceTask - */ - -/* - OnPublishCompletion(result, userdata) [Event Loop, Direct From Protocol Adapter, Operation as UserData]: - - weak-ref-operation-wrapper = userdata - if weak-ref-operation-wrapper can be resolved to an operation: - If result is error - CompleteRequestOperation(operation, error) - - dec-ref weak-ref-operation-wrapper - */ - -/* - MakeRequest(operation) [Event Loop]: - - operation.state <- SUBSCRIBED - if !client.connected - return - - // Critical Requirement - the user data for the publish completion callback must be a weak ref that wraps - // the operation. On operation destruction, we zero the weak ref (and dec ref it). - operation.state <- AWAITING_RESPONSE - if publish fails synchronously - CompleteRequestOperation(operation, error) - */ - -/* - RequestOperationOnSubscriptionStatusEvent(operation, event) [Event loop, top-level task loop]: - - If event.type == SUBSCRIBE_SUCCESS and operation.state == SUBSCRIBING - MakeRequest(operation) - - If event.type == {SUBSCRIBE_FAILURE, ENDED} - CompleteRequestOperation(failure) - */ - -/* - StreamingOperationOnSubscriptionStatusEvent(operation, event) [Event loop, top-level task loop]: - - If event.type == Success - Emit SubscriptionEstablished - Else If event.type == Lost - Emit SubscriptionLost - Else if event.type == Halted - operation.state <- TERMINAL - Emit SubscriptionHalted - - */ - -/* - HandleAcquireSubscriptionResult(operation, result) [Event Loop, Service Task Loop]: - - // invariant, BLOCKED is not possible, it was already handled - If result == {No Capacity, Failure} - If operation is streaming - Invoke failure callback - operation.state <- TERMINAL - else - CompleteRequestOperation(operation, error) - return - - // invariant, must be SUBSCRIBING or SUBSCRIBED at this point - Add operation to client's topic filter table - - If operation is streaming - Add operation to topic filter table - operation.state <- {SUBSCRIBING, SUBSCRIBED} - - If operation is request - if result == SUBSCRIBING - operation.state <- SUBSCRIBING - else // (SUBSCRIBED) - MakeRequest(op) - */ - -/* - Service task [Event Loop]: - - For all timed out operations: - OnOperationTimeout(operation) - - if client connected - - For all request operations where state == SUBSCRIBED - MakeRequest(operation) - - While OperationQueue is not empty: - operation = peek queue - result = subscription manager acquire sub(operation) - if result == Blocked - break - pop operation - HandleAcquireSubscriptionResult(operation, result) - - Reschedule Service for next timeout if it exists - */ - -/* - OnOperationTimeout(operation) [Event Loop, Service Task Loop, operation is request]: - - CompleteRequestOperation(operation, error) - - */ - struct aws_mqtt_rr_client_operation { struct aws_allocator *allocator; @@ -1643,6 +1446,8 @@ static bool s_can_operation_dequeue( } static void s_process_queued_operations(struct aws_mqtt_request_response_client *client) { + aws_rr_subscription_manager_purge_unused(&client->subscription_manager); + while (!aws_linked_list_empty(&client->operation_queue)) { struct aws_linked_list_node *head = aws_linked_list_front(&client->operation_queue); struct aws_mqtt_rr_client_operation *head_operation = @@ -1995,6 +1800,13 @@ static void s_remove_operation_from_client_tables(struct aws_mqtt_rr_client_oper } struct aws_mqtt_request_response_client *client = operation->client_internal_ref; + + aws_hash_table_remove( + &client->operations_by_correlation_tokens, + &operation->storage.request_storage.options.correlation_token, + NULL, + NULL); + struct aws_array_list *paths = &operation->storage.request_storage.operation_response_paths; size_t path_count = aws_array_list_length(paths); for (size_t i = 0; i < path_count; ++i) { diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index ecf3a6db..6a0d2d16 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -290,7 +290,7 @@ static void s_add_listener_to_subscription_record(struct aws_rr_subscription_rec aws_hash_table_get_entry_count(&record->listeners)); } -static int s_rr_subscription_cull_unused_subscriptions_wrapper(void *context, struct aws_hash_element *elem) { +static int s_rr_subscription_purge_unused_subscriptions_wrapper(void *context, struct aws_hash_element *elem) { struct aws_rr_subscription_record *record = elem->value; struct aws_rr_subscription_manager *manager = context; @@ -318,10 +318,10 @@ static int s_rr_subscription_cull_unused_subscriptions_wrapper(void *context, st return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; } -static void s_cull_unused_subscriptions(struct aws_rr_subscription_manager *manager) { +void aws_rr_subscription_manager_purge_unused(struct aws_rr_subscription_manager *manager) { AWS_LOGF_DEBUG( - AWS_LS_MQTT_REQUEST_RESPONSE, "request-response subscription manager - culling unused subscriptions"); - aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_cull_unused_subscriptions_wrapper, manager); + AWS_LS_MQTT_REQUEST_RESPONSE, "request-response subscription manager - purging unused subscriptions"); + aws_hash_table_foreach(&manager->subscriptions, s_rr_subscription_purge_unused_subscriptions_wrapper, manager); } static const char *s_rr_subscription_event_type_to_c_str(enum aws_rr_subscription_event_type type) { @@ -455,8 +455,6 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su // is no subscription present? if (existing_record == NULL) { - s_cull_unused_subscriptions(manager); - // is the budget used up? struct aws_subscription_stats stats; s_get_subscription_stats(manager, &stats); @@ -738,7 +736,7 @@ void aws_rr_subscription_manager_on_protocol_adapter_connection_event( s_apply_session_lost(manager); } - s_cull_unused_subscriptions(manager); + aws_rr_subscription_manager_purge_unused(manager); s_activate_idle_subscriptions(manager); } else { AWS_LOGF_DEBUG( diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 21919971..1a86b7f9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -548,15 +548,15 @@ add_test_case(rrc_streaming_operation_failure_exceeds_subscription_budget) add_test_case(rrc_streaming_operation_success_delayed_by_request_operations) add_test_case(rrc_streaming_operation_success_sandwiched_by_request_operations) -add_test_case(rrc_request_response_success_response_path1) -#add_test_case(rrc_request_response_success_response_path2) -#add_test_case(rrc_request_response_success_empty_correlation_token) -#add_test_case(rrc_request_response_success_empty_correlation_token_sequence) -#add_test_case(rrc_request_response_failure_puback_reason_code) -#add_test_case(rrc_request_response_failure_invalid_payload) -#add_test_case(rrc_request_response_failure_invalid_correlation_token_path) -#add_test_case(rrc_request_response_failure_invalid_correlation_token_type) -#add_test_case(rrc_request_response_failure_non_matching_correlation_token) +add_test_case(rrc_request_response_success_response_path_accepted) +add_test_case(rrc_request_response_success_response_path_rejected) +add_test_case(rrc_request_response_success_empty_correlation_token) +add_test_case(rrc_request_response_success_empty_correlation_token_sequence) +add_test_case(rrc_request_response_failure_puback_reason_code) +add_test_case(rrc_request_response_failure_invalid_payload) +add_test_case(rrc_request_response_failure_missing_correlation_token) +add_test_case(rrc_request_response_failure_invalid_correlation_token_type) +add_test_case(rrc_request_response_failure_non_matching_correlation_token) #add_test_case(rrc_request_response_multi_operation_sequence1) #add_test_case(rrc_request_response_multi_operation_sequence2) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 1f850ce7..3fcb054e 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -160,6 +160,7 @@ static int s_rrc_verify_request_completion( struct aws_rr_client_test_fixture *fixture, struct aws_byte_cursor record_key, int expected_error_code, + struct aws_byte_cursor *expected_response_topic, struct aws_byte_cursor *expected_response) { aws_mutex_lock(&fixture->lock); @@ -175,8 +176,12 @@ static int s_rrc_verify_request_completion( if (expected_response != NULL) { struct aws_byte_cursor actual_payload = aws_byte_cursor_from_buf(&record->response); ASSERT_TRUE(aws_byte_cursor_eq(expected_response, &actual_payload)); + + struct aws_byte_cursor actual_response_topic = aws_byte_cursor_from_buf(&record->response_topic); + ASSERT_TRUE(aws_byte_cursor_eq(expected_response_topic, &actual_response_topic)); } else { ASSERT_INT_EQUALS(0, record->response.len); + ASSERT_INT_EQUALS(0, record->response_topic.len); } aws_mutex_unlock(&fixture->lock); @@ -878,6 +883,7 @@ static int s_do_rrc_single_request_operation_test_fn( struct aws_mqtt_request_response_client_options *rr_client_options, struct aws_mqtt_request_operation_options *request_options, int expected_error_code, + struct aws_byte_cursor *expected_response_topic, struct aws_byte_cursor *expected_payload, bool shutdown_after_submit) { aws_mqtt_library_init(allocator); @@ -910,7 +916,7 @@ static int s_do_rrc_single_request_operation_test_fn( s_rrc_wait_on_request_completion(&fixture, request_options->serialized_request); ASSERT_SUCCESS(s_rrc_verify_request_completion( - &fixture, request_options->serialized_request, expected_error_code, expected_payload)); + &fixture, request_options->serialized_request, expected_error_code, expected_response_topic, expected_payload)); s_aws_rr_client_test_fixture_clean_up(&fixture); @@ -939,7 +945,7 @@ static int s_rrc_submit_request_operation_failure_by_shutdown_fn(struct aws_allo }; return s_do_rrc_single_request_operation_test_fn( - allocator, NULL, &request, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, NULL, true); + allocator, NULL, &request, AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, NULL, NULL, true); } AWS_TEST_CASE(rrc_submit_request_operation_failure_by_shutdown, s_rrc_submit_request_operation_failure_by_shutdown_fn) @@ -1055,7 +1061,7 @@ static int s_rrc_submit_request_operation_failure_by_timeout_fn(struct aws_alloc }; return s_do_rrc_single_request_operation_test_fn( - allocator, &rr_client_options, &request, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, false); + allocator, &rr_client_options, &request, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL, false); } AWS_TEST_CASE(rrc_submit_request_operation_failure_by_timeout, s_rrc_submit_request_operation_failure_by_timeout_fn) @@ -2040,10 +2046,10 @@ static int s_rrc_streaming_operation_success_delayed_by_request_operations_fn( s_rrc_wait_on_request_completion(&fixture, request_key1); ASSERT_SUCCESS( - s_rrc_verify_request_completion(&fixture, request_key1, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + s_rrc_verify_request_completion(&fixture, request_key1, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); s_rrc_wait_on_request_completion(&fixture, request_key2); ASSERT_SUCCESS( - s_rrc_verify_request_completion(&fixture, request_key2, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + s_rrc_verify_request_completion(&fixture, request_key2, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); @@ -2117,10 +2123,10 @@ static int s_rrc_streaming_operation_success_sandwiched_by_request_operations_fn s_rrc_wait_on_request_completion(&fixture, request_key1); ASSERT_SUCCESS( - s_rrc_verify_request_completion(&fixture, request_key1, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + s_rrc_verify_request_completion(&fixture, request_key1, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); s_rrc_wait_on_request_completion(&fixture, request_key2); ASSERT_SUCCESS( - s_rrc_verify_request_completion(&fixture, request_key2, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + s_rrc_verify_request_completion(&fixture, request_key2, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); s_rrc_wait_for_n_streaming_subscription_events(&fixture, record_key1, 1); @@ -2150,10 +2156,10 @@ static int s_rrc_streaming_operation_success_sandwiched_by_request_operations_fn s_rrc_wait_on_request_completion(&fixture, request_key3); ASSERT_SUCCESS( - s_rrc_verify_request_completion(&fixture, request_key3, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + s_rrc_verify_request_completion(&fixture, request_key3, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); s_rrc_wait_on_request_completion(&fixture, request_key4); ASSERT_SUCCESS( - s_rrc_verify_request_completion(&fixture, request_key4, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL)); + s_rrc_verify_request_completion(&fixture, request_key4, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); aws_mqtt_rr_client_operation_release(operation); @@ -2168,67 +2174,112 @@ AWS_TEST_CASE( rrc_streaming_operation_success_sandwiched_by_request_operations, s_rrc_streaming_operation_success_sandwiched_by_request_operations_fn) +enum rrc_publish_handler_directive_type { + RRC_PHDT_SUCCESS, + RRC_PHDT_FAILURE_PUBACK_REASON_CODE, + RRC_PHDT_FAILURE_BAD_PAYLOAD_FORMAT, + RRC_PHDT_FAILURE_MISSING_CORRELATION_TOKEN, + RRC_PHDT_FAILURE_BAD_CORRELATION_TOKEN_TYPE, + RRC_PHDT_FAILURE_MISMATCHED_CORRELATION_TOKEN, +}; + int aws_mqtt5_mock_server_handle_publish_json_request( void *packet, struct aws_mqtt5_server_mock_connection_context *connection, void *user_data) { (void)user_data; - int result = AWS_OP_ERR; struct aws_json_value *payload_json = NULL; struct aws_json_value *response_json = NULL; struct aws_allocator *allocator = connection->allocator; struct aws_mqtt5_packet_publish_view *publish_view = packet; - /* send a PUBACK? */ - if (publish_view->qos == AWS_MQTT5_QOS_AT_LEAST_ONCE) { - struct aws_mqtt5_packet_puback_view puback_view = { - .packet_id = publish_view->packet_id, - .reason_code = AWS_MQTT5_PARC_SUCCESS, - }; - - if (aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_PUBACK, &puback_view)) { - return AWS_OP_ERR; - } - } - /* unmarshal the payload as json */ payload_json = aws_json_value_new_from_string(allocator, publish_view->payload); - if (payload_json == NULL) { - goto done; - } + AWS_FATAL_ASSERT(payload_json != NULL); /* 'topic' field is where we should publish to */ struct aws_json_value *topic_value = aws_json_value_get_from_object(payload_json, aws_byte_cursor_from_c_str("topic")); - if (topic_value == NULL || !aws_json_value_is_string(topic_value)) { - goto done; - } + AWS_FATAL_ASSERT(topic_value != NULL && aws_json_value_is_string(topic_value)); struct aws_byte_cursor topic; AWS_ZERO_STRUCT(topic); - if (aws_json_value_get_string(topic_value, &topic)) { - goto done; - } + aws_json_value_get_string(topic_value, &topic); /* 'token' field is the correlation token we should reflect */ + struct aws_byte_cursor token; + AWS_ZERO_STRUCT(token); + struct aws_json_value *token_value = aws_json_value_get_from_object(payload_json, aws_byte_cursor_from_c_str("token")); - if (token_value == NULL || !aws_json_value_is_string(token_value)) { - goto done; + if (token_value != NULL) { + AWS_FATAL_ASSERT(aws_json_value_is_string(token_value)); + aws_json_value_get_string(token_value, &token); } - struct aws_byte_cursor token; - AWS_ZERO_STRUCT(token); - if (aws_json_value_get_string(token_value, &token)) { + /* 'directive' field indicates how the response handler should behave */ + struct aws_json_value *directive_value = + aws_json_value_get_from_object(payload_json, aws_byte_cursor_from_c_str("directive")); + AWS_FATAL_ASSERT(directive_value != NULL && aws_json_value_is_number(directive_value)); + + double raw_directive_value = 0; + aws_json_value_get_number(directive_value, &raw_directive_value); + enum rrc_publish_handler_directive_type directive = (int)raw_directive_value; + + /* send a PUBACK? */ + if (publish_view->qos == AWS_MQTT5_QOS_AT_LEAST_ONCE) { + struct aws_mqtt5_packet_puback_view puback_view = { + .packet_id = publish_view->packet_id, + .reason_code = (directive == RRC_PHDT_FAILURE_PUBACK_REASON_CODE) ? AWS_MQTT5_PARC_NOT_AUTHORIZED + : AWS_MQTT5_PARC_SUCCESS, + }; + + if (aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_PUBACK, &puback_view)) { + return AWS_OP_ERR; + } + } + + if (directive == RRC_PHDT_FAILURE_PUBACK_REASON_CODE) { goto done; } /* build the json response blob */ char response_buffer[512]; - snprintf( - response_buffer, AWS_ARRAY_SIZE(response_buffer), "{\"token\":\"" PRInSTR "\"}", AWS_BYTE_CURSOR_PRI(token)); + switch (directive) { + case RRC_PHDT_FAILURE_BAD_PAYLOAD_FORMAT: + snprintf( + response_buffer, + AWS_ARRAY_SIZE(response_buffer), + "" PRInSTR "", + AWS_BYTE_CURSOR_PRI(token)); + break; + case RRC_PHDT_FAILURE_MISSING_CORRELATION_TOKEN: + snprintf( + response_buffer, + AWS_ARRAY_SIZE(response_buffer), + "{\"wrongfield\":\"" PRInSTR "\"}", + AWS_BYTE_CURSOR_PRI(token)); + break; + case RRC_PHDT_FAILURE_BAD_CORRELATION_TOKEN_TYPE: + snprintf(response_buffer, AWS_ARRAY_SIZE(response_buffer), "{\"token\":5}"); + break; + case RRC_PHDT_FAILURE_MISMATCHED_CORRELATION_TOKEN: + snprintf(response_buffer, AWS_ARRAY_SIZE(response_buffer), "{\"token\":\"NotTheToken\"}"); + break; + default: + if (token.len > 0) { + snprintf( + response_buffer, + AWS_ARRAY_SIZE(response_buffer), + "{\"token\":\"" PRInSTR "\"}", + AWS_BYTE_CURSOR_PRI(token)); + } else { + snprintf(response_buffer, AWS_ARRAY_SIZE(response_buffer), "{}"); + } + break; + } /* build the response publish packet */ struct aws_mqtt5_packet_publish_view response_publish_view = { @@ -2237,18 +2288,14 @@ int aws_mqtt5_mock_server_handle_publish_json_request( .payload = aws_byte_cursor_from_c_str(response_buffer), }; - result = aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_PUBLISH, &response_publish_view); + aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_PUBLISH, &response_publish_view); done: aws_json_value_destroy(payload_json); aws_json_value_destroy(response_json); - if (result == AWS_OP_ERR) { - aws_raise_error(AWS_ERROR_UNKNOWN); - } - - return result; + return AWS_OP_SUCCESS; } static int s_init_fixture_request_operation_success( @@ -2264,6 +2311,8 @@ static int s_init_fixture_request_operation_success( aws_mqtt5_server_send_suback_on_subscribe; client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_PUBLISH] = aws_mqtt5_mock_server_handle_publish_json_request; + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_UNSUBSCRIBE] = + aws_mqtt5_mock_server_handle_unsubscribe_unsuback_success; struct aws_mqtt5_client_mqtt5_mock_test_fixture_options client_test_fixture_options = { .client_options = &client_test_options->client_options, @@ -2288,6 +2337,7 @@ static int s_init_fixture_request_operation_success( static int s_rrc_test_submit_test_request( struct aws_rr_client_test_fixture *fixture, + enum rrc_publish_handler_directive_type test_directive, const char *topic_prefix, struct aws_byte_cursor record_key, const char *response_topic, @@ -2298,14 +2348,19 @@ static int s_rrc_test_submit_test_request( char path2_buffer[128]; snprintf(path2_buffer, AWS_ARRAY_SIZE(path2_buffer), "%s/rejected", topic_prefix); + struct aws_byte_cursor token_path = aws_byte_cursor_from_c_str("token"); + if (token == NULL) { + AWS_ZERO_STRUCT(token_path); + } + struct aws_mqtt_request_operation_response_path response_paths[] = { { .topic = aws_byte_cursor_from_c_str(path1_buffer), - .correlation_token_json_path = aws_byte_cursor_from_c_str("token"), + .correlation_token_json_path = token_path, }, { .topic = aws_byte_cursor_from_c_str(path2_buffer), - .correlation_token_json_path = aws_byte_cursor_from_c_str("token"), + .correlation_token_json_path = token_path, }, }; @@ -2316,9 +2371,22 @@ static int s_rrc_test_submit_test_request( snprintf(publish_topic_buffer, AWS_ARRAY_SIZE(publish_topic_buffer), "%s/publish", topic_prefix); char request_buffer[512]; - snprintf( - request_buffer, AWS_ARRAY_SIZE(request_buffer), "{\"token\":\"%s\",\"topic\":\"%s\"}", token, response_topic); - + if (token != NULL) { + snprintf( + request_buffer, + AWS_ARRAY_SIZE(request_buffer), + "{\"token\":\"%s\",\"topic\":\"%s\",\"directive\":%d}", + token, + response_topic, + (int)test_directive); + } else { + snprintf( + request_buffer, + AWS_ARRAY_SIZE(request_buffer), + "{\"topic\":\"%s\",\"directive\":%d}", + response_topic, + (int)test_directive); + } struct aws_mqtt_request_operation_options request = { .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_buffer), .response_paths = response_paths, @@ -2337,7 +2405,7 @@ static int s_rrc_test_submit_test_request( return aws_mqtt_request_response_client_submit_request(fixture->rr_client, &request); } -static int s_rrc_request_response_success_response_path1_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrc_request_response_success_response_path_accepted_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_mqtt_library_init(allocator); @@ -2347,12 +2415,15 @@ static int s_rrc_request_response_success_response_path1_fn(struct aws_allocator ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); - ASSERT_SUCCESS(s_rrc_test_submit_test_request(&fixture, "test", record_key, "test/accepted", "token1")); + ASSERT_SUCCESS( + s_rrc_test_submit_test_request(&fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", "token1")); s_rrc_wait_on_request_completion(&fixture, record_key); + struct aws_byte_cursor expected_response_topic = aws_byte_cursor_from_c_str("test/accepted"); struct aws_byte_cursor expected_payload = aws_byte_cursor_from_c_str("{\"token\":\"token1\"}"); - ASSERT_SUCCESS(s_rrc_verify_request_completion(&fixture, record_key, AWS_ERROR_SUCCESS, &expected_payload)); + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, record_key, AWS_ERROR_SUCCESS, &expected_response_topic, &expected_payload)); s_aws_rr_client_test_fixture_clean_up(&fixture); @@ -2361,4 +2432,266 @@ static int s_rrc_request_response_success_response_path1_fn(struct aws_allocator return AWS_OP_SUCCESS; } -AWS_TEST_CASE(rrc_request_response_success_response_path1, s_rrc_request_response_success_response_path1_fn) +AWS_TEST_CASE( + rrc_request_response_success_response_path_accepted, + s_rrc_request_response_success_response_path_accepted_fn) + +static int s_rrc_request_response_success_response_path_rejected_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS( + s_rrc_test_submit_test_request(&fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/rejected", "token5")); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + struct aws_byte_cursor expected_response_topic = aws_byte_cursor_from_c_str("test/rejected"); + struct aws_byte_cursor expected_payload = aws_byte_cursor_from_c_str("{\"token\":\"token5\"}"); + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, record_key, AWS_ERROR_SUCCESS, &expected_response_topic, &expected_payload)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_request_response_success_response_path_rejected, + s_rrc_request_response_success_response_path_rejected_fn) + +static int s_rrc_request_response_failure_puback_reason_code_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_FAILURE_PUBACK_REASON_CODE, "test", record_key, "test/accepted", "token1")); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, record_key, AWS_ERROR_MQTT_REQUEST_RESPONSE_PUBLISH_FAILURE, NULL, NULL)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_request_response_failure_puback_reason_code, s_rrc_request_response_failure_puback_reason_code_fn) + +static int s_rrc_request_response_failure_invalid_payload_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_FAILURE_BAD_PAYLOAD_FORMAT, "test", record_key, "test/accepted", "token1")); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, record_key, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_request_response_failure_invalid_payload, s_rrc_request_response_failure_invalid_payload_fn) + +static int s_rrc_request_response_failure_missing_correlation_token_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_FAILURE_MISSING_CORRELATION_TOKEN, "test", record_key, "test/accepted", "token1")); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, record_key, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_request_response_failure_missing_correlation_token, + s_rrc_request_response_failure_missing_correlation_token_fn) + +static int s_rrc_request_response_failure_invalid_correlation_token_type_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_FAILURE_BAD_CORRELATION_TOKEN_TYPE, "test", record_key, "test/accepted", "token1")); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, record_key, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_request_response_failure_invalid_correlation_token_type, + s_rrc_request_response_failure_invalid_correlation_token_type_fn) + +static int s_rrc_request_response_failure_non_matching_correlation_token_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_FAILURE_MISMATCHED_CORRELATION_TOKEN, "test", record_key, "test/accepted", "token1")); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + ASSERT_SUCCESS( + s_rrc_verify_request_completion(&fixture, record_key, AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, NULL, NULL)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_request_response_failure_non_matching_correlation_token, + s_rrc_request_response_failure_non_matching_correlation_token_fn) + +static int s_rrc_request_response_success_empty_correlation_token_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS( + s_rrc_test_submit_test_request(&fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", NULL)); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + struct aws_byte_cursor expected_response_topic = aws_byte_cursor_from_c_str("test/accepted"); + struct aws_byte_cursor expected_payload = aws_byte_cursor_from_c_str("{}"); + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, record_key, AWS_ERROR_SUCCESS, &expected_response_topic, &expected_payload)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_request_response_success_empty_correlation_token, + s_rrc_request_response_success_empty_correlation_token_fn) + +static int s_rrc_request_response_success_empty_correlation_token_sequence_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + for (size_t i = 0; i < 2; ++i) { + char key_buffer[128]; + snprintf(key_buffer, AWS_ARRAY_SIZE(key_buffer), "testkey%zu", i); + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str(key_buffer); + + char prefix_buffer[128]; + snprintf(prefix_buffer, AWS_ARRAY_SIZE(prefix_buffer), "test%zu", i); + + char response_topic_buffer[128]; + snprintf(response_topic_buffer, AWS_ARRAY_SIZE(response_topic_buffer), "test%zu/accepted", i); + + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_SUCCESS, prefix_buffer, record_key, response_topic_buffer, NULL)); + } + + for (size_t i = 0; i < 2; ++i) { + char key_buffer[128]; + snprintf(key_buffer, AWS_ARRAY_SIZE(key_buffer), "testkey%zu", i); + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str(key_buffer); + + char response_topic_buffer[128]; + snprintf(response_topic_buffer, AWS_ARRAY_SIZE(response_topic_buffer), "test%zu/accepted", i); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + struct aws_byte_cursor expected_response_topic = aws_byte_cursor_from_c_str(response_topic_buffer); + struct aws_byte_cursor expected_payload = aws_byte_cursor_from_c_str("{}"); + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, record_key, AWS_ERROR_SUCCESS, &expected_response_topic, &expected_payload)); + } + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_request_response_success_empty_correlation_token_sequence, + s_rrc_request_response_success_empty_correlation_token_sequence_fn) diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 4b8f7929..3ab9a709 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -2131,13 +2131,8 @@ static int s_do_purge_test(struct aws_allocator *allocator, enum aws_rr_subscrip ASSERT_TRUE(s_contains_subscription_event_records(&fixture, 1, expected_empty_subscription_events)); ASSERT_FALSE(s_contains_subscription_event_records(&fixture, 1, expected_unsubscribe_events)); - // unsubscribe is lazy, so we need to trigger it by acquiring something else - struct aws_rr_acquire_subscription_options acquire3_options = { - .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), - .operation_id = 3, - }; - ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); + // purge + aws_rr_subscription_manager_purge_unused(manager); // now the unsubscribe should be present ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); From 37f4b57d90d0b3b620d6216ad2249dbd4b192cc6 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 9 Apr 2024 11:44:22 -0700 Subject: [PATCH 101/124] Final test updates --- tests/CMakeLists.txt | 3 +- .../request_response_client_tests.c | 251 +++++++++++++++--- .../subscription_manager_tests.c | 28 +- 3 files changed, 222 insertions(+), 60 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1a86b7f9..0dc0a3f8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -557,8 +557,7 @@ add_test_case(rrc_request_response_failure_invalid_payload) add_test_case(rrc_request_response_failure_missing_correlation_token) add_test_case(rrc_request_response_failure_invalid_correlation_token_type) add_test_case(rrc_request_response_failure_non_matching_correlation_token) -#add_test_case(rrc_request_response_multi_operation_sequence1) -#add_test_case(rrc_request_response_multi_operation_sequence2) +add_test_case(rrc_request_response_multi_operation_sequence) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 3fcb054e..c1fe00e8 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -2219,6 +2219,18 @@ int aws_mqtt5_mock_server_handle_publish_json_request( aws_json_value_get_string(token_value, &token); } + /* 'reflection' field is an optional field we should reflect. Used to ensure proper correlation on requests that + * don't use correlation tokens */ + struct aws_byte_cursor reflection; + AWS_ZERO_STRUCT(reflection); + + struct aws_json_value *reflection_value = + aws_json_value_get_from_object(payload_json, aws_byte_cursor_from_c_str("reflection")); + if (reflection_value != NULL) { + AWS_FATAL_ASSERT(aws_json_value_is_string(reflection_value)); + aws_json_value_get_string(reflection_value, &reflection); + } + /* 'directive' field indicates how the response handler should behave */ struct aws_json_value *directive_value = aws_json_value_get_from_object(payload_json, aws_byte_cursor_from_c_str("directive")); @@ -2268,17 +2280,29 @@ int aws_mqtt5_mock_server_handle_publish_json_request( case RRC_PHDT_FAILURE_MISMATCHED_CORRELATION_TOKEN: snprintf(response_buffer, AWS_ARRAY_SIZE(response_buffer), "{\"token\":\"NotTheToken\"}"); break; - default: + default: { + int bytes_used = snprintf(response_buffer, AWS_ARRAY_SIZE(response_buffer), "{"); if (token.len > 0) { - snprintf( - response_buffer, - AWS_ARRAY_SIZE(response_buffer), - "{\"token\":\"" PRInSTR "\"}", + bytes_used += snprintf( + response_buffer + bytes_used, + AWS_ARRAY_SIZE(response_buffer) - bytes_used, + "\"token\":\"" PRInSTR "\"", AWS_BYTE_CURSOR_PRI(token)); - } else { - snprintf(response_buffer, AWS_ARRAY_SIZE(response_buffer), "{}"); } + if (reflection.len > 0) { + if (token.len > 0) { + bytes_used += + snprintf(response_buffer + bytes_used, AWS_ARRAY_SIZE(response_buffer) - bytes_used, ","); + } + bytes_used += snprintf( + response_buffer + bytes_used, + AWS_ARRAY_SIZE(response_buffer) - bytes_used, + "\"reflection\":\"" PRInSTR "\"", + AWS_BYTE_CURSOR_PRI(reflection)); + } + snprintf(response_buffer + bytes_used, AWS_ARRAY_SIZE(response_buffer) - bytes_used, "}"); break; + } } /* build the response publish packet */ @@ -2322,7 +2346,7 @@ static int s_init_fixture_request_operation_success( struct aws_mqtt_request_response_client_options rr_client_options = { .max_subscriptions = 2, - .operation_timeout_seconds = 20000, + .operation_timeout_seconds = 2, }; if (config_modifier != NULL) { @@ -2341,7 +2365,8 @@ static int s_rrc_test_submit_test_request( const char *topic_prefix, struct aws_byte_cursor record_key, const char *response_topic, - const char *token) { + const char *token, + const char *reflection) { char path1_buffer[128]; snprintf(path1_buffer, AWS_ARRAY_SIZE(path1_buffer), "%s/accepted", topic_prefix); @@ -2371,22 +2396,28 @@ static int s_rrc_test_submit_test_request( snprintf(publish_topic_buffer, AWS_ARRAY_SIZE(publish_topic_buffer), "%s/publish", topic_prefix); char request_buffer[512]; + int used_bytes = snprintf( + request_buffer, + AWS_ARRAY_SIZE(request_buffer), + "{\"topic\":\"%s\",\"directive\":%d", + response_topic, + (int)test_directive); + if (token != NULL) { - snprintf( - request_buffer, - AWS_ARRAY_SIZE(request_buffer), - "{\"token\":\"%s\",\"topic\":\"%s\",\"directive\":%d}", - token, - response_topic, - (int)test_directive); - } else { - snprintf( - request_buffer, - AWS_ARRAY_SIZE(request_buffer), - "{\"topic\":\"%s\",\"directive\":%d}", - response_topic, - (int)test_directive); + used_bytes += snprintf( + request_buffer + used_bytes, AWS_ARRAY_SIZE(request_buffer) - used_bytes, ",\"token\":\"%s\"", token); + } + + if (reflection != NULL) { + used_bytes += snprintf( + request_buffer + used_bytes, + AWS_ARRAY_SIZE(request_buffer) - used_bytes, + ",\"reflection\":\"%s\"", + reflection); } + + snprintf(request_buffer + used_bytes, AWS_ARRAY_SIZE(request_buffer) - used_bytes, "}"); + struct aws_mqtt_request_operation_options request = { .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_buffer), .response_paths = response_paths, @@ -2415,8 +2446,8 @@ static int s_rrc_request_response_success_response_path_accepted_fn(struct aws_a ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); - ASSERT_SUCCESS( - s_rrc_test_submit_test_request(&fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", "token1")); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", "token1", NULL)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2446,8 +2477,8 @@ static int s_rrc_request_response_success_response_path_rejected_fn(struct aws_a ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); - ASSERT_SUCCESS( - s_rrc_test_submit_test_request(&fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/rejected", "token5")); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/rejected", "token5", NULL)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2478,7 +2509,7 @@ static int s_rrc_request_response_failure_puback_reason_code_fn(struct aws_alloc struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_PUBACK_REASON_CODE, "test", record_key, "test/accepted", "token1")); + &fixture, RRC_PHDT_FAILURE_PUBACK_REASON_CODE, "test", record_key, "test/accepted", "token1", NULL)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2505,7 +2536,7 @@ static int s_rrc_request_response_failure_invalid_payload_fn(struct aws_allocato struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_BAD_PAYLOAD_FORMAT, "test", record_key, "test/accepted", "token1")); + &fixture, RRC_PHDT_FAILURE_BAD_PAYLOAD_FORMAT, "test", record_key, "test/accepted", "token1", NULL)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2532,7 +2563,7 @@ static int s_rrc_request_response_failure_missing_correlation_token_fn(struct aw struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_MISSING_CORRELATION_TOKEN, "test", record_key, "test/accepted", "token1")); + &fixture, RRC_PHDT_FAILURE_MISSING_CORRELATION_TOKEN, "test", record_key, "test/accepted", "token1", NULL)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2563,7 +2594,7 @@ static int s_rrc_request_response_failure_invalid_correlation_token_type_fn( struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_BAD_CORRELATION_TOKEN_TYPE, "test", record_key, "test/accepted", "token1")); + &fixture, RRC_PHDT_FAILURE_BAD_CORRELATION_TOKEN_TYPE, "test", record_key, "test/accepted", "token1", NULL)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2594,7 +2625,7 @@ static int s_rrc_request_response_failure_non_matching_correlation_token_fn( struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_MISMATCHED_CORRELATION_TOKEN, "test", record_key, "test/accepted", "token1")); + &fixture, RRC_PHDT_FAILURE_MISMATCHED_CORRELATION_TOKEN, "test", record_key, "test/accepted", "token1", NULL)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2623,7 +2654,7 @@ static int s_rrc_request_response_success_empty_correlation_token_fn(struct aws_ struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS( - s_rrc_test_submit_test_request(&fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", NULL)); + s_rrc_test_submit_test_request(&fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", NULL, NULL)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2654,7 +2685,7 @@ static int s_rrc_request_response_success_empty_correlation_token_sequence_fn( struct aws_rr_client_test_fixture fixture; ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); - for (size_t i = 0; i < 2; ++i) { + for (size_t i = 0; i < 20; ++i) { char key_buffer[128]; snprintf(key_buffer, AWS_ARRAY_SIZE(key_buffer), "testkey%zu", i); struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str(key_buffer); @@ -2666,10 +2697,10 @@ static int s_rrc_request_response_success_empty_correlation_token_sequence_fn( snprintf(response_topic_buffer, AWS_ARRAY_SIZE(response_topic_buffer), "test%zu/accepted", i); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_SUCCESS, prefix_buffer, record_key, response_topic_buffer, NULL)); + &fixture, RRC_PHDT_SUCCESS, prefix_buffer, record_key, response_topic_buffer, NULL, NULL)); } - for (size_t i = 0; i < 2; ++i) { + for (size_t i = 0; i < 20; ++i) { char key_buffer[128]; snprintf(key_buffer, AWS_ARRAY_SIZE(key_buffer), "testkey%zu", i); struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str(key_buffer); @@ -2695,3 +2726,151 @@ static int s_rrc_request_response_success_empty_correlation_token_sequence_fn( AWS_TEST_CASE( rrc_request_response_success_empty_correlation_token_sequence, s_rrc_request_response_success_empty_correlation_token_sequence_fn) + +struct rrc_multi_test_operation { + const char *prefix; + const char *token; + const char *reflection; +}; + +static int s_do_rrc_operation_sequence_test( + struct aws_rr_client_test_fixture *fixture, + size_t operation_count, + struct rrc_multi_test_operation *operations) { + for (size_t i = 0; i < operation_count; ++i) { + + struct rrc_multi_test_operation *operation = &operations[i]; + + char key_buffer[128]; + snprintf(key_buffer, AWS_ARRAY_SIZE(key_buffer), "testkey%zu", i); + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str(key_buffer); + + char response_topic_buffer[128]; + snprintf(response_topic_buffer, AWS_ARRAY_SIZE(response_topic_buffer), "%s/accepted", operation->prefix); + + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + fixture, + RRC_PHDT_SUCCESS, + operation->prefix, + record_key, + response_topic_buffer, + operation->token, + operation->reflection)); + } + + for (size_t i = 0; i < operation_count; ++i) { + + struct rrc_multi_test_operation *operation = &operations[i]; + + char key_buffer[128]; + snprintf(key_buffer, AWS_ARRAY_SIZE(key_buffer), "testkey%zu", i); + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str(key_buffer); + + char response_topic_buffer[128]; + snprintf(response_topic_buffer, AWS_ARRAY_SIZE(response_topic_buffer), "%s/accepted", operation->prefix); + + s_rrc_wait_on_request_completion(fixture, record_key); + + struct aws_byte_cursor expected_response_topic = aws_byte_cursor_from_c_str(response_topic_buffer); + struct aws_byte_cursor expected_payload; + AWS_ZERO_STRUCT(expected_payload); + char payload_buffer[256]; + int bytes_used = snprintf(payload_buffer, AWS_ARRAY_SIZE(payload_buffer), "{"); + if (operation->token != NULL) { + bytes_used += snprintf( + payload_buffer + bytes_used, + AWS_ARRAY_SIZE(payload_buffer) - bytes_used, + "\"token\":\"%s\"", + operation->token); + if (operation->reflection != NULL) { + bytes_used += snprintf(payload_buffer + bytes_used, AWS_ARRAY_SIZE(payload_buffer) - bytes_used, ","); + } + } + if (operation->reflection != NULL) { + bytes_used += snprintf( + payload_buffer + bytes_used, + AWS_ARRAY_SIZE(payload_buffer) - bytes_used, + "\"reflection\":\"%s\"", + operation->reflection); + } + snprintf(payload_buffer + bytes_used, AWS_ARRAY_SIZE(payload_buffer) - bytes_used, "}"); + + expected_payload = aws_byte_cursor_from_c_str(payload_buffer); + ASSERT_SUCCESS(s_rrc_verify_request_completion( + fixture, record_key, AWS_ERROR_SUCCESS, &expected_response_topic, &expected_payload)); + } + + return AWS_OP_SUCCESS; +} + +static int s_rrc_request_response_multi_operation_sequence_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct rrc_multi_test_operation request_sequence[] = { + { + .prefix = "test", + .token = "token1", + }, + { + .prefix = "test", + .token = "token2", + }, + { + .prefix = "test2", + .token = "token3", + }, + { + .prefix = "whatthe", + .token = "hey", + .reflection = "something", + }, + { + .prefix = "test", + .token = "token4", + }, + { + .prefix = "test2", + .token = "token5", + }, + { + .prefix = "provision", + .reflection = "provision1", + }, + { + .prefix = "provision", + .reflection = "provision2", + }, + { + .prefix = "create-keys-and-cert", + .reflection = "create-keys1", + }, + { + .prefix = "test", + .token = "token6", + }, + { + .prefix = "test2", + .token = "token7", + }, + { + .prefix = "provision", + .reflection = "provision3", + }, + }; + + ASSERT_SUCCESS(s_do_rrc_operation_sequence_test(&fixture, AWS_ARRAY_SIZE(request_sequence), request_sequence)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_request_response_multi_operation_sequence, s_rrc_request_response_multi_operation_sequence_fn) diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 3ab9a709..4c23d7c0 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -942,13 +942,7 @@ static int s_rrsm_release_unsubscribes_request_fn(struct aws_allocator *allocato aws_rr_subscription_manager_release_subscription(manager, &release2_options); ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); - // unsubscribe is lazy, so we need to trigger it by acquiring something else - struct aws_rr_acquire_subscription_options acquire3_options = { - .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), - .operation_id = 3, - }; - ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); + aws_rr_subscription_manager_purge_unused(manager); // now the unsubscribe should be present ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); @@ -1034,13 +1028,7 @@ static int s_rrsm_release_unsubscribes_streaming_fn(struct aws_allocator *alloca aws_rr_subscription_manager_release_subscription(manager, &release2_options); ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); - // unsubscribe is lazy, so we need to trigger it by acquiring something else - struct aws_rr_acquire_subscription_options acquire3_options = { - .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), - .operation_id = 3, - }; - ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); + aws_rr_subscription_manager_purge_unused(manager); // now the unsubscribe should be present ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); @@ -1101,7 +1089,7 @@ static int s_rrsm_do_unsubscribe_test(struct aws_allocator *allocator, bool shou aws_rr_subscription_manager_release_subscription(manager, &release1_options); // unsubscribe should be visible, but we're still blocked because it hasn't completed - ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + aws_rr_subscription_manager_purge_unused(manager); struct aws_protocol_adapter_api_record expected_unsubscribe = { .type = PAAT_UNSUBSCRIBE, .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), @@ -1117,6 +1105,8 @@ static int s_rrsm_do_unsubscribe_test(struct aws_allocator *allocator, bool shou }; aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_unsubscribe_event); + aws_rr_subscription_manager_purge_unused(manager); + // a successful unsubscribe should clear space, a failed one should not ASSERT_INT_EQUALS( (should_succeed ? AASRT_SUBSCRIBING : AASRT_BLOCKED), @@ -1893,13 +1883,7 @@ static int s_rrsm_do_no_session_subscription_ended_test( ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); // trigger an event that would cull unused subscriptions, causing an unsubscribe - struct aws_rr_acquire_subscription_options acquire3_options = { - .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world3"), - .operation_id = 2, - }; - ASSERT_INT_EQUALS( - AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); + aws_rr_subscription_manager_purge_unused(manager); // now the unsubscribe should be present ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); From c0263eb9e11157b6b44fe391edc3133eea4f1647 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 10 Apr 2024 18:14:06 -0700 Subject: [PATCH 102/124] Checkpoint --- CMakeLists.txt | 1 + bin/elastishadow/CMakeLists.txt | 29 ++ bin/elastishadow/main.c | 659 ++++++++++++++++++++++++++++++++ 3 files changed, 689 insertions(+) create mode 100644 bin/elastishadow/CMakeLists.txt create mode 100644 bin/elastishadow/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a2eeefa..4a3348ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,7 @@ if (BUILD_TESTING) if (NOT CMAKE_CROSSCOMPILING ) add_subdirectory(bin/elastipubsub) add_subdirectory(bin/elastipubsub5) + add_subdirectory(bin/elastishadow) add_subdirectory(bin/mqtt5canary) endif() endif () diff --git a/bin/elastishadow/CMakeLists.txt b/bin/elastishadow/CMakeLists.txt new file mode 100644 index 00000000..c86e47c3 --- /dev/null +++ b/bin/elastishadow/CMakeLists.txt @@ -0,0 +1,29 @@ +project(elastishadow C) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/cmake") + +file(GLOB ELASTISHADOW_SRC + "*.c" + ) + +set(ELASTISHADOW_PROJECT_NAME elastishadow) +add_executable(${ELASTISHADOW_PROJECT_NAME} ${ELASTISHADOW_SRC}) +aws_set_common_properties(${ELASTISHADOW_PROJECT_NAME}) + + +target_include_directories(${ELASTISHADOW_PROJECT_NAME} PUBLIC + $ + $) + +target_link_libraries(${ELASTISHADOW_PROJECT_NAME} PRIVATE aws-c-mqtt) + +if (BUILD_SHARED_LIBS AND NOT WIN32) + message(INFO " elastishadow will be built with shared libs, but you may need to set LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib to run the application") +endif() + +install(TARGETS ${ELASTISHADOW_PROJECT_NAME} + EXPORT ${ELASTISHADOW_PROJECT_NAME}-targets + COMPONENT Runtime + RUNTIME + DESTINATION bin + COMPONENT Runtime) diff --git a/bin/elastishadow/main.c b/bin/elastishadow/main.c new file mode 100644 index 00000000..b87f0306 --- /dev/null +++ b/bin/elastishadow/main.c @@ -0,0 +1,659 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifdef _MSC_VER +# pragma warning(disable : 4996) /* Disable warnings about fopen() being insecure */ +# pragma warning(disable : 4204) /* Declared initializers */ +# pragma warning(disable : 4221) /* Local var in declared initializer */ +#endif + +#ifdef WIN32 +// Windows does not need specific imports +#else +# include +#endif + +struct app_ctx { + struct aws_allocator *allocator; + struct aws_mutex lock; + struct aws_condition_variable signal; + struct aws_uri uri; + uint32_t port; + const char *cert; + const char *key; + + struct aws_tls_connection_options tls_connection_options; + + const char *log_filename; + enum aws_log_level log_level; + + struct aws_mqtt5_client *client; + struct aws_mqtt_request_response_client *rr_client; +}; + +static void s_usage(int exit_code) { + + fprintf(stderr, "usage: elastishadow [options] endpoint\n"); + fprintf(stderr, " endpoint: url to connect to\n"); + fprintf(stderr, "\n Options:\n\n"); + fprintf(stderr, " --cert FILE: path to a PEM encoded certificate to use with mTLS\n"); + fprintf(stderr, " --key FILE: Path to a PEM encoded private key that matches cert.\n"); + fprintf(stderr, " -l, --log FILE: dumps logs to FILE instead of stderr.\n"); + fprintf(stderr, " -v, --verbose: ERROR|INFO|DEBUG|TRACE: log level to configure. Default is none.\n"); + fprintf(stderr, " -h, --help\n"); + fprintf(stderr, " Display this message and quit.\n"); + exit(exit_code); +} + +static struct aws_cli_option s_long_options[] = { + {"cert", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'c'}, + {"key", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'e'}, + {"log", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'l'}, + {"verbose", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'v'}, + {"help", AWS_CLI_OPTIONS_NO_ARGUMENT, NULL, 'h'}, + /* Per getopt(3) the last element of the array has to be filled with all zeros */ + {NULL, AWS_CLI_OPTIONS_NO_ARGUMENT, NULL, 0}, +}; + +static void s_parse_options(int argc, char **argv, struct app_ctx *ctx) { + bool uri_found = false; + + while (true) { + int option_index = 0; + int c = aws_cli_getopt_long(argc, argv, "c:e:l:v:h", s_long_options, &option_index); + if (c == -1) { + break; + } + + switch (c) { + case 0: + /* getopt_long() returns 0 if an option.flag is non-null */ + break; + case 'c': + ctx->cert = aws_cli_optarg; + break; + case 'e': + ctx->key = aws_cli_optarg; + break; + case 'l': + ctx->log_filename = aws_cli_optarg; + break; + case 'v': + if (!strcmp(aws_cli_optarg, "TRACE")) { + ctx->log_level = AWS_LL_TRACE; + } else if (!strcmp(aws_cli_optarg, "INFO")) { + ctx->log_level = AWS_LL_INFO; + } else if (!strcmp(aws_cli_optarg, "DEBUG")) { + ctx->log_level = AWS_LL_DEBUG; + } else if (!strcmp(aws_cli_optarg, "ERROR")) { + ctx->log_level = AWS_LL_ERROR; + } else { + fprintf(stderr, "unsupported log level %s.\n", aws_cli_optarg); + s_usage(1); + } + break; + case 'h': + s_usage(0); + break; + case 0x02: { + struct aws_byte_cursor uri_cursor = aws_byte_cursor_from_c_str(aws_cli_positional_arg); + if (aws_uri_init_parse(&ctx->uri, ctx->allocator, &uri_cursor)) { + fprintf( + stderr, + "Failed to parse uri %s with error %s\n", + (char *)uri_cursor.ptr, + aws_error_debug_str(aws_last_error())); + s_usage(1); + } + uri_found = true; + break; + } + + default: + fprintf(stderr, "Unknown option\n"); + s_usage(1); + } + } + + if (!uri_found) { + fprintf(stderr, "A URI for the request must be supplied.\n"); + s_usage(1); + } +} + +static bool s_skip_whitespace(uint8_t value) { + return value == '\n' || value == '\r' || value == '\t' || value == ' '; +} + +static void s_split_command_line(struct aws_byte_cursor cursor, struct aws_array_list *words) { + struct aws_byte_cursor split_cursor; + AWS_ZERO_STRUCT(split_cursor); + + while (aws_byte_cursor_next_split(&cursor, ' ', &split_cursor)) { + struct aws_byte_cursor word_cursor = aws_byte_cursor_trim_pred(&split_cursor, &s_skip_whitespace); + if (word_cursor.len > 0) { + aws_array_list_push_back(words, &word_cursor); + } + } +} + +static void s_on_get_shadow_complete( + const struct aws_byte_cursor *response_topic, + const struct aws_byte_cursor *payload, + int error_code, + void *user_data) { + + struct aws_string *correlation_token = user_data; + + if (payload != NULL) { + printf("GetNamedShadow request '%s' response received on topic '" PRInSTR "' with body:\n " PRInSTR "\n", correlation_token->bytes, AWS_BYTE_CURSOR_PRI(*response_topic), AWS_BYTE_CURSOR_PRI(*payload)); + } else { + printf("GetNamedShadow request '%s' failed with error code %d(%s)\n", correlation_token->bytes, error_code, aws_error_debug_str(error_code)); + } + + aws_string_destroy(correlation_token); +} + +static void s_handle_get( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments) { + + size_t argument_count = aws_array_list_length(arguments) - 1; + if (argument_count != 2) { + printf("invalid get options:\n"); + printf(" get \n"); + return; + } + + struct aws_byte_cursor thing_name_cursor; + AWS_ZERO_STRUCT(thing_name_cursor); + aws_array_list_get_at(arguments, &thing_name_cursor, 1); + + struct aws_byte_cursor shadow_name_cursor; + AWS_ZERO_STRUCT(shadow_name_cursor); + aws_array_list_get_at(arguments, &shadow_name_cursor, 2); + + char subscription_topic_filter[128]; + snprintf(subscription_topic_filter, AWS_ARRAY_SIZE(subscription_topic_filter), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/+", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + char accepted_path[128]; + snprintf(accepted_path, AWS_ARRAY_SIZE(accepted_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/accepted", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + char rejected_path[128]; + snprintf(rejected_path, AWS_ARRAY_SIZE(rejected_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/rejected", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str(""); + struct aws_mqtt_request_operation_response_path response_paths[] = { + { + .topic = aws_byte_cursor_from_c_str(accepted_path), + .correlation_token_json_path = correlation_token_path, + }, + { + .topic = aws_byte_cursor_from_c_str(rejected_path), + .correlation_token_json_path = correlation_token_path, + }, + }; + + char publish_topic[128]; + snprintf(publish_topic, AWS_ARRAY_SIZE(publish_topic), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + char correlation_token[128]; + struct aws_byte_buf correlation_token_buf = aws_byte_buf_from_empty_array(correlation_token, AWS_ARRAY_SIZE(correlation_token)); + + struct aws_uuid uuid; + aws_uuid_init(&uuid); + aws_uuid_to_str(&uuid, &correlation_token_buf); + + char request[256]; + snprintf(request, AWS_ARRAY_SIZE(request), "{\"clientToken\":\"%s\"}", correlation_token); + + struct aws_mqtt_request_operation_options get_options = { + .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .response_paths = response_paths, + .response_path_count = 2, + .publish_topic = aws_byte_cursor_from_c_str(publish_topic), + .serialized_request = aws_byte_cursor_from_c_str(request), + .correlation_token = aws_byte_cursor_from_c_str(correlation_token), + .completion_callback = s_on_get_shadow_complete, + .user_data = aws_string_new_from_c_str(allocator, correlation_token), + }; + + printf("Submitting GetNamedShadow '" PRInSTR "' for thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); + + if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { + int error_code = aws_last_error(); + printf("GetNamedShadow synchronous failure: %d(%s)", error_code, aws_error_debug_str(error_code)); + } +} + +static void s_handle_update( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments, + struct aws_byte_cursor line_cursor) { + (void)context; + (void)allocator; + (void)arguments; + (void)line_cursor; +} + +static void s_on_delete_shadow_complete( + const struct aws_byte_cursor *response_topic, + const struct aws_byte_cursor *payload, + int error_code, + void *user_data) { + + struct aws_string *correlation_token = user_data; + + if (payload != NULL) { + printf("DeleteNamedShadow request '%s' response received on topic '" PRInSTR "' with body:\n " PRInSTR "\n", correlation_token->bytes, AWS_BYTE_CURSOR_PRI(*response_topic), AWS_BYTE_CURSOR_PRI(*payload)); + } else { + printf("DeleteNamedShadow request '%s' failed with error code %d(%s)\n", correlation_token->bytes, error_code, aws_error_debug_str(error_code)); + } + + aws_string_destroy(correlation_token); +} + +static void s_handle_delete( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments) { + + size_t argument_count = aws_array_list_length(arguments) - 1; + if (argument_count != 2) { + printf("invalid delete options:\n"); + printf(" delete \n"); + return; + } + + struct aws_byte_cursor thing_name_cursor; + AWS_ZERO_STRUCT(thing_name_cursor); + aws_array_list_get_at(arguments, &thing_name_cursor, 1); + + struct aws_byte_cursor shadow_name_cursor; + AWS_ZERO_STRUCT(shadow_name_cursor); + aws_array_list_get_at(arguments, &shadow_name_cursor, 2); + + char subscription_topic_filter[128]; + snprintf(subscription_topic_filter, AWS_ARRAY_SIZE(subscription_topic_filter), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/+", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + char accepted_path[128]; + snprintf(accepted_path, AWS_ARRAY_SIZE(accepted_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/accepted", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + char rejected_path[128]; + snprintf(rejected_path, AWS_ARRAY_SIZE(rejected_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/rejected", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str(""); + struct aws_mqtt_request_operation_response_path response_paths[] = { + { + .topic = aws_byte_cursor_from_c_str(accepted_path), + .correlation_token_json_path = correlation_token_path, + }, + { + .topic = aws_byte_cursor_from_c_str(rejected_path), + .correlation_token_json_path = correlation_token_path, + }, + }; + + char publish_topic[128]; + snprintf(publish_topic, AWS_ARRAY_SIZE(publish_topic), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + char correlation_token[128]; + struct aws_byte_buf correlation_token_buf = aws_byte_buf_from_empty_array(correlation_token, AWS_ARRAY_SIZE(correlation_token)); + + struct aws_uuid uuid; + aws_uuid_init(&uuid); + aws_uuid_to_str(&uuid, &correlation_token_buf); + + char request[256]; + snprintf(request, AWS_ARRAY_SIZE(request), "{\"clientToken\":\"%s\"}", correlation_token); + + struct aws_mqtt_request_operation_options get_options = { + .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .response_paths = response_paths, + .response_path_count = 2, + .publish_topic = aws_byte_cursor_from_c_str(publish_topic), + .serialized_request = aws_byte_cursor_from_c_str(request), + .correlation_token = aws_byte_cursor_from_c_str(correlation_token), + .completion_callback = s_on_delete_shadow_complete, + .user_data = aws_string_new_from_c_str(allocator, correlation_token), + }; + + printf("Submitting DeleteNamedShadow '" PRInSTR "' for thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); + + if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { + int error_code = aws_last_error(); + printf("DeleteNamedShadow synchronous failure: %d(%s)", error_code, aws_error_debug_str(error_code)); + } +} + +static bool s_handle_input(struct app_ctx *context, struct aws_allocator *allocator, const char *input_line) { + + struct aws_mqtt5_client *client = context->client; + + struct aws_byte_cursor quit_cursor = aws_byte_cursor_from_c_str("quit"); + struct aws_byte_cursor start_cursor = aws_byte_cursor_from_c_str("start"); + struct aws_byte_cursor stop_cursor = aws_byte_cursor_from_c_str("stop"); + struct aws_byte_cursor get_cursor = aws_byte_cursor_from_c_str("get"); + struct aws_byte_cursor update_cursor = aws_byte_cursor_from_c_str("update"); + struct aws_byte_cursor delete_cursor = aws_byte_cursor_from_c_str("delete"); + + struct aws_array_list words; + aws_array_list_init_dynamic(&words, allocator, 10, sizeof(struct aws_byte_cursor)); + + struct aws_byte_cursor line_cursor = aws_byte_cursor_from_c_str(input_line); + line_cursor = aws_byte_cursor_trim_pred(&line_cursor, &s_skip_whitespace); + + bool done = false; + + s_split_command_line(line_cursor, &words); + if (aws_array_list_length(&words) == 0) { + printf("Empty command line\n"); + goto done; + } + + struct aws_byte_cursor command_cursor; + AWS_ZERO_STRUCT(command_cursor); + aws_array_list_get_at(&words, &command_cursor, 0); + + if (aws_byte_cursor_eq_ignore_case(&command_cursor, &quit_cursor)) { + printf("Quitting!\n"); + done = true; + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &start_cursor)) { + printf("Starting client!\n"); + aws_mqtt5_client_start(client); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &stop_cursor)) { + aws_mqtt5_client_stop(client, NULL, NULL); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &get_cursor)) { + s_handle_get(context, allocator, &words); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &update_cursor)) { + s_handle_update(context, allocator, &words, line_cursor); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &delete_cursor)) { + s_handle_delete(context, allocator, &words); + } else { + printf("Unknown command: " PRInSTR "\n", AWS_BYTE_CURSOR_PRI(command_cursor)); + } + +done: + + aws_array_list_clean_up(&words); + return done; +} + +static void s_on_publish_received(const struct aws_mqtt5_packet_publish_view *publish, void *user_data) { + (void)publish; + (void)user_data; + + printf("PUBLISH received!\n"); + printf( + "Publish received to topic:'" PRInSTR "' payload '" PRInSTR "'\n", + AWS_BYTE_CURSOR_PRI(publish->topic), + AWS_BYTE_CURSOR_PRI(publish->payload)); +} + +static void s_lifecycle_event_callback(const struct aws_mqtt5_client_lifecycle_event *event) { + + switch (event->event_type) { + case AWS_MQTT5_CLET_STOPPED: + printf("Lifecycle event: Stopped!\n"); + break; + + case AWS_MQTT5_CLET_ATTEMPTING_CONNECT: + printf("Lifecycle event: Attempting Connect!\n"); + break; + + case AWS_MQTT5_CLET_CONNECTION_FAILURE: + printf("Lifecycle event: Connection Failure!\n"); + printf(" Error Code: %d(%s)\n", event->error_code, aws_error_debug_str(event->error_code)); + break; + + case AWS_MQTT5_CLET_CONNECTION_SUCCESS: + printf("Lifecycle event: Connection Success!\n"); + break; + + case AWS_MQTT5_CLET_DISCONNECTION: + printf("Lifecycle event: Disconnect!\n"); + printf(" Error Code: %d(%s)\n", event->error_code, aws_error_debug_str(event->error_code)); + break; + } + + fflush(stdout); +} + +static void s_release_streaming_operations(struct app_ctx *ctx) { + (void)ctx; +} + +AWS_STATIC_STRING_FROM_LITERAL(s_client_id, "HelloWorld"); + +int main(int argc, char **argv) { + struct aws_allocator *allocator = aws_mem_tracer_new(aws_default_allocator(), NULL, AWS_MEMTRACE_STACKS, 15); + + aws_mqtt_library_init(allocator); + + struct app_ctx app_ctx; + AWS_ZERO_STRUCT(app_ctx); + app_ctx.allocator = allocator; + app_ctx.signal = (struct aws_condition_variable)AWS_CONDITION_VARIABLE_INIT; + aws_mutex_init(&app_ctx.lock); + app_ctx.port = 1883; + + s_parse_options(argc, argv, &app_ctx); + if (app_ctx.uri.port) { + app_ctx.port = app_ctx.uri.port; + } + + struct aws_logger logger; + AWS_ZERO_STRUCT(logger); + + struct aws_logger_standard_options options = { + .level = app_ctx.log_level, + }; + + if (app_ctx.log_level) { + if (app_ctx.log_filename) { + options.filename = app_ctx.log_filename; + } else { + options.file = stderr; + } + + if (aws_logger_init_standard(&logger, allocator, &options)) { + fprintf(stderr, "Failed to initialize logger with error %s\n", aws_error_debug_str(aws_last_error())); + exit(1); + } + + aws_logger_set(&logger); + } + + if (!app_ctx.cert || !app_ctx.key) { + fprintf(stderr, "Elastishadow requires mtls connections. You must specify a cert and key to use.\n"); + exit(1); + } + + struct aws_tls_ctx *tls_ctx = NULL; + struct aws_tls_ctx_options tls_ctx_options; + AWS_ZERO_STRUCT(tls_ctx_options); + struct aws_tls_connection_options tls_connection_options; + AWS_ZERO_STRUCT(tls_connection_options); + + if (aws_tls_ctx_options_init_client_mtls_from_path(&tls_ctx_options, allocator, app_ctx.cert, app_ctx.key)) { + fprintf( + stderr, + "Failed to load %s and %s with error %s.", + app_ctx.cert, + app_ctx.key, + aws_error_debug_str(aws_last_error())); + exit(1); + } + + if (aws_tls_ctx_options_set_alpn_list(&tls_ctx_options, "x-amzn-mqtt-ca")) { + fprintf(stderr, "Failed to set alpn list with error %s.", aws_error_debug_str(aws_last_error())); + exit(1); + } + + tls_ctx = aws_tls_client_ctx_new(allocator, &tls_ctx_options); + + if (!tls_ctx) { + fprintf(stderr, "Failed to initialize TLS context with error %s.", aws_error_debug_str(aws_last_error())); + exit(1); + } + + aws_tls_connection_options_init_from_ctx(&tls_connection_options, tls_ctx); + if (aws_tls_connection_options_set_server_name(&tls_connection_options, allocator, &app_ctx.uri.host_name)) { + fprintf(stderr, "Failed to set servername with error %s.", aws_error_debug_str(aws_last_error())); + exit(1); + } + + struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 2, NULL); + + struct aws_host_resolver_default_options resolver_options = { + .el_group = el_group, + .max_entries = 8, + }; + + struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); + + struct aws_client_bootstrap_options bootstrap_options = { + .event_loop_group = el_group, + .host_resolver = resolver, + }; + + struct aws_client_bootstrap *bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); + + struct aws_socket_options socket_options = { + .type = AWS_SOCKET_STREAM, + .connect_timeout_ms = (uint32_t)10000, + .keep_alive_timeout_sec = 0, + .keepalive = false, + .keep_alive_interval_sec = 0, + }; + + struct aws_mqtt5_packet_connect_view connect_options = { + .keep_alive_interval_seconds = 30, + .client_id = aws_byte_cursor_from_string(s_client_id), + }; + + struct aws_mqtt5_client_options client_options = { + .host_name = app_ctx.uri.host_name, + .port = app_ctx.port, + .bootstrap = bootstrap, + .socket_options = &socket_options, + .tls_options = &tls_connection_options, + .connect_options = &connect_options, + .session_behavior = AWS_MQTT5_CSBT_CLEAN, + .lifecycle_event_handler = s_lifecycle_event_callback, + .lifecycle_event_handler_user_data = NULL, + .retry_jitter_mode = AWS_EXPONENTIAL_BACKOFF_JITTER_NONE, + .min_reconnect_delay_ms = 1000, + .max_reconnect_delay_ms = 120000, + .min_connected_time_to_reset_reconnect_delay_ms = 30000, + .ping_timeout_ms = 10000, + .publish_received_handler = s_on_publish_received, + }; + + app_ctx.client = aws_mqtt5_client_new(allocator, &client_options); + + struct aws_mqtt_request_response_client_options rr_client_options = { + .max_subscriptions = 30, + .operation_timeout_seconds = 60, + }; + + app_ctx.rr_client = aws_mqtt_request_response_client_new_from_mqtt5_client(allocator, app_ctx.client, &rr_client_options); + + bool done = false; + while (!done) { + printf("Enter command:\n"); + + char input_buffer[4096]; +#ifdef WIN32 + char *line = gets_s(input_buffer, AWS_ARRAY_SIZE(input_buffer)); +#else + char *line = fgets(input_buffer, AWS_ARRAY_SIZE(input_buffer), stdin); +#endif + done = s_handle_input(&app_ctx, allocator, line); + } + + s_release_streaming_operations(&app_ctx); + + aws_mqtt_request_response_client_release(app_ctx.rr_client); + aws_mqtt5_client_release(app_ctx.client); + + aws_client_bootstrap_release(bootstrap); + aws_host_resolver_release(resolver); + aws_event_loop_group_release(el_group); + + if (tls_ctx) { + aws_tls_connection_options_clean_up(&tls_connection_options); + aws_tls_ctx_release(tls_ctx); + aws_tls_ctx_options_clean_up(&tls_ctx_options); + } + + aws_thread_join_all_managed(); + + const size_t outstanding_bytes = aws_mem_tracer_bytes(allocator); + printf("Summary:\n"); + printf(" Outstanding bytes: %zu\n\n", outstanding_bytes); + + if (app_ctx.log_level) { + aws_logger_set(NULL); + aws_logger_clean_up(&logger); + } + + aws_uri_clean_up(&app_ctx.uri); + + aws_mqtt_library_clean_up(); + + const size_t leaked_bytes = aws_mem_tracer_bytes(allocator); + if (leaked_bytes) { + struct aws_logger memory_logger; + AWS_ZERO_STRUCT(memory_logger); + + aws_logger_init_noalloc(&memory_logger, aws_default_allocator(), &options); + aws_logger_set(&memory_logger); + + aws_mqtt_library_init(aws_default_allocator()); + + printf("Writing memory leaks to log.\n"); + aws_mem_tracer_dump(allocator); + + aws_logger_set(NULL); + aws_logger_clean_up(&memory_logger); + + aws_mqtt_library_clean_up(); + } else { + printf("Finished, with no memory leaks\n"); + } + + aws_mem_tracer_destroy(allocator); + + return 0; +} From 468e73e7e3ecf12462e44ef5ce8ad9d3a18c661d Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 11 Apr 2024 10:33:33 -0700 Subject: [PATCH 103/124] Updates --- bin/elastishadow/main.c | 133 ++++++++++++++++++++++++++++++++++------ 1 file changed, 113 insertions(+), 20 deletions(-) diff --git a/bin/elastishadow/main.c b/bin/elastishadow/main.c index b87f0306..08639bf5 100644 --- a/bin/elastishadow/main.c +++ b/bin/elastishadow/main.c @@ -189,8 +189,8 @@ static void s_handle_get( size_t argument_count = aws_array_list_length(arguments) - 1; if (argument_count != 2) { - printf("invalid get options:\n"); - printf(" get \n"); + printf("invalid get-named-shadow options:\n"); + printf(" get-named-shadow \n"); return; } @@ -211,7 +211,7 @@ static void s_handle_get( char rejected_path[128]; snprintf(rejected_path, AWS_ARRAY_SIZE(rejected_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/rejected", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); - struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str(""); + struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str("clientToken"); struct aws_mqtt_request_operation_response_path response_paths[] = { { .topic = aws_byte_cursor_from_c_str(accepted_path), @@ -247,7 +247,7 @@ static void s_handle_get( .user_data = aws_string_new_from_c_str(allocator, correlation_token), }; - printf("Submitting GetNamedShadow '" PRInSTR "' for thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); + printf("Submitting GetNamedShadow request for shadow '" PRInSTR "' of thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { int error_code = aws_last_error(); @@ -255,7 +255,7 @@ static void s_handle_get( } } -static void s_handle_update( +static void s_handle_update_reported( struct app_ctx *context, struct aws_allocator *allocator, struct aws_array_list *arguments, @@ -266,6 +266,102 @@ static void s_handle_update( (void)line_cursor; } +static void s_on_update_shadow_desired_complete( + const struct aws_byte_cursor *response_topic, + const struct aws_byte_cursor *payload, + int error_code, + void *user_data) { + + struct aws_string *correlation_token = user_data; + + if (payload != NULL) { + printf("UpdateNameShadowDesired request '%s' response received on topic '" PRInSTR "' with body:\n " PRInSTR "\n", correlation_token->bytes, AWS_BYTE_CURSOR_PRI(*response_topic), AWS_BYTE_CURSOR_PRI(*payload)); + } else { + printf("UpdateNameShadowDesired request '%s' failed with error code %d(%s)\n", correlation_token->bytes, error_code, aws_error_debug_str(error_code)); + } + + aws_string_destroy(correlation_token); +} + + +static void s_handle_update_desired( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments, + struct aws_byte_cursor line_cursor) { + + size_t argument_count = aws_array_list_length(arguments) - 1; + if (argument_count < 3) { + printf("invalid update-named-shadow-desired options:\n"); + printf(" delete-named-shadow \n"); + return; + } + + struct aws_byte_cursor thing_name_cursor; + AWS_ZERO_STRUCT(thing_name_cursor); + aws_array_list_get_at(arguments, &thing_name_cursor, 1); + + struct aws_byte_cursor shadow_name_cursor; + AWS_ZERO_STRUCT(shadow_name_cursor); + aws_array_list_get_at(arguments, &shadow_name_cursor, 2); + + struct aws_byte_cursor desired_state_cursor; + aws_array_list_get_at(arguments, &desired_state_cursor, 3); + desired_state_cursor.len = (size_t)(line_cursor.ptr + line_cursor.len - desired_state_cursor.ptr); + + char subscription_topic_filter[128]; + snprintf(subscription_topic_filter, AWS_ARRAY_SIZE(subscription_topic_filter), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/+", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + char accepted_path[128]; + snprintf(accepted_path, AWS_ARRAY_SIZE(accepted_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/accepted", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + char rejected_path[128]; + snprintf(rejected_path, AWS_ARRAY_SIZE(rejected_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/rejected", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str("clientToken"); + struct aws_mqtt_request_operation_response_path response_paths[] = { + { + .topic = aws_byte_cursor_from_c_str(accepted_path), + .correlation_token_json_path = correlation_token_path, + }, + { + .topic = aws_byte_cursor_from_c_str(rejected_path), + .correlation_token_json_path = correlation_token_path, + }, + }; + + char publish_topic[128]; + snprintf(publish_topic, AWS_ARRAY_SIZE(publish_topic), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + char correlation_token[128]; + struct aws_byte_buf correlation_token_buf = aws_byte_buf_from_empty_array(correlation_token, AWS_ARRAY_SIZE(correlation_token)); + + struct aws_uuid uuid; + aws_uuid_init(&uuid); + aws_uuid_to_str(&uuid, &correlation_token_buf); + + char request[256]; + snprintf(request, AWS_ARRAY_SIZE(request), "{\"clientToken\":\"%s\",\"state\":{\"desired\":" PRInSTR "}}", correlation_token, AWS_BYTE_CURSOR_PRI(desired_state_cursor)); + + struct aws_mqtt_request_operation_options get_options = { + .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .response_paths = response_paths, + .response_path_count = 2, + .publish_topic = aws_byte_cursor_from_c_str(publish_topic), + .serialized_request = aws_byte_cursor_from_c_str(request), + .correlation_token = aws_byte_cursor_from_c_str(correlation_token), + .completion_callback = s_on_update_shadow_desired_complete, + .user_data = aws_string_new_from_c_str(allocator, correlation_token), + }; + + printf("Submitting UpdateNameShadowDesired request for shadow '" PRInSTR "' of thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); + + if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { + int error_code = aws_last_error(); + printf("UpdateNameShadowDesired synchronous failure: %d(%s)", error_code, aws_error_debug_str(error_code)); + } +} + static void s_on_delete_shadow_complete( const struct aws_byte_cursor *response_topic, const struct aws_byte_cursor *payload, @@ -290,8 +386,8 @@ static void s_handle_delete( size_t argument_count = aws_array_list_length(arguments) - 1; if (argument_count != 2) { - printf("invalid delete options:\n"); - printf(" delete \n"); + printf("invalid delete-named-shadow options:\n"); + printf(" delete-named-shadow \n"); return; } @@ -312,7 +408,7 @@ static void s_handle_delete( char rejected_path[128]; snprintf(rejected_path, AWS_ARRAY_SIZE(rejected_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/rejected", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); - struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str(""); + struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str("clientToken"); struct aws_mqtt_request_operation_response_path response_paths[] = { { .topic = aws_byte_cursor_from_c_str(accepted_path), @@ -348,7 +444,7 @@ static void s_handle_delete( .user_data = aws_string_new_from_c_str(allocator, correlation_token), }; - printf("Submitting DeleteNamedShadow '" PRInSTR "' for thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); + printf("Submitting DeleteNamedShadow request for shadow '" PRInSTR "' of thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { int error_code = aws_last_error(); @@ -363,9 +459,10 @@ static bool s_handle_input(struct app_ctx *context, struct aws_allocator *alloca struct aws_byte_cursor quit_cursor = aws_byte_cursor_from_c_str("quit"); struct aws_byte_cursor start_cursor = aws_byte_cursor_from_c_str("start"); struct aws_byte_cursor stop_cursor = aws_byte_cursor_from_c_str("stop"); - struct aws_byte_cursor get_cursor = aws_byte_cursor_from_c_str("get"); - struct aws_byte_cursor update_cursor = aws_byte_cursor_from_c_str("update"); - struct aws_byte_cursor delete_cursor = aws_byte_cursor_from_c_str("delete"); + struct aws_byte_cursor get_cursor = aws_byte_cursor_from_c_str("get-named-shadow"); + struct aws_byte_cursor update_reported_cursor = aws_byte_cursor_from_c_str("update-named-shadow-reported"); + struct aws_byte_cursor update_desired_cursor = aws_byte_cursor_from_c_str("update-named-shadow-desired"); + struct aws_byte_cursor delete_cursor = aws_byte_cursor_from_c_str("delete-named-shadow"); struct aws_array_list words; aws_array_list_init_dynamic(&words, allocator, 10, sizeof(struct aws_byte_cursor)); @@ -395,8 +492,10 @@ static bool s_handle_input(struct app_ctx *context, struct aws_allocator *alloca aws_mqtt5_client_stop(client, NULL, NULL); } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &get_cursor)) { s_handle_get(context, allocator, &words); - } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &update_cursor)) { - s_handle_update(context, allocator, &words, line_cursor); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &update_reported_cursor)) { + s_handle_update_reported(context, allocator, &words, line_cursor); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &update_desired_cursor)) { + s_handle_update_desired(context, allocator, &words, line_cursor); } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &delete_cursor)) { s_handle_delete(context, allocator, &words); } else { @@ -412,12 +511,6 @@ static bool s_handle_input(struct app_ctx *context, struct aws_allocator *alloca static void s_on_publish_received(const struct aws_mqtt5_packet_publish_view *publish, void *user_data) { (void)publish; (void)user_data; - - printf("PUBLISH received!\n"); - printf( - "Publish received to topic:'" PRInSTR "' payload '" PRInSTR "'\n", - AWS_BYTE_CURSOR_PRI(publish->topic), - AWS_BYTE_CURSOR_PRI(publish->payload)); } static void s_lifecycle_event_callback(const struct aws_mqtt5_client_lifecycle_event *event) { From 8567ffbfc64b1feb218d3c659bb91e45757402a8 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 11 Apr 2024 19:40:08 -0700 Subject: [PATCH 104/124] Checkpoint --- .../request_response_client.h | 2 +- .../request-response/subscription_manager.h | 33 +- .../request_response_client.h | 21 +- .../request_response_client.c | 120 +++++-- .../request-response/subscription_manager.c | 219 +++++++----- .../subscription_manager_tests.c | 338 +++++++++++------- 6 files changed, 446 insertions(+), 287 deletions(-) diff --git a/include/aws/mqtt/private/request-response/request_response_client.h b/include/aws/mqtt/private/request-response/request_response_client.h index d0109f24..b64ae7fa 100644 --- a/include/aws/mqtt/private/request-response/request_response_client.h +++ b/include/aws/mqtt/private/request-response/request_response_client.h @@ -8,7 +8,7 @@ #include -struct aws_mqtt_request_response_client; +#include AWS_EXTERN_C_BEGIN diff --git a/include/aws/mqtt/private/request-response/subscription_manager.h b/include/aws/mqtt/private/request-response/subscription_manager.h index 072c580c..da70b4c9 100644 --- a/include/aws/mqtt/private/request-response/subscription_manager.h +++ b/include/aws/mqtt/private/request-response/subscription_manager.h @@ -90,9 +90,14 @@ typedef void( struct aws_rr_subscription_manager_options { /* - * Maximum number of concurrent subscriptions allowed + * Maximum number of request-response subscriptions allowed. Must be at least two. */ - size_t max_subscriptions; + size_t max_request_response_subscriptions; + + /* + * Maximum number of streaming subscriptions allowed. + */ + size_t max_streaming_subscriptions; /* * Ack timeout to use for all subscribe and unsubscribe operations @@ -104,8 +109,8 @@ struct aws_rr_subscription_manager_options { }; /* - * The subscription manager works from a purely lazy perspective. Unsubscribes (from topic filters that are no longer - * referenced) occur when looking for new subscription space. Unsubscribe failures don't trigger anything special, + * The subscription manager works with the request-response client to handle subscriptions in an eager manner. + * Subscription purges are checked with every client service call. Unsubscribe failures don't trigger anything special, * we'll just try again next time we look for subscription space. Subscribes are attempted on idle subscriptions * that still need them, either in response to a new operation listener or a connection resumption event. * @@ -135,43 +140,47 @@ enum aws_rr_subscription_type { }; struct aws_rr_acquire_subscription_options { - struct aws_byte_cursor topic_filter; + struct aws_byte_cursor *topic_filters; + size_t topic_filter_count; + uint64_t operation_id; enum aws_rr_subscription_type type; }; struct aws_rr_release_subscription_options { - struct aws_byte_cursor topic_filter; + struct aws_byte_cursor *topic_filters; + size_t topic_filter_count; + uint64_t operation_id; }; enum aws_acquire_subscription_result_type { /* - * The requested subscription already exists and is active. The operation can proceed to the next stage. + * All requested subscriptions already exist and are active. The operation can proceed to the next stage. */ AASRT_SUBSCRIBED, /* - * The requested subscription now exists but is not yet active. The operation must wait for the subscribe + * The requested subscriptions now exist but at least one is not yet active. The operation must wait for subscribes * to complete as success or failure. */ AASRT_SUBSCRIBING, /* - * The subscription does not exist and there is no room for it currently. Room may open up in the future, so - * the operation should wait. + * At least one subscription does not exist and there is no room for it currently. Room may open up in the future, + * so the operation should wait. */ AASRT_BLOCKED, /* - * The subscription does not exist, there is no room for it, and unless an event stream subscription gets + * At least one subscription does not exist and there is no room for it. Unless an event stream subscription gets * closed, no room will be available in the future. The operation should be failed. */ AASRT_NO_CAPACITY, /* - * An internal failure occurred while trying to establish the subscription. The operation should be failed. + * An internal failure occurred while trying to establish subscriptions. The operation should be failed. */ AASRT_FAILURE }; diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 18b01843..1abd108a 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -26,7 +26,8 @@ typedef void(aws_mqtt_request_operation_completion_fn)( void *user_data); struct aws_mqtt_request_operation_options { - struct aws_byte_cursor subscription_topic_filter; + struct aws_byte_cursor *subscription_topic_filters; + size_t subscription_topic_filter_count; struct aws_mqtt_request_operation_response_path *response_paths; size_t response_path_count; @@ -39,14 +40,6 @@ struct aws_mqtt_request_operation_options { void *user_data; }; -struct aws_mqtt_request_operation_storage { - struct aws_mqtt_request_operation_options options; - - struct aws_array_list operation_response_paths; - - struct aws_byte_buf operation_data; -}; - /* * Describes a change to the state of a request operation subscription */ @@ -86,17 +79,13 @@ struct aws_mqtt_streaming_operation_options { void *user_data; }; -struct aws_mqtt_streaming_operation_storage { - struct aws_mqtt_streaming_operation_options options; - - struct aws_byte_buf operation_data; -}; - typedef void(aws_mqtt_request_response_client_initialized_callback_fn)(void *user_data); typedef void(aws_mqtt_request_response_client_terminated_callback_fn)(void *user_data); struct aws_mqtt_request_response_client_options { - size_t max_subscriptions; + size_t max_request_response_subscriptions; + size_t max_streaming_subscriptions; + uint32_t operation_timeout_seconds; /* Do not bind the initialized callback; it exists mostly for tests and should not be exposed */ diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index dc13b273..c56f3d64 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -20,6 +20,21 @@ #define MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE 50 #define MQTT_RR_CLIENT_RESPONSE_TABLE_DEFAULT_SIZE 50 +struct aws_mqtt_request_operation_storage { + struct aws_mqtt_request_operation_options options; + + struct aws_array_list operation_response_paths; + struct aws_array_list subscription_topic_filters; + + struct aws_byte_buf operation_data; +}; + +struct aws_mqtt_streaming_operation_storage { + struct aws_mqtt_streaming_operation_options options; + + struct aws_byte_buf operation_data; +}; + enum aws_mqtt_request_response_operation_type { AWS_MRROT_REQUEST, AWS_MRROT_STREAMING, @@ -1117,7 +1132,8 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie const struct aws_mqtt_request_response_client_options *options, struct aws_event_loop *loop) { struct aws_rr_subscription_manager_options sm_options = { - .max_subscriptions = options->max_subscriptions, + .max_request_response_subscriptions = options->max_request_response_subscriptions, + .max_streaming_subscriptions = options->max_streaming_subscriptions, .operation_timeout_seconds = options->operation_timeout_seconds, }; @@ -1215,7 +1231,8 @@ static void s_aws_rr_client_init_subscription_manager( struct aws_rr_subscription_manager_options subscription_manager_options = { .operation_timeout_seconds = rr_client->config.operation_timeout_seconds, - .max_subscriptions = rr_client->config.max_subscriptions, + .max_request_response_subscriptions = rr_client->config.max_request_response_subscriptions, + .max_streaming_subscriptions = rr_client->config.max_streaming_subscriptions, .subscription_status_callback = s_aws_rr_client_subscription_status_event_callback, .userdata = rr_client, }; @@ -1266,15 +1283,6 @@ static uint64_t s_mqtt_request_response_client_get_next_service_time(struct aws_ return UINT64_MAX; } -static struct aws_byte_cursor s_aws_mqtt_rr_operation_get_subscription_topic_filter( - struct aws_mqtt_rr_client_operation *operation) { - if (operation->type == AWS_MRROT_REQUEST) { - return operation->storage.request_storage.options.subscription_topic_filter; - } else { - return operation->storage.streaming_storage.options.topic_filter; - } -} - /* TODO: add aws-c-common API? */ static bool s_is_operation_in_list(const struct aws_mqtt_rr_client_operation *operation) { return aws_linked_list_node_prev_is_valid(&operation->node) && aws_linked_list_node_next_is_valid(&operation->node); @@ -1284,7 +1292,7 @@ static int s_add_streaming_operation_to_subscription_topic_filter_table( struct aws_mqtt_request_response_client *client, struct aws_mqtt_rr_client_operation *operation) { - struct aws_byte_cursor topic_filter_cursor = s_aws_mqtt_rr_operation_get_subscription_topic_filter(operation); + struct aws_byte_cursor topic_filter_cursor = operation->storage.streaming_storage.options.topic_filter; struct aws_hash_element *element = NULL; if (aws_hash_table_find(&client->streaming_operation_subscription_lists, &topic_filter_cursor, &element)) { @@ -1445,6 +1453,24 @@ static bool s_can_operation_dequeue( return token_element == NULL; } +static struct aws_byte_cursor *s_aws_mqtt_rr_operation_get_subscription_topic_filters( + struct aws_mqtt_rr_client_operation *operation) { + if (operation->type == AWS_MRROT_STREAMING) { + return &operation->storage.streaming_storage.options.topic_filter; + } else { + return operation->storage.request_storage.options.subscription_topic_filters; + } +} + +static size_t s_aws_mqtt_rr_operation_get_subscription_topic_filter_count( + struct aws_mqtt_rr_client_operation *operation) { + if (operation->type == AWS_MRROT_STREAMING) { + return 1; + } else { + return operation->storage.request_storage.options.subscription_topic_filter_count; + } +} + static void s_process_queued_operations(struct aws_mqtt_request_response_client *client) { aws_rr_subscription_manager_purge_unused(&client->subscription_manager); @@ -1458,7 +1484,8 @@ static void s_process_queued_operations(struct aws_mqtt_request_response_client } struct aws_rr_acquire_subscription_options subscribe_options = { - .topic_filter = s_aws_mqtt_rr_operation_get_subscription_topic_filter(head_operation), + .topic_filters = s_aws_mqtt_rr_operation_get_subscription_topic_filters(head_operation), + .topic_filter_count = s_aws_mqtt_rr_operation_get_subscription_topic_filter_count(head_operation), .operation_id = head_operation->id, .type = s_rr_operation_type_to_subscription_type(head_operation->type), }; @@ -1692,15 +1719,26 @@ static bool s_are_request_operation_options_valid( return false; } - if (!aws_mqtt_is_valid_topic_filter(&request_options->subscription_topic_filter)) { + if (request_options->subscription_topic_filter_count == 0) { AWS_LOGF_ERROR( AWS_LS_MQTT_REQUEST_RESPONSE, - "(%p) rr client request options - " PRInSTR " is not a valid topic filter", - (void *)client, - AWS_BYTE_CURSOR_PRI(request_options->subscription_topic_filter)); + "(%p) rr client request options - no subscription topic filters supplied", + (void *)client); return false; } + for (size_t i = 0; i < request_options->subscription_topic_filter_count; ++i) { + const struct aws_byte_cursor subscription_topic_filter = request_options->subscription_topic_filters[i]; + if (!aws_mqtt_is_valid_topic_filter(&subscription_topic_filter)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "(%p) rr client request options - " PRInSTR " is not a valid subscription topic filter", + (void *)client, + AWS_BYTE_CURSOR_PRI(subscription_topic_filter)); + return false; + } + } + if (request_options->serialized_request.len == 0) { AWS_LOGF_ERROR( AWS_LS_MQTT_REQUEST_RESPONSE, "(%p) rr client request options - empty request payload", (void *)client); @@ -1860,7 +1898,8 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, if (client->state != AWS_RRCS_SHUTTING_DOWN) { struct aws_rr_release_subscription_options release_options = { - .topic_filter = s_aws_mqtt_rr_operation_get_subscription_topic_filter(operation), + .topic_filters = s_aws_mqtt_rr_operation_get_subscription_topic_filters(operation), + .topic_filter_count = s_aws_mqtt_rr_operation_get_subscription_topic_filter_count(operation), .operation_id = operation->id, }; aws_rr_subscription_manager_release_subscription(&client->subscription_manager, &release_options); @@ -1933,7 +1972,12 @@ void s_aws_mqtt_request_operation_storage_init_from_options( bytes_needed += request_options->publish_topic.len; bytes_needed += request_options->serialized_request.len; bytes_needed += request_options->correlation_token.len; - bytes_needed += request_options->subscription_topic_filter.len; + + for (size_t i = 0; i < request_options->subscription_topic_filter_count; ++i) { + const struct aws_byte_cursor *subscription_topic_filter = &request_options->subscription_topic_filters[i]; + + bytes_needed += subscription_topic_filter->len; + } for (size_t i = 0; i < request_options->response_path_count; ++i) { const struct aws_mqtt_request_operation_response_path *response_path = &request_options->response_paths[i]; @@ -1950,6 +1994,11 @@ void s_aws_mqtt_request_operation_storage_init_from_options( allocator, request_options->response_path_count, sizeof(struct aws_mqtt_request_operation_response_path)); + aws_array_list_init_dynamic( + &storage->subscription_topic_filters, + allocator, + request_options->subscription_topic_filter_count, + sizeof(struct aws_byte_cursor)); AWS_FATAL_ASSERT( aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.publish_topic) == AWS_OP_SUCCESS); @@ -1959,9 +2008,15 @@ void s_aws_mqtt_request_operation_storage_init_from_options( AWS_FATAL_ASSERT( aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.correlation_token) == AWS_OP_SUCCESS); - AWS_FATAL_ASSERT( - aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.subscription_topic_filter) == - AWS_OP_SUCCESS); + + for (size_t i = 0; i < request_options->subscription_topic_filter_count; ++i) { + struct aws_byte_cursor subscription_topic_filter = request_options->subscription_topic_filters[i]; + + AWS_FATAL_ASSERT( + aws_byte_buf_append_and_update(&storage->operation_data, &subscription_topic_filter) == AWS_OP_SUCCESS); + + aws_array_list_push_back(&storage->subscription_topic_filters, &subscription_topic_filter); + } for (size_t i = 0; i < request_options->response_path_count; ++i) { struct aws_mqtt_request_operation_response_path response_path = request_options->response_paths[i]; @@ -1988,14 +2043,19 @@ static void s_log_request_response_operation( struct aws_mqtt_request_operation_options *options = &operation->storage.request_storage.options; - AWS_LOGUF( - log_handle, - AWS_LL_DEBUG, - AWS_LS_MQTT_REQUEST_RESPONSE, - "id=%p: request-response client operation %" PRIu64 " - subscription topic filter: '" PRInSTR "'", - (void *)client, - operation->id, - AWS_BYTE_CURSOR_PRI(options->subscription_topic_filter)); + for (size_t i = 0; i < options->subscription_topic_filter_count; ++i) { + struct aws_byte_cursor subscription_topic_filter = options->subscription_topic_filters[i]; + + AWS_LOGUF( + log_handle, + AWS_LL_DEBUG, + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client operation %" PRIu64 " - subscription topic filter %zu topic '" PRInSTR "'", + (void *)client, + operation->id, + i, + AWS_BYTE_CURSOR_PRI(subscription_topic_filter)); + } AWS_LOGUF( log_handle, diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 6a0d2d16..0430cb66 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -76,15 +76,6 @@ struct aws_rr_subscription_record { bool poisoned; }; -static void s_aws_rr_subscription_record_log_invariant_violations(const struct aws_rr_subscription_record *record) { - if (record->status == ARRSST_SUBSCRIBED && record->pending_action == ARRSPAT_SUBSCRIBING) { - AWS_LOGF_ERROR( - AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - subscription ('" PRInSTR "') invalid state", - AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); - } -} - static void s_aws_rr_subscription_record_destroy(void *element) { struct aws_rr_subscription_record *record = element; @@ -96,11 +87,12 @@ static void s_aws_rr_subscription_record_destroy(void *element) { static struct aws_rr_subscription_record *s_aws_rr_subscription_new( struct aws_allocator *allocator, - const struct aws_rr_acquire_subscription_options *options) { + struct aws_byte_cursor topic_filter, + enum aws_rr_subscription_type type) { struct aws_rr_subscription_record *record = aws_mem_calloc(allocator, 1, sizeof(struct aws_rr_subscription_record)); record->allocator = allocator; - aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, options->topic_filter); + aws_byte_buf_init_copy_from_cursor(&record->topic_filter, allocator, topic_filter); record->topic_filter_cursor = aws_byte_cursor_from_buf(&record->topic_filter); aws_hash_table_init( @@ -115,7 +107,7 @@ static struct aws_rr_subscription_record *s_aws_rr_subscription_new( record->status = ARRSST_NOT_SUBSCRIBED; record->pending_action = ARRSPAT_NOTHING; - record->type = options->type; + record->type = type; return record; } @@ -451,114 +443,148 @@ static int s_rr_activate_idle_subscription( enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription( struct aws_rr_subscription_manager *manager, const struct aws_rr_acquire_subscription_options *options) { - struct aws_rr_subscription_record *existing_record = s_get_subscription_record(manager, options->topic_filter); - // is no subscription present? - if (existing_record == NULL) { - // is the budget used up? - struct aws_subscription_stats stats; - s_get_subscription_stats(manager, &stats); + if (options->topic_filter_count == 0) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } - bool space_for_subscription = - stats.event_stream_subscriptions + stats.request_response_subscriptions < manager->config.max_subscriptions; - if (options->type == ARRST_EVENT_STREAM) { - // event stream subscriptions cannot hog the entire subscription budget - space_for_subscription = - space_for_subscription && (stats.event_stream_subscriptions + 1 < manager->config.max_subscriptions); + /* + * Check for poisoned or mismatched records. This has precedence over the following unsubscribing check, + * and so we put them in separate loops + */ + for (size_t i = 0; i < options->topic_filter_count; ++i) { + struct aws_byte_cursor topic_filter = options->topic_filters[i]; + struct aws_rr_subscription_record *existing_record = s_get_subscription_record(manager, topic_filter); + if (existing_record != NULL) { + if (existing_record->poisoned) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR + "'), operation %" PRIu64 " failed - existing subscription is poisoned and has not been released", + AWS_BYTE_CURSOR_PRI(topic_filter), + options->operation_id); + return AASRT_FAILURE; + } + } + + if (existing_record->type != options->type) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 + " failed - conflicts with subscription type of existing subscription", + AWS_BYTE_CURSOR_PRI(topic_filter), + options->operation_id); + return AASRT_FAILURE; } + } - if (!space_for_subscription) { - // could space eventually free up? - if (options->type == ARRST_REQUEST_RESPONSE || stats.request_response_subscriptions > 1 || - stats.unsubscribing_event_stream_subscriptions > 0) { + /* blocked if an existing record is unsubscribing; also compute how many subscriptions are needed */ + size_t subscriptions_needed = 0; + for (size_t i = 0; i < options->topic_filter_count; ++i) { + struct aws_byte_cursor topic_filter = options->topic_filters[i]; + struct aws_rr_subscription_record *existing_record = s_get_subscription_record(manager, topic_filter); + if (existing_record != NULL) { + if (existing_record->pending_action == ARRSPAT_UNSUBSCRIBING) { AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, "request-response subscription manager - acquire subscription for ('" PRInSTR - "'), operation %" PRIu64 " blocked - no room currently", - AWS_BYTE_CURSOR_PRI(options->topic_filter), + "'), operation %" PRIu64 " blocked - existing subscription is unsubscribing", + AWS_BYTE_CURSOR_PRI(topic_filter), options->operation_id); return AASRT_BLOCKED; - } else { + } + } else { + ++subscriptions_needed; + } + } + + /* Check for space and fail or block as appropriate */ + if (subscriptions_needed > 0) { + /* how much of the budget are we using? */ + struct aws_subscription_stats stats; + s_get_subscription_stats(manager, &stats); + + if (options->type == ARRST_REQUEST_RESPONSE) { + if (subscriptions_needed > + manager->config.max_request_response_subscriptions - stats.request_response_subscriptions) { AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - acquire subscription for ('" PRInSTR - "'), operation %" PRIu64 " failed - no room", - AWS_BYTE_CURSOR_PRI(options->topic_filter), + "request-response subscription manager - acquire subscription for request operation %" PRIu64 + " blocked - no room currently", options->operation_id); - return AASRT_NO_CAPACITY; + return AASRT_BLOCKED; + } + } else { + /* + * Streaming subscriptions have more complicated space-checking logic. Under certain conditions, we may + * block rather than failing + */ + if (subscriptions_needed + stats.event_stream_subscriptions > manager->config.max_streaming_subscriptions) { + if (subscriptions_needed + stats.event_stream_subscriptions <= + manager->config.max_streaming_subscriptions + stats.unsubscribing_event_stream_subscriptions) { + /* If enough subscriptions are in the process of going away then wait in the blocked state */ + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for streaming operation %" PRIu64 + " blocked - no room currently", + options->operation_id); + return AASRT_BLOCKED; + } else { + /* Otherwise, there's no hope, fail */ + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for operation %" PRIu64 + " failed - no room", + options->operation_id); + return AASRT_NO_CAPACITY; + } } } - - // create-and-add subscription - existing_record = s_aws_rr_subscription_new(manager->allocator, options); - aws_hash_table_put(&manager->subscriptions, &existing_record->topic_filter_cursor, existing_record, NULL); - } - - AWS_FATAL_ASSERT(existing_record != NULL); - if (existing_record->type != options->type) { - AWS_LOGF_ERROR( - AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 - " failed - conflicts with subscription type of existing subscription", - AWS_BYTE_CURSOR_PRI(options->topic_filter), - options->operation_id); - return AASRT_FAILURE; } - if (existing_record->poisoned) { - AWS_LOGF_ERROR( - AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 - " failed - existing subscription is poisoned and has not been released", - AWS_BYTE_CURSOR_PRI(options->topic_filter), - options->operation_id); - return AASRT_FAILURE; - } + bool is_fully_subscribed = true; + for (size_t i = 0; i < options->topic_filter_count; ++i) { + struct aws_byte_cursor topic_filter = options->topic_filters[i]; + struct aws_rr_subscription_record *existing_record = s_get_subscription_record(manager, topic_filter); - s_aws_rr_subscription_record_log_invariant_violations(existing_record); + if (existing_record == NULL) { + existing_record = s_aws_rr_subscription_new(manager->allocator, topic_filter, options->type); + aws_hash_table_put(&manager->subscriptions, &existing_record->topic_filter_cursor, existing_record, NULL); + } - // for simplicity, we require unsubscribes to complete before re-subscribing - if (existing_record->pending_action == ARRSPAT_UNSUBSCRIBING) { - AWS_LOGF_DEBUG( - AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 - " blocked - existing subscription is unsubscribing", - AWS_BYTE_CURSOR_PRI(options->topic_filter), - options->operation_id); - return AASRT_BLOCKED; + s_add_listener_to_subscription_record(existing_record, options->operation_id); + if (existing_record->status != ARRSST_SUBSCRIBED) { + is_fully_subscribed = false; + } } - // register the operation as a listener - s_add_listener_to_subscription_record(existing_record, options->operation_id); - if (existing_record->status == ARRSST_SUBSCRIBED) { + if (is_fully_subscribed) { AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 - " subscribed - existing subscription is active", - AWS_BYTE_CURSOR_PRI(options->topic_filter), + "request-response subscription manager - acquire subscription for operation %" PRIu64 + " fully subscribed - all required subscriptions are active", options->operation_id); return AASRT_SUBSCRIBED; } - // do we need to send a subscribe? - if (s_rr_activate_idle_subscription(manager, existing_record)) { - // error code was already logged at the point of failure - AWS_LOGF_ERROR( - AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 - " failed - synchronous subscribe failure", - AWS_BYTE_CURSOR_PRI(options->topic_filter), - options->operation_id); - return AASRT_FAILURE; - } + for (size_t i = 0; i < options->topic_filter_count; ++i) { + struct aws_byte_cursor topic_filter = options->topic_filters[i]; + struct aws_rr_subscription_record *existing_record = s_get_subscription_record(manager, topic_filter); - s_aws_rr_subscription_record_log_invariant_violations(existing_record); + if (s_rr_activate_idle_subscription(manager, existing_record)) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for operation %" PRIu64 + " failed - synchronous subscribe failure", + options->operation_id); + return AASRT_FAILURE; + } + } AWS_LOGF_DEBUG( AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 - " subscribing - waiting on existing subscription", - AWS_BYTE_CURSOR_PRI(options->topic_filter), + "request-response subscription manager - acquire subscription for operation %" PRIu64 + " subscribing - waiting on one or more subscribes to complete", options->operation_id); return AASRT_SUBSCRIBING; @@ -567,7 +593,10 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su void aws_rr_subscription_manager_release_subscription( struct aws_rr_subscription_manager *manager, const struct aws_rr_release_subscription_options *options) { - s_remove_listener_from_subscription_record(manager, options->topic_filter, options->operation_id); + for (size_t i = 0; i < options->topic_filter_count; ++i) { + struct aws_byte_cursor topic_filter = options->topic_filters[i]; + s_remove_listener_from_subscription_record(manager, topic_filter, options->operation_id); + } } static void s_handle_protocol_adapter_request_subscription_event( @@ -663,8 +692,6 @@ void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( } else { s_handle_protocol_adapter_streaming_subscription_event(manager, record, event); } - - s_aws_rr_subscription_record_log_invariant_violations(record); } static int s_rr_activate_idle_subscriptions_wrapper(void *context, struct aws_hash_element *elem) { @@ -673,8 +700,6 @@ static int s_rr_activate_idle_subscriptions_wrapper(void *context, struct aws_ha s_rr_activate_idle_subscription(manager, record); - s_aws_rr_subscription_record_log_invariant_violations(record); - return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; } @@ -748,7 +773,7 @@ void aws_rr_subscription_manager_on_protocol_adapter_connection_event( } bool aws_rr_subscription_manager_are_options_valid(const struct aws_rr_subscription_manager_options *options) { - if (options == NULL || options->max_subscriptions < 1 || options->operation_timeout_seconds == 0) { + if (options == NULL || options->max_request_response_subscriptions < 2 || options->operation_timeout_seconds == 0) { return false; } @@ -771,7 +796,7 @@ void aws_rr_subscription_manager_init( aws_hash_table_init( &manager->subscriptions, allocator, - options->max_subscriptions, + options->max_request_response_subscriptions + options->max_streaming_subscriptions, aws_hash_byte_cursor_ptr, aws_mqtt_byte_cursor_hash_equality, NULL, diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 4c23d7c0..099b1d1a 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -262,7 +262,8 @@ static void s_aws_rr_subscription_status_event_test_callback_fn( struct aws_subscription_manager_test_fixture_options { uint32_t operation_timeout_seconds; - size_t max_subscriptions; + size_t max_request_response_subscriptions; + size_t max_streaming_subscriptions; bool start_connected; const struct aws_mqtt_protocol_adapter_vtable *adapter_vtable; }; @@ -276,7 +277,8 @@ static int s_aws_subscription_manager_test_fixture_init( AWS_ZERO_STRUCT(*fixture); struct aws_subscription_manager_test_fixture_options default_options = { - .max_subscriptions = 3, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, .start_connected = true, }; @@ -293,7 +295,8 @@ static int s_aws_subscription_manager_test_fixture_init( &fixture->subscription_status_records, allocator, 10, sizeof(struct aws_subscription_status_record)); struct aws_rr_subscription_manager_options subscription_manager_options = { - .max_subscriptions = options->max_subscriptions, + .max_request_response_subscriptions = options->max_request_response_subscriptions, + .max_streaming_subscriptions = options->max_streaming_subscriptions, .operation_timeout_seconds = options->operation_timeout_seconds, .subscription_status_callback = s_aws_rr_subscription_status_event_test_callback_fn, .userdata = fixture}; @@ -391,6 +394,24 @@ static bool s_contains_subscription_event_sequential_records( return true; } +static char s_hello_world1[] = "hello/world1"; +static struct aws_byte_cursor s_hello_world1_cursor = { + .ptr = (uint8_t *)s_hello_world1, + .len = AWS_ARRAY_SIZE(s_hello_world1) - 1, +}; + +static char s_hello_world2[] = "hello/world2"; +static struct aws_byte_cursor s_hello_world2_cursor = { + .ptr = (uint8_t *)s_hello_world2, + .len = AWS_ARRAY_SIZE(s_hello_world2) - 1, +}; + +static char s_hello_world3[] = "hello/world3"; +static struct aws_byte_cursor s_hello_world3_cursor = { + .ptr = (uint8_t *)s_hello_world3, + .len = AWS_ARRAY_SIZE(s_hello_world3) - 1, +}; + /* * Verify: Acquiring a new subscription triggers a protocol client subscribe and returns SUBSCRIBING */ @@ -405,17 +426,17 @@ static int s_rrsm_acquire_subscribing_fn(struct aws_allocator *allocator, void * struct aws_protocol_adapter_api_record expected_subscribes[] = { { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filter_cursor = s_hello_world2_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world3"), + .topic_filter_cursor = s_hello_world3_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, }; @@ -424,7 +445,8 @@ static int s_rrsm_acquire_subscribing_fn(struct aws_allocator *allocator, void * struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); @@ -432,7 +454,8 @@ static int s_rrsm_acquire_subscribing_fn(struct aws_allocator *allocator, void * struct aws_rr_acquire_subscription_options acquire2_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); @@ -440,7 +463,8 @@ static int s_rrsm_acquire_subscribing_fn(struct aws_allocator *allocator, void * struct aws_rr_acquire_subscription_options acquire3_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world3"), + .topic_filters = &s_hello_world3_cursor, + .topic_filter_count = 1, .operation_id = 3, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); @@ -469,12 +493,12 @@ static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocato struct aws_protocol_adapter_api_record expected_subscribes[] = { { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filter_cursor = s_hello_world2_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, }; @@ -483,7 +507,8 @@ static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocato struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); @@ -491,7 +516,8 @@ static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocato struct aws_rr_acquire_subscription_options acquire2_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); @@ -499,7 +525,8 @@ static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocato struct aws_rr_acquire_subscription_options reacquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 3, }; ASSERT_INT_EQUALS( @@ -508,7 +535,8 @@ static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocato struct aws_rr_acquire_subscription_options reacquire2_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, .operation_id = 4, }; ASSERT_INT_EQUALS( @@ -538,12 +566,12 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator struct aws_protocol_adapter_api_record expected_subscribes[] = { { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filter_cursor = s_hello_world2_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, }; @@ -552,7 +580,8 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); @@ -560,14 +589,15 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator struct aws_rr_acquire_subscription_options acquire2_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); struct aws_protocol_adapter_subscription_event successful_subscription1_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_SUBSCRIBE, .error_code = AWS_ERROR_SUCCESS, }; @@ -576,18 +606,18 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator struct aws_subscription_status_record expected_subscription_events[] = { { .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }, { .type = ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filter_cursor = s_hello_world2_cursor, .operation_id = 2, }}; ASSERT_TRUE(s_contains_subscription_event_sequential_records(&fixture, 1, expected_subscription_events)); struct aws_protocol_adapter_subscription_event successful_subscription2_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filter = s_hello_world2_cursor, .event_type = AWS_PASET_SUBSCRIBE, .error_code = AWS_ERROR_SUCCESS, }; @@ -597,7 +627,8 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator struct aws_rr_acquire_subscription_options reacquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 3, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBED, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire1_options)); @@ -605,7 +636,8 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator struct aws_rr_acquire_subscription_options reacquire2_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, .operation_id = 4, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBED, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire2_options)); @@ -626,7 +658,8 @@ static int s_do_acquire_blocked_test( struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); @@ -634,7 +667,8 @@ static int s_do_acquire_blocked_test( // no room, but it could potentially free up in the future struct aws_rr_acquire_subscription_options acquire2_options = { .type = subscription_type, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); @@ -651,7 +685,8 @@ static int s_rrsm_acquire_blocked_rr_fn(struct aws_allocator *allocator, void *c aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_subscriptions = 1, + .max_request_response_subscriptions = 1, + .max_streaming_subscriptions = 1, .operation_timeout_seconds = 30, }; struct aws_subscription_manager_test_fixture fixture; @@ -676,21 +711,13 @@ static int s_rrsm_acquire_blocked_eventstream_fn(struct aws_allocator *allocator aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_subscriptions = 2, + .max_request_response_subscriptions = 1, + .max_streaming_subscriptions = 1, .operation_timeout_seconds = 30, }; struct aws_subscription_manager_test_fixture fixture; ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); - struct aws_rr_acquire_subscription_options acquire1_options = { - .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world3"), - .operation_id = 3, - }; - ASSERT_INT_EQUALS( - AASRT_SUBSCRIBING, - aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire1_options)); - ASSERT_SUCCESS(s_do_acquire_blocked_test(&fixture, ARRST_EVENT_STREAM)); s_aws_subscription_manager_test_fixture_clean_up(&fixture); @@ -706,7 +733,8 @@ static int s_do_acquire_no_capacity_test(struct aws_subscription_manager_test_fi struct aws_rr_acquire_subscription_options acquire_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_NO_CAPACITY, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); @@ -723,7 +751,8 @@ static int s_rrsm_acquire_no_capacity_max1_fn(struct aws_allocator *allocator, v aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_subscriptions = 1, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 0, .operation_timeout_seconds = 30, }; struct aws_subscription_manager_test_fixture fixture; @@ -749,20 +778,22 @@ static int s_rrsm_acquire_no_capacity_too_many_event_stream_fn(struct aws_alloca aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_subscriptions = 5, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 4, .operation_timeout_seconds = 30, }; struct aws_subscription_manager_test_fixture fixture; ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); - // N - 1 event stream subscriptions for (size_t i = 0; i < 4; ++i) { char topic_filter_buffer[256]; snprintf(topic_filter_buffer, AWS_ARRAY_SIZE(topic_filter_buffer), "hello/world/%d", (int)(i + 1)); + struct aws_byte_cursor topic_filter_cursor = aws_byte_cursor_from_c_str(topic_filter_buffer); struct aws_rr_acquire_subscription_options acquire_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str(topic_filter_buffer), + .topic_filters = &topic_filter_cursor, + .topic_filter_count = 1, .operation_id = i + 2, }; ASSERT_INT_EQUALS( @@ -794,7 +825,8 @@ static int s_rrsm_acquire_failure_mixed_subscription_types_fn(struct aws_allocat struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS( @@ -803,7 +835,8 @@ static int s_rrsm_acquire_failure_mixed_subscription_types_fn(struct aws_allocat struct aws_rr_acquire_subscription_options acquire2_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS( @@ -819,8 +852,7 @@ static int s_rrsm_acquire_failure_mixed_subscription_types_fn(struct aws_allocat AWS_TEST_CASE(rrsm_acquire_failure_mixed_subscription_types, s_rrsm_acquire_failure_mixed_subscription_types_fn) /* - * Verify: Acquiring an existing, completed request subscription does not trigger a protocol client subscribe and - * returns SUBSCRIBED. Verify request and streaming subscription events are emitted. + * Verify: Acquiring a poisoned streaming subscription results in failure. */ static int s_rrsm_acquire_failure_poisoned_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -834,13 +866,14 @@ static int s_rrsm_acquire_failure_poisoned_fn(struct aws_allocator *allocator, v struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); struct aws_protocol_adapter_subscription_event unretryable_failure_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_SUBSCRIBE, .error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, .retryable = false, @@ -849,14 +882,15 @@ static int s_rrsm_acquire_failure_poisoned_fn(struct aws_allocator *allocator, v struct aws_subscription_status_record expected_subscription_events[] = {{ .type = ARRSET_STREAMING_SUBSCRIPTION_HALTED, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }}; ASSERT_TRUE(s_contains_subscription_event_sequential_records(&fixture, 1, expected_subscription_events)); struct aws_rr_acquire_subscription_options reacquire1_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world1"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 3, }; ASSERT_INT_EQUALS(AASRT_FAILURE, aws_rr_subscription_manager_acquire_subscription(manager, &reacquire1_options)); @@ -885,20 +919,22 @@ static int s_rrsm_release_unsubscribes_request_fn(struct aws_allocator *allocato struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); struct aws_rr_acquire_subscription_options acquire2_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); struct aws_protocol_adapter_subscription_event successful_subscription_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_SUBSCRIBE, .error_code = AWS_ERROR_SUCCESS, }; @@ -908,12 +944,12 @@ static int s_rrsm_release_unsubscribes_request_fn(struct aws_allocator *allocato struct aws_subscription_status_record expected_subscription_events[] = { { .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }, { .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 2, }}; ASSERT_TRUE(s_contains_subscription_event_records(&fixture, 2, expected_subscription_events)); @@ -921,14 +957,15 @@ static int s_rrsm_release_unsubscribes_request_fn(struct aws_allocator *allocato // verify no unsubscribes struct aws_protocol_adapter_api_record expected_unsubscribe = { .type = PAAT_UNSUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }; ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); // release once, verify no unsubscribe struct aws_rr_release_subscription_options release1_options = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; aws_rr_subscription_manager_release_subscription(manager, &release1_options); @@ -936,7 +973,8 @@ static int s_rrsm_release_unsubscribes_request_fn(struct aws_allocator *allocato // release second struct aws_rr_release_subscription_options release2_options = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 2, }; aws_rr_subscription_manager_release_subscription(manager, &release2_options); @@ -971,20 +1009,22 @@ static int s_rrsm_release_unsubscribes_streaming_fn(struct aws_allocator *alloca struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); struct aws_rr_acquire_subscription_options acquire2_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); struct aws_protocol_adapter_subscription_event successful_subscription_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_SUBSCRIBE, .error_code = AWS_ERROR_SUCCESS, }; @@ -994,12 +1034,12 @@ static int s_rrsm_release_unsubscribes_streaming_fn(struct aws_allocator *alloca struct aws_subscription_status_record expected_subscription_events[] = { { .type = ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }, { .type = ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 2, }}; ASSERT_TRUE(s_contains_subscription_event_records(&fixture, 2, expected_subscription_events)); @@ -1007,14 +1047,15 @@ static int s_rrsm_release_unsubscribes_streaming_fn(struct aws_allocator *alloca // verify no unsubscribes struct aws_protocol_adapter_api_record expected_unsubscribe = { .type = PAAT_UNSUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }; ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); // release once, verify no unsubscribe struct aws_rr_release_subscription_options release1_options = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; aws_rr_subscription_manager_release_subscription(manager, &release1_options); @@ -1022,7 +1063,8 @@ static int s_rrsm_release_unsubscribes_streaming_fn(struct aws_allocator *alloca // release second struct aws_rr_release_subscription_options release2_options = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 2, }; aws_rr_subscription_manager_release_subscription(manager, &release2_options); @@ -1045,7 +1087,7 @@ static int s_rrsm_do_unsubscribe_test(struct aws_allocator *allocator, bool shou aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_subscriptions = 1, + .max_request_response_subscriptions = 2, .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, .start_connected = true, }; @@ -1057,33 +1099,44 @@ static int s_rrsm_do_unsubscribe_test(struct aws_allocator *allocator, bool shou struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); - // budget of 1, so new acquires should be blocked struct aws_rr_acquire_subscription_options acquire2_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, .operation_id = 2, }; - ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + + // budget of 2, so new acquires should be blocked + struct aws_rr_acquire_subscription_options acquire3_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = &s_hello_world3_cursor, + .topic_filter_count = 1, + .operation_id = 3, + }; + ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); // complete the subscribe struct aws_protocol_adapter_subscription_event successful_subscription_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_SUBSCRIBE, .error_code = AWS_ERROR_SUCCESS, }; aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription_event); // still blocked - ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); // release struct aws_rr_release_subscription_options release1_options = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; aws_rr_subscription_manager_release_subscription(manager, &release1_options); @@ -1092,14 +1145,14 @@ static int s_rrsm_do_unsubscribe_test(struct aws_allocator *allocator, bool shou aws_rr_subscription_manager_purge_unused(manager); struct aws_protocol_adapter_api_record expected_unsubscribe = { .type = PAAT_UNSUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }; ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); // complete the unsubscribe struct aws_protocol_adapter_subscription_event successful_unsubscribe_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_UNSUBSCRIBE, .error_code = should_succeed ? AWS_ERROR_SUCCESS : AWS_ERROR_MQTT5_UNSUBSCRIBE_OPTIONS_VALIDATION, }; @@ -1110,7 +1163,7 @@ static int s_rrsm_do_unsubscribe_test(struct aws_allocator *allocator, bool shou // a successful unsubscribe should clear space, a failed one should not ASSERT_INT_EQUALS( (should_succeed ? AASRT_SUBSCRIBING : AASRT_BLOCKED), - aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + aws_rr_subscription_manager_acquire_subscription(manager, &acquire3_options)); s_aws_subscription_manager_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -1167,7 +1220,8 @@ static int s_rrsm_acquire_failure_subscribe_sync_failure_request_fn(struct aws_a failing_vtable.aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_mock_subscribe_fails_first_time; struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_subscriptions = 3, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, .start_connected = true, .adapter_vtable = &failing_vtable, @@ -1180,7 +1234,8 @@ static int s_rrsm_acquire_failure_subscribe_sync_failure_request_fn(struct aws_a struct aws_rr_acquire_subscription_options acquire_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_FAILURE, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); @@ -1208,7 +1263,8 @@ static int s_rrsm_acquire_failure_subscribe_sync_failure_streaming_fn(struct aws failing_vtable.aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_mock_subscribe_fails_first_time; struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_subscriptions = 3, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, .start_connected = true, .adapter_vtable = &failing_vtable, @@ -1221,14 +1277,16 @@ static int s_rrsm_acquire_failure_subscribe_sync_failure_streaming_fn(struct aws struct aws_rr_acquire_subscription_options acquire_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_FAILURE, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); struct aws_rr_acquire_subscription_options acquire_options2 = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS(AASRT_FAILURE, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options2)); @@ -1258,14 +1316,15 @@ static int s_rrsm_acquire_request_subscribe_failure_event_fn(struct aws_allocato struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); // complete the subscribe with a failure struct aws_protocol_adapter_subscription_event failed_subscription_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_SUBSCRIBE, .error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, }; @@ -1274,7 +1333,7 @@ static int s_rrsm_acquire_request_subscribe_failure_event_fn(struct aws_allocato // verify subscribe failure event emission struct aws_subscription_status_record expected_subscription_event = { .type = ARRSET_REQUEST_SUBSCRIBE_FAILURE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }; @@ -1305,14 +1364,15 @@ static int s_rrsm_acquire_streaming_subscribe_failure_retryable_resubscribe_fn( struct aws_rr_acquire_subscription_options acquire1_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); // complete the subscribe with a retryable failure struct aws_protocol_adapter_subscription_event failed_subscription_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_SUBSCRIBE, .error_code = AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, .retryable = true, @@ -1322,12 +1382,12 @@ static int s_rrsm_acquire_streaming_subscribe_failure_retryable_resubscribe_fn( struct aws_protocol_adapter_api_record expected_subscribes[] = { { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, }; @@ -1370,7 +1430,8 @@ static int s_do_offline_acquire_online_test( aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_subscriptions = 3, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, .start_connected = false, }; @@ -1382,7 +1443,8 @@ static int s_do_offline_acquire_online_test( struct aws_rr_acquire_subscription_options acquire_options = { .type = subscription_type, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); @@ -1400,7 +1462,7 @@ static int s_do_offline_acquire_online_test( struct aws_protocol_adapter_api_record expected_subscribes[] = { { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, }; @@ -1409,14 +1471,14 @@ static int s_do_offline_acquire_online_test( // complete the subscribe, verify a subscription success/failure event struct aws_protocol_adapter_subscription_event subscription_event = { .event_type = AWS_PASET_SUBSCRIBE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .error_code = success ? AWS_ERROR_SUCCESS : AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, }; aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { .type = s_compute_expected_subscription_event_offline_acquire_online(subscription_type, success), - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }; ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); @@ -1494,7 +1556,8 @@ static int s_do_offline_acquire_release_online_test( // acquire struct aws_rr_acquire_subscription_options acquire_options = { .type = subscription_type, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); @@ -1504,7 +1567,8 @@ static int s_do_offline_acquire_release_online_test( // release while offline, nothing should happen struct aws_rr_release_subscription_options release_options = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; aws_rr_subscription_manager_release_subscription(manager, &release_options); @@ -1520,7 +1584,8 @@ static int s_do_offline_acquire_release_online_test( // trigger a different subscription, verify it's the only thing that has reached the protocol adapter struct aws_rr_acquire_subscription_options acquire2_options = { .type = subscription_type, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); @@ -1528,7 +1593,7 @@ static int s_do_offline_acquire_release_online_test( struct aws_protocol_adapter_api_record expected_subscribes[] = { { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filter_cursor = s_hello_world2_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, }; @@ -1578,7 +1643,8 @@ static int s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test( // acquire struct aws_rr_acquire_subscription_options acquire_options = { .type = subscription_type, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); @@ -1586,7 +1652,7 @@ static int s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test( // successfully complete subscription struct aws_protocol_adapter_subscription_event subscription_event = { .event_type = AWS_PASET_SUBSCRIBE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .error_code = AWS_ERROR_SUCCESS, }; aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); @@ -1594,7 +1660,7 @@ static int s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test( struct aws_subscription_status_record expected_subscription_event = { .type = subscription_type == ARRST_REQUEST_RESPONSE ? ARRSET_REQUEST_SUBSCRIBE_SUCCESS : ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }; ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); @@ -1607,7 +1673,8 @@ static int s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test( // release struct aws_rr_release_subscription_options release_options = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; aws_rr_subscription_manager_release_subscription(manager, &release_options); @@ -1615,7 +1682,8 @@ static int s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test( // acquire something different, normally that triggers an unsubscribe, but we're offline struct aws_rr_acquire_subscription_options acquire2_options = { .type = subscription_type, - .topic_filter = aws_byte_cursor_from_c_str("hello/world2"), + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, .operation_id = 2, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); @@ -1623,7 +1691,7 @@ static int s_do_acquire_success_offline_release_acquire2_no_unsubscribe_test( // verify no unsubscribe has been sent struct aws_protocol_adapter_api_record expected_unsubscribe = { .type = PAAT_UNSUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }; ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); @@ -1689,7 +1757,8 @@ static int s_do_rrsm_acquire_clean_up_test( // acquire struct aws_rr_acquire_subscription_options acquire_options = { .type = subscription_type, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); @@ -1698,7 +1767,7 @@ static int s_do_rrsm_acquire_clean_up_test( if (complete_subscribe) { struct aws_protocol_adapter_subscription_event subscription_event = { .event_type = AWS_PASET_SUBSCRIBE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .error_code = AWS_ERROR_SUCCESS, }; aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); @@ -1706,7 +1775,7 @@ static int s_do_rrsm_acquire_clean_up_test( struct aws_subscription_status_record expected_subscription_event = { .type = subscription_type == ARRST_REQUEST_RESPONSE ? ARRSET_REQUEST_SUBSCRIBE_SUCCESS : ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }; ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); @@ -1726,7 +1795,7 @@ static int s_do_rrsm_acquire_clean_up_test( // verify an unsubscribe was sent even though we are offline struct aws_protocol_adapter_api_record expected_unsubscribe = { .type = PAAT_UNSUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }; ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); @@ -1734,7 +1803,8 @@ static int s_do_rrsm_acquire_clean_up_test( // prevent the fixture cleanup from double freeing the subscription manager that was already cleaned up by // reinitializing the subscription manager struct aws_rr_subscription_manager_options subscription_manager_options = { - .max_subscriptions = 3, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, .operation_timeout_seconds = 5, .subscription_status_callback = s_aws_rr_subscription_status_event_test_callback_fn, .userdata = &fixture, @@ -1847,7 +1917,8 @@ static int s_rrsm_do_no_session_subscription_ended_test( // acquire struct aws_rr_acquire_subscription_options acquire_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); @@ -1855,14 +1926,14 @@ static int s_rrsm_do_no_session_subscription_ended_test( // successfully complete subscription struct aws_protocol_adapter_subscription_event subscription_event = { .event_type = AWS_PASET_SUBSCRIBE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .error_code = AWS_ERROR_SUCCESS, }; aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }; ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); @@ -1870,14 +1941,15 @@ static int s_rrsm_do_no_session_subscription_ended_test( // release if appropriate if (offline_while_unsubscribing) { struct aws_rr_release_subscription_options release_options = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; aws_rr_subscription_manager_release_subscription(manager, &release_options); struct aws_protocol_adapter_api_record expected_unsubscribe = { .type = PAAT_UNSUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }; ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); @@ -1906,7 +1978,7 @@ static int s_rrsm_do_no_session_subscription_ended_test( if (!offline_while_unsubscribing) { struct aws_subscription_status_record expected_subscription_ended_event = { .type = ARRSET_REQUEST_SUBSCRIPTION_ENDED, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }; ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_ended_event)); @@ -1915,7 +1987,8 @@ static int s_rrsm_do_no_session_subscription_ended_test( // if we were unsubscribing, verify reacquire is blocked and then complete the unsubscribe struct aws_rr_acquire_subscription_options reacquire_options = { .type = ARRST_REQUEST_RESPONSE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 3, }; @@ -1924,7 +1997,7 @@ static int s_rrsm_do_no_session_subscription_ended_test( struct aws_protocol_adapter_subscription_event unsubscribe_event = { .event_type = AWS_PASET_UNSUBSCRIBE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .error_code = AWS_ERROR_SUCCESS, }; aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &unsubscribe_event); @@ -1986,7 +2059,8 @@ static int s_rrsm_streaming_subscription_lost_resubscribe_on_no_session_fn(struc // acquire struct aws_rr_acquire_subscription_options acquire_options = { .type = ARRST_EVENT_STREAM, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); @@ -1994,14 +2068,14 @@ static int s_rrsm_streaming_subscription_lost_resubscribe_on_no_session_fn(struc // successfully complete subscription struct aws_protocol_adapter_subscription_event subscription_event = { .event_type = AWS_PASET_SUBSCRIBE, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .error_code = AWS_ERROR_SUCCESS, }; aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &subscription_event); struct aws_subscription_status_record expected_subscription_event = { .type = ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }; ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_event)); @@ -2022,7 +2096,7 @@ static int s_rrsm_streaming_subscription_lost_resubscribe_on_no_session_fn(struc // verify subscription lost on rejoin struct aws_subscription_status_record expected_subscription_ended_event = { .type = ARRSET_STREAMING_SUBSCRIPTION_LOST, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 1, }; ASSERT_TRUE(s_contains_subscription_event_record(&fixture, &expected_subscription_ended_event)); @@ -2031,12 +2105,12 @@ static int s_rrsm_streaming_subscription_lost_resubscribe_on_no_session_fn(struc struct aws_protocol_adapter_api_record expected_subscribes[] = { { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, { .type = PAAT_SUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }, }; @@ -2064,13 +2138,14 @@ static int s_do_purge_test(struct aws_allocator *allocator, enum aws_rr_subscrip struct aws_rr_acquire_subscription_options acquire1_options = { .type = subscription_type, - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); struct aws_protocol_adapter_subscription_event successful_subscription_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_SUBSCRIBE, .error_code = AWS_ERROR_SUCCESS, }; @@ -2079,7 +2154,7 @@ static int s_do_purge_test(struct aws_allocator *allocator, enum aws_rr_subscrip struct aws_subscription_status_record expected_empty_subscription_events[] = { { .type = ARRSET_SUBSCRIPTION_EMPTY, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 0, }, }; @@ -2087,7 +2162,7 @@ static int s_do_purge_test(struct aws_allocator *allocator, enum aws_rr_subscrip struct aws_subscription_status_record expected_unsubscribe_events[] = { { .type = ARRSET_UNSUBSCRIBE_COMPLETE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .operation_id = 0, }, }; @@ -2098,14 +2173,15 @@ static int s_do_purge_test(struct aws_allocator *allocator, enum aws_rr_subscrip // verify no unsubscribes struct aws_protocol_adapter_api_record expected_unsubscribe = { .type = PAAT_UNSUBSCRIBE, - .topic_filter_cursor = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter_cursor = s_hello_world1_cursor, .timeout = DEFAULT_SM_TEST_TIMEOUT, }; ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); // release once, verify no unsubscribe struct aws_rr_release_subscription_options release1_options = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, .operation_id = 1, }; aws_rr_subscription_manager_release_subscription(manager, &release1_options); @@ -2123,7 +2199,7 @@ static int s_do_purge_test(struct aws_allocator *allocator, enum aws_rr_subscrip // complete the unsubscribe struct aws_protocol_adapter_subscription_event successful_unsubscribe_event = { - .topic_filter = aws_byte_cursor_from_c_str("hello/world"), + .topic_filter = s_hello_world1_cursor, .event_type = AWS_PASET_UNSUBSCRIBE, .error_code = AWS_ERROR_SUCCESS, }; From 53835c52ba6a086506bd42c441f2489dbde01a82 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 12 Apr 2024 14:23:20 -0700 Subject: [PATCH 105/124] Tests passing --- .../request_response_client.c | 3 + .../request-response/subscription_manager.c | 22 ++-- .../request_response_client_tests.c | 49 ++++++-- .../subscription_manager_tests.c | 111 +++++++++++++----- 4 files changed, 133 insertions(+), 52 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index c56f3d64..b4361a5e 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -1825,6 +1825,7 @@ static void s_aws_mqtt_streaming_operation_storage_clean_up(struct aws_mqtt_stre static void s_aws_mqtt_request_operation_storage_clean_up(struct aws_mqtt_request_operation_storage *storage) { aws_array_list_clean_up(&storage->operation_response_paths); + aws_array_list_clean_up(&storage->subscription_topic_filters); aws_byte_buf_clean_up(&storage->operation_data); } @@ -2018,6 +2019,8 @@ void s_aws_mqtt_request_operation_storage_init_from_options( aws_array_list_push_back(&storage->subscription_topic_filters, &subscription_topic_filter); } + storage->options.subscription_topic_filters = storage->subscription_topic_filters.data; + for (size_t i = 0; i < request_options->response_path_count; ++i) { struct aws_mqtt_request_operation_response_path response_path = request_options->response_paths[i]; diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 0430cb66..522f9738 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -455,16 +455,18 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su for (size_t i = 0; i < options->topic_filter_count; ++i) { struct aws_byte_cursor topic_filter = options->topic_filters[i]; struct aws_rr_subscription_record *existing_record = s_get_subscription_record(manager, topic_filter); - if (existing_record != NULL) { - if (existing_record->poisoned) { - AWS_LOGF_ERROR( - AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - acquire subscription for ('" PRInSTR - "'), operation %" PRIu64 " failed - existing subscription is poisoned and has not been released", - AWS_BYTE_CURSOR_PRI(topic_filter), - options->operation_id); - return AASRT_FAILURE; - } + if (existing_record == NULL) { + continue; + } + + if (existing_record->poisoned) { + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire subscription for ('" PRInSTR "'), operation %" PRIu64 + " failed - existing subscription is poisoned and has not been released", + AWS_BYTE_CURSOR_PRI(topic_filter), + options->operation_id); + return AASRT_FAILURE; } if (existing_record->type != options->type) { diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index c1fe00e8..c7c43b72 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -309,7 +309,7 @@ static struct aws_rr_client_fixture_streaming_record *s_rrc_fixture_add_streamin struct aws_rr_client_fixture_streaming_record *record = s_aws_rr_client_fixture_streaming_record_new(fixture->allocator, fixture, key); - aws_hash_table_put(&fixture->streaming_records, &record->record_key, record, NULL); + aws_hash_table_put(&fixture->streaming_records, &record->record_key_cursor, record, NULL); return record; } @@ -539,7 +539,8 @@ static int s_aws_rr_client_test_fixture_init_from_mqtt5( } struct aws_mqtt_request_response_client_options client_options = { - .max_subscriptions = 3, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, .operation_timeout_seconds = 5, }; @@ -597,7 +598,8 @@ static int s_aws_rr_client_test_fixture_init_from_mqtt311( aws_test311_setup_mqtt_server_fn(allocator, &fixture->client_test_fixture.mqtt311_test_fixture); struct aws_mqtt_request_response_client_options client_options = { - .max_subscriptions = 3, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, .operation_timeout_seconds = 5, }; @@ -707,6 +709,12 @@ static int s_rrc_mqtt311_create_destroy_fn(struct aws_allocator *allocator, void AWS_TEST_CASE(rrc_mqtt311_create_destroy, s_rrc_mqtt311_create_destroy_fn) +static char s_response_filter_wildcard[] = "response/filter/+"; +static struct aws_byte_cursor s_response_filter_wildcard_cursor = { + .ptr = (uint8_t *)s_response_filter_wildcard, + .len = AWS_ARRAY_SIZE(s_response_filter_wildcard) - 1, +}; + static int s_rrc_do_submit_request_operation_failure_test( struct aws_allocator *allocator, void (*request_mutator_fn)(struct aws_mqtt_request_operation_options *)) { @@ -735,7 +743,8 @@ static int s_rrc_do_submit_request_operation_failure_test( }, }; struct aws_mqtt_request_operation_options good_request = { - .subscription_topic_filter = aws_byte_cursor_from_c_str("response/filter/+"), + .subscription_topic_filters = &s_response_filter_wildcard_cursor, + .subscription_topic_filter_count = 1, .response_paths = response_paths, .response_path_count = AWS_ARRAY_SIZE(response_paths), .publish_topic = aws_byte_cursor_from_c_str("get/shadow"), @@ -801,8 +810,14 @@ AWS_TEST_CASE( rrc_submit_request_operation_failure_invalid_publish_topic, s_rrc_submit_request_operation_failure_invalid_publish_topic_fn) +static char s_bad_filter[] = "a/#/c"; +static struct aws_byte_cursor s_bad_filter_cursor = { + .ptr = (uint8_t *)s_bad_filter, + .len = AWS_ARRAY_SIZE(s_bad_filter) - 1, +}; + static void s_invalid_subscription_topic_filter_mutator(struct aws_mqtt_request_operation_options *request_options) { - request_options->subscription_topic_filter = aws_byte_cursor_from_c_str("a/#/c"); + request_options->subscription_topic_filters = &s_bad_filter_cursor; } static int s_rrc_submit_request_operation_failure_invalid_subscription_topic_filter_fn( @@ -936,7 +951,8 @@ static int s_rrc_submit_request_operation_failure_by_shutdown_fn(struct aws_allo }; struct aws_mqtt_request_operation_options request = { - .subscription_topic_filter = aws_byte_cursor_from_c_str("response/filter/+"), + .subscription_topic_filters = &s_response_filter_wildcard_cursor, + .subscription_topic_filter_count = 1, .response_paths = response_paths, .response_path_count = AWS_ARRAY_SIZE(response_paths), .publish_topic = aws_byte_cursor_from_c_str("get/shadow"), @@ -1047,7 +1063,8 @@ static int s_rrc_submit_request_operation_failure_by_timeout_fn(struct aws_alloc }; struct aws_mqtt_request_operation_options request = { - .subscription_topic_filter = aws_byte_cursor_from_c_str("response/filter/+"), + .subscription_topic_filters = &s_response_filter_wildcard_cursor, + .subscription_topic_filter_count = 1, .response_paths = response_paths, .response_path_count = AWS_ARRAY_SIZE(response_paths), .publish_topic = aws_byte_cursor_from_c_str("get/shadow"), @@ -1056,7 +1073,8 @@ static int s_rrc_submit_request_operation_failure_by_timeout_fn(struct aws_alloc }; struct aws_mqtt_request_response_client_options rr_client_options = { - .max_subscriptions = 2, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 1, .operation_timeout_seconds = 2, }; @@ -1143,7 +1161,8 @@ static int s_init_fixture_streaming_operation_success( }; struct aws_mqtt_request_response_client_options rr_client_options = { - .max_subscriptions = 2, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 1, .operation_timeout_seconds = 2, }; @@ -1981,6 +2000,8 @@ static int s_submit_request_operation_from_prefix( AWS_BYTE_CURSOR_PRI(prefix)); snprintf(publish_topic, AWS_ARRAY_SIZE(publish_topic), PRInSTR "/get", AWS_BYTE_CURSOR_PRI(prefix)); + struct aws_byte_cursor subscription_topic_filter_cursor = aws_byte_cursor_from_c_str(subscription_topic_filter); + char correlation_token[128]; struct aws_byte_buf correlation_token_buf = aws_byte_buf_from_empty_array(correlation_token, AWS_ARRAY_SIZE(correlation_token)); @@ -2004,7 +2025,8 @@ static int s_submit_request_operation_from_prefix( s_rrc_fixture_add_request_record(fixture, record_key); struct aws_mqtt_request_operation_options request = { - .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .subscription_topic_filters = &subscription_topic_filter_cursor, + .subscription_topic_filter_count = 1, .response_paths = response_paths, .response_path_count = AWS_ARRAY_SIZE(response_paths), .publish_topic = aws_byte_cursor_from_c_str(publish_topic), @@ -2345,7 +2367,8 @@ static int s_init_fixture_request_operation_success( }; struct aws_mqtt_request_response_client_options rr_client_options = { - .max_subscriptions = 2, + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, .operation_timeout_seconds = 2, }; @@ -2391,6 +2414,7 @@ static int s_rrc_test_submit_test_request( char subscription_buffer[128]; snprintf(subscription_buffer, AWS_ARRAY_SIZE(subscription_buffer), "%s/+", topic_prefix); + struct aws_byte_cursor subscription_buffer_cursor = aws_byte_cursor_from_c_str(subscription_buffer); char publish_topic_buffer[128]; snprintf(publish_topic_buffer, AWS_ARRAY_SIZE(publish_topic_buffer), "%s/publish", topic_prefix); @@ -2419,7 +2443,8 @@ static int s_rrc_test_submit_test_request( snprintf(request_buffer + used_bytes, AWS_ARRAY_SIZE(request_buffer) - used_bytes, "}"); struct aws_mqtt_request_operation_options request = { - .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_buffer), + .subscription_topic_filters = &subscription_buffer_cursor, + .subscription_topic_filter_count = 1, .response_paths = response_paths, .response_path_count = AWS_ARRAY_SIZE(response_paths), .publish_topic = aws_byte_cursor_from_c_str(publish_topic_buffer), diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 099b1d1a..939d0d5e 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -651,31 +651,6 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator AWS_TEST_CASE(rrsm_acquire_existing_subscribed, s_rrsm_acquire_existing_subscribed_fn) -static int s_do_acquire_blocked_test( - struct aws_subscription_manager_test_fixture *fixture, - enum aws_rr_subscription_type subscription_type) { - struct aws_rr_subscription_manager *manager = &fixture->subscription_manager; - - struct aws_rr_acquire_subscription_options acquire1_options = { - .type = ARRST_REQUEST_RESPONSE, - .topic_filters = &s_hello_world1_cursor, - .topic_filter_count = 1, - .operation_id = 1, - }; - ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); - - // no room, but it could potentially free up in the future - struct aws_rr_acquire_subscription_options acquire2_options = { - .type = subscription_type, - .topic_filters = &s_hello_world2_cursor, - .topic_filter_count = 1, - .operation_id = 2, - }; - ASSERT_INT_EQUALS(AASRT_BLOCKED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); - - return AWS_OP_SUCCESS; -} - /* * Verify: Acquiring a new request-response subscription when there is no room returns BLOCKED */ @@ -685,14 +660,43 @@ static int s_rrsm_acquire_blocked_rr_fn(struct aws_allocator *allocator, void *c aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_request_response_subscriptions = 1, + .max_request_response_subscriptions = 2, .max_streaming_subscriptions = 1, .operation_timeout_seconds = 30, }; struct aws_subscription_manager_test_fixture fixture; ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); - ASSERT_SUCCESS(s_do_acquire_blocked_test(&fixture, ARRST_REQUEST_RESPONSE)); + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, + .operation_id = 1, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire1_options)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, + .operation_id = 2, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire2_options)); + + // no room, but it could potentially free up in the future + struct aws_rr_acquire_subscription_options acquire3_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = &s_hello_world3_cursor, + .topic_filter_count = 1, + .operation_id = 3, + }; + ASSERT_INT_EQUALS( + AASRT_BLOCKED, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire3_options)); s_aws_subscription_manager_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); @@ -711,14 +715,61 @@ static int s_rrsm_acquire_blocked_eventstream_fn(struct aws_allocator *allocator aws_mqtt_library_init(allocator); struct aws_subscription_manager_test_fixture_options fixture_config = { - .max_request_response_subscriptions = 1, + .max_request_response_subscriptions = 2, .max_streaming_subscriptions = 1, - .operation_timeout_seconds = 30, + .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, }; struct aws_subscription_manager_test_fixture fixture; ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); - ASSERT_SUCCESS(s_do_acquire_blocked_test(&fixture, ARRST_EVENT_STREAM)); + struct aws_protocol_adapter_connection_event connected_event = { + .event_type = AWS_PACET_CONNECTED, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(&fixture.subscription_manager, &connected_event); + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_EVENT_STREAM, + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, + .operation_id = 1, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire1_options)); + + struct aws_protocol_adapter_subscription_event subscribe_success_event = { + .event_type = AWS_PASET_SUBSCRIBE, + .topic_filter = s_hello_world1_cursor, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event( + &fixture.subscription_manager, &subscribe_success_event); + + // release and trigger an unsubscribe + struct aws_rr_release_subscription_options release1_options = { + .operation_id = 1, + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, + }; + aws_rr_subscription_manager_release_subscription(&fixture.subscription_manager, &release1_options); + aws_rr_subscription_manager_purge_unused(&fixture.subscription_manager); + + struct aws_protocol_adapter_api_record expected_unsubscribe = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = s_hello_world1_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // acquire while the streaming unsubscribe is in-progress should return blocked + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filters = &s_hello_world2_cursor, + .topic_filter_count = 1, + .operation_id = 2, + }; + ASSERT_INT_EQUALS( + AASRT_BLOCKED, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire2_options)); s_aws_subscription_manager_test_fixture_clean_up(&fixture); aws_mqtt_library_clean_up(); From cb777b70f611221023d7864bb01fe2272eabc45a Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 12 Apr 2024 14:43:24 -0700 Subject: [PATCH 106/124] Finalize support for multi-subscription request operations --- source/request-response/request_response_client.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index b4361a5e..bc86347d 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -260,6 +260,8 @@ struct aws_mqtt_rr_client_operation { enum aws_mqtt_request_response_operation_state state; + size_t pending_subscriptions; + bool in_client_tables; struct aws_task submit_task; @@ -721,8 +723,11 @@ static void s_on_request_operation_subscription_status_event( case ARRSET_REQUEST_SUBSCRIBE_SUCCESS: if (operation->state == AWS_MRROS_PENDING_SUBSCRIPTION) { - s_change_operation_state(operation, AWS_MRROS_PENDING_RESPONSE); - s_make_mqtt_request(operation->client_internal_ref, operation); + --operation->pending_subscriptions; + if (operation->pending_subscriptions == 0) { + s_change_operation_state(operation, AWS_MRROS_PENDING_RESPONSE); + s_make_mqtt_request(operation->client_internal_ref, operation); + } } break; @@ -2139,6 +2144,7 @@ int aws_mqtt_request_response_client_submit_request( operation->timeout_timepoint_ns = now + aws_timestamp_convert(client->config.operation_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL); + operation->pending_subscriptions = request_options->subscription_topic_filter_count; s_aws_mqtt_request_operation_storage_init_from_options( &operation->storage.request_storage, allocator, request_options); @@ -2211,6 +2217,7 @@ struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_str operation->allocator = allocator; operation->type = AWS_MRROT_STREAMING; operation->timeout_timepoint_ns = UINT64_MAX; + operation->pending_subscriptions = 1; s_aws_mqtt_streaming_operation_storage_init_from_options( &operation->storage.streaming_storage, allocator, streaming_options); From 9ab9fd65cdb78db3aab0a0b5214f94e1c3a062e9 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sat, 13 Apr 2024 16:14:57 -0700 Subject: [PATCH 107/124] Finish multi sub tests --- tests/CMakeLists.txt | 14 + .../request_response_client_tests.c | 257 +++++++- .../subscription_manager_tests.c | 597 +++++++++++++++++- 3 files changed, 853 insertions(+), 15 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0dc0a3f8..3fe1c582 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -485,16 +485,25 @@ add_test_case(mqtt311_listener_publish_event) # these tests tend to have longer operation sequences and checks in their names # more details about each test can be found at the top of each test function add_test_case(rrsm_acquire_subscribing) +add_test_case(rrsm_acquire_multi_subscribing) add_test_case(rrsm_acquire_existing_subscribing) +add_test_case(rrsm_acquire_multi_existing_subscribing) +add_test_case(rrsm_acquire_multi_partially_subscribed) add_test_case(rrsm_acquire_existing_subscribed) +add_test_case(rrsm_acquire_multi_existing_subscribed) add_test_case(rrsm_acquire_blocked_rr) +add_test_case(rrsm_acquire_multi_blocked_rr) add_test_case(rrsm_acquire_blocked_eventstream) +add_test_case(rrsm_acquire_multi_blocked_eventstream) add_test_case(rrsm_acquire_no_capacity_max1) add_test_case(rrsm_acquire_no_capacity_too_many_event_stream) +add_test_case(rrsm_acquire_multi_no_capacity_event_stream) add_test_case(rrsm_acquire_failure_mixed_subscription_types) +add_test_case(rrsm_acquire_multi_failure_mixed_subscription_types) add_test_case(rrsm_acquire_failure_poisoned) add_test_case(rrsm_release_unsubscribes_request) add_test_case(rrsm_release_unsubscribes_streaming) +add_test_case(rrsm_release_multi_unsubscribes_request) add_test_case(rrsm_release_unsubscribe_success_clears_space) add_test_case(rrsm_release_unsubscribe_failure_blocked) add_test_case(rrsm_acquire_failure_subscribe_sync_failure_request) @@ -529,6 +538,7 @@ add_test_case(rrc_submit_request_operation_failure_no_response_paths) add_test_case(rrc_submit_request_operation_failure_invalid_response_topic) add_test_case(rrc_submit_request_operation_failure_invalid_publish_topic) add_test_case(rrc_submit_request_operation_failure_invalid_subscription_topic_filter) +add_test_case(rrc_submit_request_operation_failure_no_subscription_topic_filters) add_test_case(rrc_submit_request_operation_failure_empty_request) add_test_case(rrc_submit_streaming_operation_failure_invalid_subscription_topic_filter) @@ -549,9 +559,13 @@ add_test_case(rrc_streaming_operation_success_delayed_by_request_operations) add_test_case(rrc_streaming_operation_success_sandwiched_by_request_operations) add_test_case(rrc_request_response_success_response_path_accepted) +add_test_case(rrc_request_response_multi_sub_success_response_path_accepted) add_test_case(rrc_request_response_success_response_path_rejected) +add_test_case(rrc_request_response_multi_sub_success_response_path_rejected) add_test_case(rrc_request_response_success_empty_correlation_token) add_test_case(rrc_request_response_success_empty_correlation_token_sequence) +add_test_case(rrc_request_response_subscribe_failure) +add_test_case(rrc_request_response_multi_subscribe_failure) add_test_case(rrc_request_response_failure_puback_reason_code) add_test_case(rrc_request_response_failure_invalid_payload) add_test_case(rrc_request_response_failure_missing_correlation_token) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index c7c43b72..dd28c65e 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -832,6 +832,22 @@ AWS_TEST_CASE( rrc_submit_request_operation_failure_invalid_subscription_topic_filter, s_rrc_submit_request_operation_failure_invalid_subscription_topic_filter_fn) +static void s_no_subscription_topic_filter_mutator(struct aws_mqtt_request_operation_options *request_options) { + request_options->subscription_topic_filter_count = 0; +} + +static int s_rrc_submit_request_operation_failure_no_subscription_topic_filters_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + return s_rrc_do_submit_request_operation_failure_test(allocator, s_no_subscription_topic_filter_mutator); +} + +AWS_TEST_CASE( + rrc_submit_request_operation_failure_no_subscription_topic_filters, + s_rrc_submit_request_operation_failure_no_subscription_topic_filters_fn) + static void s_empty_request_mutator(struct aws_mqtt_request_operation_options *request_options) { request_options->serialized_request = aws_byte_cursor_from_c_str(""); } @@ -2389,7 +2405,8 @@ static int s_rrc_test_submit_test_request( struct aws_byte_cursor record_key, const char *response_topic, const char *token, - const char *reflection) { + const char *reflection, + bool is_multi_subscribe) { char path1_buffer[128]; snprintf(path1_buffer, AWS_ARRAY_SIZE(path1_buffer), "%s/accepted", topic_prefix); @@ -2416,6 +2433,18 @@ static int s_rrc_test_submit_test_request( snprintf(subscription_buffer, AWS_ARRAY_SIZE(subscription_buffer), "%s/+", topic_prefix); struct aws_byte_cursor subscription_buffer_cursor = aws_byte_cursor_from_c_str(subscription_buffer); + struct aws_byte_cursor *subscriptions = &subscription_buffer_cursor; + size_t subscription_count = 1; + + struct aws_byte_cursor multi_subs[] = { + aws_byte_cursor_from_c_str(path1_buffer), + aws_byte_cursor_from_c_str(path2_buffer), + }; + if (is_multi_subscribe) { + subscriptions = multi_subs; + subscription_count = 2; + } + char publish_topic_buffer[128]; snprintf(publish_topic_buffer, AWS_ARRAY_SIZE(publish_topic_buffer), "%s/publish", topic_prefix); @@ -2443,8 +2472,8 @@ static int s_rrc_test_submit_test_request( snprintf(request_buffer + used_bytes, AWS_ARRAY_SIZE(request_buffer) - used_bytes, "}"); struct aws_mqtt_request_operation_options request = { - .subscription_topic_filters = &subscription_buffer_cursor, - .subscription_topic_filter_count = 1, + .subscription_topic_filters = subscriptions, + .subscription_topic_filter_count = subscription_count, .response_paths = response_paths, .response_path_count = AWS_ARRAY_SIZE(response_paths), .publish_topic = aws_byte_cursor_from_c_str(publish_topic_buffer), @@ -2472,7 +2501,7 @@ static int s_rrc_request_response_success_response_path_accepted_fn(struct aws_a struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", "token1", NULL)); + &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", "token1", NULL, false)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2492,6 +2521,39 @@ AWS_TEST_CASE( rrc_request_response_success_response_path_accepted, s_rrc_request_response_success_response_path_accepted_fn) +static int s_rrc_request_response_multi_sub_success_response_path_accepted_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", "token1", NULL, true)); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + struct aws_byte_cursor expected_response_topic = aws_byte_cursor_from_c_str("test/accepted"); + struct aws_byte_cursor expected_payload = aws_byte_cursor_from_c_str("{\"token\":\"token1\"}"); + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, record_key, AWS_ERROR_SUCCESS, &expected_response_topic, &expected_payload)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_request_response_multi_sub_success_response_path_accepted, + s_rrc_request_response_multi_sub_success_response_path_accepted_fn) + static int s_rrc_request_response_success_response_path_rejected_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -2503,7 +2565,7 @@ static int s_rrc_request_response_success_response_path_rejected_fn(struct aws_a struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/rejected", "token5", NULL)); + &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/rejected", "token5", NULL, false)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2523,6 +2585,151 @@ AWS_TEST_CASE( rrc_request_response_success_response_path_rejected, s_rrc_request_response_success_response_path_rejected_fn) +static int s_rrc_request_response_multi_sub_success_response_path_rejected_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/rejected", "token5", NULL, true)); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + struct aws_byte_cursor expected_response_topic = aws_byte_cursor_from_c_str("test/rejected"); + struct aws_byte_cursor expected_payload = aws_byte_cursor_from_c_str("{\"token\":\"token5\"}"); + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, record_key, AWS_ERROR_SUCCESS, &expected_response_topic, &expected_payload)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrc_request_response_multi_sub_success_response_path_rejected, + s_rrc_request_response_multi_sub_success_response_path_rejected_fn) + +static void s_fail_all_subscribes_config_modifier_fn( + struct aws_mqtt_request_response_client_options *fixture_options, + struct mqtt5_client_test_options *client_test_options) { + + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + s_handle_subscribe_with_terminal_failure; +} + +static int s_rrc_request_response_subscribe_failure_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + + struct rrc_subscribe_handler_context subscribe_context = { + .fixture = &fixture, + .subscribes_received = 0, + }; + ASSERT_SUCCESS(s_init_fixture_request_operation_success( + &fixture, &client_test_options, allocator, s_fail_all_subscribes_config_modifier_fn, &subscribe_context)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", "token1", NULL, false)); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, record_key, AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE, NULL, NULL)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_request_response_subscribe_failure, s_rrc_request_response_subscribe_failure_fn) + +static enum aws_mqtt5_suback_reason_code s_rrc_usuback_success_rcs[] = { + AWS_MQTT5_SARC_GRANTED_QOS_1, +}; + +int s_handle_second_subscribe_with_failure( + void *packet, + struct aws_mqtt5_server_mock_connection_context *connection, + void *user_data) { + (void)packet; + + struct rrc_subscribe_handler_context *context = user_data; + + size_t subscribes_received = 0; + + aws_mutex_lock(&context->fixture->lock); + ++context->subscribes_received; + subscribes_received = context->subscribes_received; + aws_mutex_unlock(&context->fixture->lock); + + struct aws_mqtt5_packet_subscribe_view *subscribe_packet = packet; + + struct aws_mqtt5_packet_suback_view suback_view = { + .packet_id = subscribe_packet->packet_id, + .reason_code_count = 1, + .reason_codes = (subscribes_received == 1) ? s_rrc_usuback_success_rcs : s_rrc_unretryable_suback_rcs, + }; + + return aws_mqtt5_mock_server_send_packet(connection, AWS_MQTT5_PT_SUBACK, &suback_view); +} + +static void s_fail_second_subscribe_config_modifier_fn( + struct aws_mqtt_request_response_client_options *fixture_options, + struct mqtt5_client_test_options *client_test_options) { + + client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = + s_handle_second_subscribe_with_failure; +} + +static int s_rrc_request_response_multi_subscribe_failure_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct mqtt5_client_test_options client_test_options; + struct aws_rr_client_test_fixture fixture; + + struct rrc_subscribe_handler_context subscribe_context = { + .fixture = &fixture, + .subscribes_received = 0, + }; + ASSERT_SUCCESS(s_init_fixture_request_operation_success( + &fixture, &client_test_options, allocator, s_fail_second_subscribe_config_modifier_fn, &subscribe_context)); + + struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", "token1", NULL, true)); + + s_rrc_wait_on_request_completion(&fixture, record_key); + + ASSERT_SUCCESS(s_rrc_verify_request_completion( + &fixture, record_key, AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE, NULL, NULL)); + + s_aws_rr_client_test_fixture_clean_up(&fixture); + + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrc_request_response_multi_subscribe_failure, s_rrc_request_response_multi_subscribe_failure_fn) + static int s_rrc_request_response_failure_puback_reason_code_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -2534,7 +2741,7 @@ static int s_rrc_request_response_failure_puback_reason_code_fn(struct aws_alloc struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_PUBACK_REASON_CODE, "test", record_key, "test/accepted", "token1", NULL)); + &fixture, RRC_PHDT_FAILURE_PUBACK_REASON_CODE, "test", record_key, "test/accepted", "token1", NULL, false)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2561,7 +2768,7 @@ static int s_rrc_request_response_failure_invalid_payload_fn(struct aws_allocato struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_BAD_PAYLOAD_FORMAT, "test", record_key, "test/accepted", "token1", NULL)); + &fixture, RRC_PHDT_FAILURE_BAD_PAYLOAD_FORMAT, "test", record_key, "test/accepted", "token1", NULL, false)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2588,7 +2795,14 @@ static int s_rrc_request_response_failure_missing_correlation_token_fn(struct aw struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_MISSING_CORRELATION_TOKEN, "test", record_key, "test/accepted", "token1", NULL)); + &fixture, + RRC_PHDT_FAILURE_MISSING_CORRELATION_TOKEN, + "test", + record_key, + "test/accepted", + "token1", + NULL, + false)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2619,7 +2833,14 @@ static int s_rrc_request_response_failure_invalid_correlation_token_type_fn( struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_BAD_CORRELATION_TOKEN_TYPE, "test", record_key, "test/accepted", "token1", NULL)); + &fixture, + RRC_PHDT_FAILURE_BAD_CORRELATION_TOKEN_TYPE, + "test", + record_key, + "test/accepted", + "token1", + NULL, + false)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2650,7 +2871,14 @@ static int s_rrc_request_response_failure_non_matching_correlation_token_fn( struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_FAILURE_MISMATCHED_CORRELATION_TOKEN, "test", record_key, "test/accepted", "token1", NULL)); + &fixture, + RRC_PHDT_FAILURE_MISMATCHED_CORRELATION_TOKEN, + "test", + record_key, + "test/accepted", + "token1", + NULL, + false)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2678,8 +2906,8 @@ static int s_rrc_request_response_success_empty_correlation_token_fn(struct aws_ ASSERT_SUCCESS(s_init_fixture_request_operation_success(&fixture, &client_test_options, allocator, NULL, NULL)); struct aws_byte_cursor record_key = aws_byte_cursor_from_c_str("testkey"); - ASSERT_SUCCESS( - s_rrc_test_submit_test_request(&fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", NULL, NULL)); + ASSERT_SUCCESS(s_rrc_test_submit_test_request( + &fixture, RRC_PHDT_SUCCESS, "test", record_key, "test/accepted", NULL, NULL, false)); s_rrc_wait_on_request_completion(&fixture, record_key); @@ -2722,7 +2950,7 @@ static int s_rrc_request_response_success_empty_correlation_token_sequence_fn( snprintf(response_topic_buffer, AWS_ARRAY_SIZE(response_topic_buffer), "test%zu/accepted", i); ASSERT_SUCCESS(s_rrc_test_submit_test_request( - &fixture, RRC_PHDT_SUCCESS, prefix_buffer, record_key, response_topic_buffer, NULL, NULL)); + &fixture, RRC_PHDT_SUCCESS, prefix_buffer, record_key, response_topic_buffer, NULL, NULL, false)); } for (size_t i = 0; i < 20; ++i) { @@ -2780,7 +3008,8 @@ static int s_do_rrc_operation_sequence_test( record_key, response_topic_buffer, operation->token, - operation->reflection)); + operation->reflection, + false)); } for (size_t i = 0; i < operation_count; ++i) { diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 939d0d5e..443d1b0a 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -478,6 +478,55 @@ static int s_rrsm_acquire_subscribing_fn(struct aws_allocator *allocator, void * AWS_TEST_CASE(rrsm_acquire_subscribing, s_rrsm_acquire_subscribing_fn) +/* + * Verify: Acquiring a new multi-topic-filter subscription triggers two protocol client subscribes and returns + * SUBSCRIBING + */ +static int s_rrsm_acquire_multi_subscribing_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = s_hello_world1_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = s_hello_world2_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_byte_cursor multi_filters[] = { + s_hello_world1_cursor, + s_hello_world2_cursor, + }; + + struct aws_rr_acquire_subscription_options acquire_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = multi_filters, + .topic_filter_count = AWS_ARRAY_SIZE(multi_filters), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_multi_subscribing, s_rrsm_acquire_multi_subscribing_fn) + /* * Verify: Acquiring an existing, incomplete subscription does not trigger a protocol client subscribe and returns * SUBSCRIBING @@ -551,6 +600,122 @@ static int s_rrsm_acquire_existing_subscribing_fn(struct aws_allocator *allocato AWS_TEST_CASE(rrsm_acquire_existing_subscribing, s_rrsm_acquire_existing_subscribing_fn) +/* + * Verify: Acquiring multiple existing, incomplete subscriptions does not trigger a protocol client subscribe and + * returns SUBSCRIBING + */ +static int s_rrsm_acquire_multi_existing_subscribing_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = s_hello_world1_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = s_hello_world2_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_byte_cursor multi_subs[] = { + s_hello_world1_cursor, + s_hello_world2_cursor, + }; + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + ASSERT_TRUE( + s_api_records_equals(fixture.mock_protocol_adapter, AWS_ARRAY_SIZE(expected_subscribes), expected_subscribes)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_multi_existing_subscribing, s_rrsm_acquire_multi_existing_subscribing_fn) + +/* + * Verify: A two-subscription acquire where one is already subscribing triggers a single subscribe and returns + * SUBSCRIBING + */ +static int s_rrsm_acquire_multi_partially_subscribed_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = s_hello_world1_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = s_hello_world2_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_byte_cursor multi_subs[] = { + s_hello_world1_cursor, + s_hello_world2_cursor, + }; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 1, expected_subscribes)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_multi_partially_subscribed, s_rrsm_acquire_multi_partially_subscribed_fn) + /* * Verify: Acquiring an existing, completed request subscription does not trigger a protocol client subscribe and * returns SUBSCRIBED. Verify request and streaming subscription events are emitted. @@ -651,6 +816,93 @@ static int s_rrsm_acquire_existing_subscribed_fn(struct aws_allocator *allocator AWS_TEST_CASE(rrsm_acquire_existing_subscribed, s_rrsm_acquire_existing_subscribed_fn) +/* + * Verify: A multi-sub acquire where all subscriptions are currently subscribed + * returns SUBSCRIBED. Verify subscription events are emitted. + */ +static int s_rrsm_acquire_multi_existing_subscribed_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_protocol_adapter_api_record expected_subscribes[] = { + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = s_hello_world1_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + { + .type = PAAT_SUBSCRIBE, + .topic_filter_cursor = s_hello_world2_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }, + }; + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_byte_cursor multi_subs[] = { + s_hello_world1_cursor, + s_hello_world2_cursor, + }; + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + ASSERT_TRUE( + s_api_records_equals(fixture.mock_protocol_adapter, AWS_ARRAY_SIZE(expected_subscribes), expected_subscribes)); + + struct aws_protocol_adapter_subscription_event successful_subscription1_event = { + .topic_filter = s_hello_world1_cursor, + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription1_event); + + struct aws_protocol_adapter_subscription_event successful_subscription2_event = { + .topic_filter = s_hello_world2_cursor, + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription2_event); + + struct aws_subscription_status_record expected_subscription_events[] = { + { + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = s_hello_world1_cursor, + .operation_id = 1, + }, + { + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = s_hello_world2_cursor, + .operation_id = 1, + }}; + ASSERT_TRUE(s_contains_subscription_event_sequential_records( + &fixture, AWS_ARRAY_SIZE(expected_subscription_events), expected_subscription_events)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBED, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + ASSERT_TRUE(s_api_records_equals(fixture.mock_protocol_adapter, 2, expected_subscribes)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_multi_existing_subscribed, s_rrsm_acquire_multi_existing_subscribed_fn) + /* * Verify: Acquiring a new request-response subscription when there is no room returns BLOCKED */ @@ -706,6 +958,54 @@ static int s_rrsm_acquire_blocked_rr_fn(struct aws_allocator *allocator, void *c AWS_TEST_CASE(rrsm_acquire_blocked_rr, s_rrsm_acquire_blocked_rr_fn) +/* + * Verify: A two-subscription acquire returns BLOCKED when only one space is available + */ +static int s_rrsm_acquire_multi_blocked_rr_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 1, + .operation_timeout_seconds = 30, + }; + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, + .operation_id = 1, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire1_options)); + + struct aws_byte_cursor multi_subs[] = { + s_hello_world2_cursor, + s_hello_world3_cursor, + }; + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 2, + }; + ASSERT_INT_EQUALS( + AASRT_BLOCKED, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire2_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_multi_blocked_rr, s_rrsm_acquire_multi_blocked_rr_fn) + /* * Verify: Acquiring a new eventstream subscription when there is no room, but room could free up later, returns BLOCKED */ @@ -779,6 +1079,83 @@ static int s_rrsm_acquire_blocked_eventstream_fn(struct aws_allocator *allocator AWS_TEST_CASE(rrsm_acquire_blocked_eventstream, s_rrsm_acquire_blocked_eventstream_fn) +/* + * Verify: A two-subscription streaming acquire when there is no room, but room could free up later, returns BLOCKED + */ +static int s_rrsm_acquire_multi_blocked_eventstream_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, + .operation_timeout_seconds = DEFAULT_SM_TEST_TIMEOUT, + }; + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + struct aws_protocol_adapter_connection_event connected_event = { + .event_type = AWS_PACET_CONNECTED, + }; + aws_rr_subscription_manager_on_protocol_adapter_connection_event(&fixture.subscription_manager, &connected_event); + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_EVENT_STREAM, + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, + .operation_id = 1, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire1_options)); + + struct aws_protocol_adapter_subscription_event subscribe_success_event = { + .event_type = AWS_PASET_SUBSCRIBE, + .topic_filter = s_hello_world1_cursor, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event( + &fixture.subscription_manager, &subscribe_success_event); + + // release and trigger an unsubscribe + struct aws_rr_release_subscription_options release1_options = { + .operation_id = 1, + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, + }; + aws_rr_subscription_manager_release_subscription(&fixture.subscription_manager, &release1_options); + aws_rr_subscription_manager_purge_unused(&fixture.subscription_manager); + + struct aws_protocol_adapter_api_record expected_unsubscribe = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = s_hello_world1_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe)); + + // multi-acquire while the streaming unsubscribe is in-progress should return blocked + struct aws_byte_cursor multi_subs[] = { + s_hello_world2_cursor, + s_hello_world3_cursor, + }; + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 2, + }; + ASSERT_INT_EQUALS( + AASRT_BLOCKED, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire2_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_multi_blocked_eventstream, s_rrsm_acquire_multi_blocked_eventstream_fn) + static int s_do_acquire_no_capacity_test(struct aws_subscription_manager_test_fixture *fixture) { struct aws_rr_subscription_manager *manager = &fixture->subscription_manager; @@ -863,6 +1240,56 @@ static int s_rrsm_acquire_no_capacity_too_many_event_stream_fn(struct aws_alloca AWS_TEST_CASE(rrsm_acquire_no_capacity_too_many_event_stream, s_rrsm_acquire_no_capacity_too_many_event_stream_fn) +/* + * Verify: A two-subscription streaming acquire, where there is only room for one, and no room could free up later, + * returns NO_CAPACITY + */ +static int s_rrsm_acquire_multi_no_capacity_event_stream_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture_options fixture_config = { + .max_request_response_subscriptions = 2, + .max_streaming_subscriptions = 2, + .operation_timeout_seconds = 30, + }; + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, &fixture_config)); + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_EVENT_STREAM, + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, + .operation_id = 1, + }; + + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire1_options)); + + struct aws_byte_cursor multi_subs[] = { + s_hello_world2_cursor, + s_hello_world3_cursor, + }; + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 2, + }; + ASSERT_INT_EQUALS( + AASRT_NO_CAPACITY, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire2_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_acquire_multi_no_capacity_event_stream, s_rrsm_acquire_multi_no_capacity_event_stream_fn) + /* * Verify: Acquiring an existing subscription with an unequal subscription type returns FAILURE */ @@ -902,6 +1329,51 @@ static int s_rrsm_acquire_failure_mixed_subscription_types_fn(struct aws_allocat AWS_TEST_CASE(rrsm_acquire_failure_mixed_subscription_types, s_rrsm_acquire_failure_mixed_subscription_types_fn) +/* + * Verify: A two-subscription acquire that partially overlaps a subscription of different type results in FAILURE + */ +static int s_rrsm_acquire_multi_failure_mixed_subscription_types_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = &s_hello_world1_cursor, + .topic_filter_count = 1, + .operation_id = 1, + }; + ASSERT_INT_EQUALS( + AASRT_SUBSCRIBING, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire1_options)); + + struct aws_byte_cursor multi_subs[] = { + s_hello_world2_cursor, + s_hello_world1_cursor, + }; + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_EVENT_STREAM, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 2, + }; + ASSERT_INT_EQUALS( + AASRT_FAILURE, + aws_rr_subscription_manager_acquire_subscription(&fixture.subscription_manager, &acquire2_options)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE( + rrsm_acquire_multi_failure_mixed_subscription_types, + s_rrsm_acquire_multi_failure_mixed_subscription_types_fn) + /* * Verify: Acquiring a poisoned streaming subscription results in failure. */ @@ -956,7 +1428,7 @@ AWS_TEST_CASE(rrsm_acquire_failure_poisoned, s_rrsm_acquire_failure_poisoned_fn) /* * Verify: A request subscription that resolves successfully invokes callbacks for every operation listener; releasing - * both references and calling a new acquire will trigger an unsubscribe of the first subscription + * both references and purging will trigger an unsubscribe of the first subscription */ static int s_rrsm_release_unsubscribes_request_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -1044,6 +1516,129 @@ static int s_rrsm_release_unsubscribes_request_fn(struct aws_allocator *allocato AWS_TEST_CASE(rrsm_release_unsubscribes_request, s_rrsm_release_unsubscribes_request_fn) +/* + * Verify: A multi subscription that resolves successfully invokes callbacks for every operation listener; releasing + * all references and purging will trigger an unsubscribe of both subscriptions + */ +static int s_rrsm_release_multi_unsubscribes_request_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_mqtt_library_init(allocator); + + struct aws_subscription_manager_test_fixture fixture; + ASSERT_SUCCESS(s_aws_subscription_manager_test_fixture_init(&fixture, allocator, NULL)); + + struct aws_rr_subscription_manager *manager = &fixture.subscription_manager; + + struct aws_byte_cursor multi_subs[] = { + s_hello_world1_cursor, + s_hello_world2_cursor, + }; + struct aws_rr_acquire_subscription_options acquire1_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 1, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire1_options)); + + struct aws_rr_acquire_subscription_options acquire2_options = { + .type = ARRST_REQUEST_RESPONSE, + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 2, + }; + ASSERT_INT_EQUALS(AASRT_SUBSCRIBING, aws_rr_subscription_manager_acquire_subscription(manager, &acquire2_options)); + + struct aws_protocol_adapter_subscription_event successful_subscription_event1 = { + .topic_filter = s_hello_world1_cursor, + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription_event1); + + struct aws_protocol_adapter_subscription_event successful_subscription_event2 = { + .topic_filter = s_hello_world2_cursor, + .event_type = AWS_PASET_SUBSCRIBE, + .error_code = AWS_ERROR_SUCCESS, + }; + aws_rr_subscription_manager_on_protocol_adapter_subscription_event(manager, &successful_subscription_event2); + + // verify four success callbacks + struct aws_subscription_status_record expected_subscription_events[] = { + { + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = s_hello_world1_cursor, + .operation_id = 1, + }, + { + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = s_hello_world1_cursor, + .operation_id = 2, + }, + { + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = s_hello_world2_cursor, + .operation_id = 1, + }, + { + .type = ARRSET_REQUEST_SUBSCRIBE_SUCCESS, + .topic_filter_cursor = s_hello_world2_cursor, + .operation_id = 2, + }, + }; + ASSERT_TRUE(s_contains_subscription_event_records( + &fixture, AWS_ARRAY_SIZE(expected_subscription_events), expected_subscription_events)); + + // verify no unsubscribes + struct aws_protocol_adapter_api_record expected_unsubscribe1 = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = s_hello_world1_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe1)); + + struct aws_protocol_adapter_api_record expected_unsubscribe2 = { + .type = PAAT_UNSUBSCRIBE, + .topic_filter_cursor = s_hello_world2_cursor, + .timeout = DEFAULT_SM_TEST_TIMEOUT, + }; + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe2)); + + // release once, verify no unsubscribes + struct aws_rr_release_subscription_options release1_options = { + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 1, + }; + aws_rr_subscription_manager_release_subscription(manager, &release1_options); + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe1)); + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe2)); + + // release second + struct aws_rr_release_subscription_options release2_options = { + .topic_filters = multi_subs, + .topic_filter_count = AWS_ARRAY_SIZE(multi_subs), + .operation_id = 2, + }; + aws_rr_subscription_manager_release_subscription(manager, &release2_options); + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe1)); + ASSERT_FALSE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe2)); + + aws_rr_subscription_manager_purge_unused(manager); + + // now the unsubscribes should be present + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe1)); + ASSERT_TRUE(s_api_records_contains_record(fixture.mock_protocol_adapter, &expected_unsubscribe2)); + + s_aws_subscription_manager_test_fixture_clean_up(&fixture); + aws_mqtt_library_clean_up(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(rrsm_release_multi_unsubscribes_request, s_rrsm_release_multi_unsubscribes_request_fn) + /* * Verify: A streaming subscription that resolves successfully invokes callbacks for every operation listener; releasing * both references and calling a new acquire will trigger an unsubscribe of the first subscription From 84d9696a538348f1db7b8ce6c6ba2b808041a080 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Sun, 14 Apr 2024 15:26:00 -0700 Subject: [PATCH 108/124] Checkpoint --- bin/elastishadow/main.c | 684 +++++++++++++++--- .../private/{shared_constants.h => shared.h} | 5 + source/client.c | 2 +- source/{shared_constants.c => shared.c} | 2 +- source/v5/mqtt5_client.c | 2 +- 5 files changed, 605 insertions(+), 90 deletions(-) rename include/aws/mqtt/private/{shared_constants.h => shared.h} (69%) rename source/{shared_constants.c => shared.c} (95%) diff --git a/bin/elastishadow/main.c b/bin/elastishadow/main.c index 08639bf5..729e4133 100644 --- a/bin/elastishadow/main.c +++ b/bin/elastishadow/main.c @@ -22,8 +22,9 @@ #include #include +#include #include -#include +#include #include #include @@ -57,8 +58,66 @@ struct app_ctx { struct aws_mqtt5_client *client; struct aws_mqtt_request_response_client *rr_client; + + /* + * &aws_shadow_streaming_operation.id -> aws_shadow_streaming_operation * + */ + struct aws_hash_table streaming_operations; + uint64_t next_id; }; +struct aws_shadow_streaming_operation { + struct aws_allocator *allocator; + + struct aws_byte_buf thing; + struct aws_byte_buf shadow; + struct aws_byte_buf topic_filter; + + uint64_t id; + struct aws_mqtt_rr_client_operation *streaming_operation; +}; + +struct aws_shadow_streaming_operation *s_aws_shadow_streaming_operation_new( + struct aws_allocator *allocator, + struct app_ctx *app_ctx, + struct aws_byte_cursor thing, + struct aws_byte_cursor shadow, + struct aws_byte_cursor topic_filter) { + struct aws_shadow_streaming_operation *operation = + aws_mem_calloc(allocator, 1, sizeof(struct aws_shadow_streaming_operation)); + + operation->allocator = allocator; + operation->id = ++app_ctx->next_id; + operation->streaming_operation = NULL; + + aws_byte_buf_init_copy_from_cursor(&operation->thing, allocator, thing); + aws_byte_buf_init_copy_from_cursor(&operation->shadow, allocator, shadow); + aws_byte_buf_init_copy_from_cursor(&operation->topic_filter, allocator, topic_filter); + + return operation; +} + +void s_aws_shadow_streaming_operation_destroy(struct aws_shadow_streaming_operation *operation) { + if (operation == NULL) { + return; + } + + aws_byte_buf_clean_up(&operation->thing); + aws_byte_buf_clean_up(&operation->shadow); + + aws_mem_release(operation->allocator, operation); +} + +static void s_release_streaming_operation(void *value) { + struct aws_shadow_streaming_operation *operation = value; + + if (operation->streaming_operation != NULL) { + aws_mqtt_rr_client_operation_release(operation->streaming_operation); + } else { + s_aws_shadow_streaming_operation_destroy(operation); + } +} + static void s_usage(int exit_code) { fprintf(stderr, "usage: elastishadow [options] endpoint\n"); @@ -165,6 +224,14 @@ static void s_split_command_line(struct aws_byte_cursor cursor, struct aws_array } } +static void s_write_correlation_token_string(struct aws_byte_cursor scratch_space) { + struct aws_byte_buf correlation_token_buf = aws_byte_buf_from_empty_array(scratch_space.ptr, scratch_space.len); + + struct aws_uuid uuid; + aws_uuid_init(&uuid); + aws_uuid_to_str(&uuid, &correlation_token_buf); +} + static void s_on_get_shadow_complete( const struct aws_byte_cursor *response_topic, const struct aws_byte_cursor *payload, @@ -174,23 +241,28 @@ static void s_on_get_shadow_complete( struct aws_string *correlation_token = user_data; if (payload != NULL) { - printf("GetNamedShadow request '%s' response received on topic '" PRInSTR "' with body:\n " PRInSTR "\n", correlation_token->bytes, AWS_BYTE_CURSOR_PRI(*response_topic), AWS_BYTE_CURSOR_PRI(*payload)); + printf( + "GetNamedShadow request '%s' response received on topic '" PRInSTR "' with body:\n " PRInSTR "\n\n", + correlation_token->bytes, + AWS_BYTE_CURSOR_PRI(*response_topic), + AWS_BYTE_CURSOR_PRI(*payload)); } else { - printf("GetNamedShadow request '%s' failed with error code %d(%s)\n", correlation_token->bytes, error_code, aws_error_debug_str(error_code)); + printf( + "GetNamedShadow request '%s' failed with error code %d(%s)\n\n", + correlation_token->bytes, + error_code, + aws_error_debug_str(error_code)); } aws_string_destroy(correlation_token); } -static void s_handle_get( - struct app_ctx *context, - struct aws_allocator *allocator, - struct aws_array_list *arguments) { +static void s_handle_get(struct app_ctx *context, struct aws_allocator *allocator, struct aws_array_list *arguments) { size_t argument_count = aws_array_list_length(arguments) - 1; if (argument_count != 2) { printf("invalid get-named-shadow options:\n"); - printf(" get-named-shadow \n"); + printf(" get-named-shadow \n\n"); return; } @@ -203,13 +275,29 @@ static void s_handle_get( aws_array_list_get_at(arguments, &shadow_name_cursor, 2); char subscription_topic_filter[128]; - snprintf(subscription_topic_filter, AWS_ARRAY_SIZE(subscription_topic_filter), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/+", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + subscription_topic_filter, + AWS_ARRAY_SIZE(subscription_topic_filter), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/+", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + struct aws_byte_cursor subscription_topic_filter_cursor = aws_byte_cursor_from_c_str(subscription_topic_filter); char accepted_path[128]; - snprintf(accepted_path, AWS_ARRAY_SIZE(accepted_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/accepted", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + accepted_path, + AWS_ARRAY_SIZE(accepted_path), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/accepted", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); char rejected_path[128]; - snprintf(rejected_path, AWS_ARRAY_SIZE(rejected_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/rejected", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + rejected_path, + AWS_ARRAY_SIZE(rejected_path), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get/rejected", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str("clientToken"); struct aws_mqtt_request_operation_response_path response_paths[] = { @@ -224,20 +312,22 @@ static void s_handle_get( }; char publish_topic[128]; - snprintf(publish_topic, AWS_ARRAY_SIZE(publish_topic), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + publish_topic, + AWS_ARRAY_SIZE(publish_topic), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/get", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); char correlation_token[128]; - struct aws_byte_buf correlation_token_buf = aws_byte_buf_from_empty_array(correlation_token, AWS_ARRAY_SIZE(correlation_token)); - - struct aws_uuid uuid; - aws_uuid_init(&uuid); - aws_uuid_to_str(&uuid, &correlation_token_buf); + s_write_correlation_token_string(aws_byte_cursor_from_array(correlation_token, AWS_ARRAY_SIZE(correlation_token))); char request[256]; snprintf(request, AWS_ARRAY_SIZE(request), "{\"clientToken\":\"%s\"}", correlation_token); struct aws_mqtt_request_operation_options get_options = { - .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .subscription_topic_filters = &subscription_topic_filter_cursor, + .subscription_topic_filter_count = 1, .response_paths = response_paths, .response_path_count = 2, .publish_topic = aws_byte_cursor_from_c_str(publish_topic), @@ -247,26 +337,22 @@ static void s_handle_get( .user_data = aws_string_new_from_c_str(allocator, correlation_token), }; - printf("Submitting GetNamedShadow request for shadow '" PRInSTR "' of thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); + printf( + "Submitting GetNamedShadow request for shadow '" PRInSTR "' of thing '" PRInSTR + "' using correlation token %s...\n", + AWS_BYTE_CURSOR_PRI(shadow_name_cursor), + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + correlation_token); if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { int error_code = aws_last_error(); - printf("GetNamedShadow synchronous failure: %d(%s)", error_code, aws_error_debug_str(error_code)); + printf("GetNamedShadow synchronous failure: %d(%s)\n", error_code, aws_error_debug_str(error_code)); } -} -static void s_handle_update_reported( - struct app_ctx *context, - struct aws_allocator *allocator, - struct aws_array_list *arguments, - struct aws_byte_cursor line_cursor) { - (void)context; - (void)allocator; - (void)arguments; - (void)line_cursor; + printf("\n"); } -static void s_on_update_shadow_desired_complete( +static void s_on_update_shadow_complete( const struct aws_byte_cursor *response_topic, const struct aws_byte_cursor *payload, int error_code, @@ -275,27 +361,28 @@ static void s_on_update_shadow_desired_complete( struct aws_string *correlation_token = user_data; if (payload != NULL) { - printf("UpdateNameShadowDesired request '%s' response received on topic '" PRInSTR "' with body:\n " PRInSTR "\n", correlation_token->bytes, AWS_BYTE_CURSOR_PRI(*response_topic), AWS_BYTE_CURSOR_PRI(*payload)); + printf( + "UpdateNamedShadow request '%s' response received on topic '" PRInSTR "' with body:\n " PRInSTR "\n\n", + correlation_token->bytes, + AWS_BYTE_CURSOR_PRI(*response_topic), + AWS_BYTE_CURSOR_PRI(*payload)); } else { - printf("UpdateNameShadowDesired request '%s' failed with error code %d(%s)\n", correlation_token->bytes, error_code, aws_error_debug_str(error_code)); + printf( + "UpdateNamedShadow request '%s' failed with error code %d(%s)\n\n", + correlation_token->bytes, + error_code, + aws_error_debug_str(error_code)); } aws_string_destroy(correlation_token); } - -static void s_handle_update_desired( +static void s_handle_update( struct app_ctx *context, struct aws_allocator *allocator, struct aws_array_list *arguments, - struct aws_byte_cursor line_cursor) { - - size_t argument_count = aws_array_list_length(arguments) - 1; - if (argument_count < 3) { - printf("invalid update-named-shadow-desired options:\n"); - printf(" delete-named-shadow \n"); - return; - } + struct aws_byte_cursor desired_state, + struct aws_byte_cursor correlation_token) { struct aws_byte_cursor thing_name_cursor; AWS_ZERO_STRUCT(thing_name_cursor); @@ -305,18 +392,46 @@ static void s_handle_update_desired( AWS_ZERO_STRUCT(shadow_name_cursor); aws_array_list_get_at(arguments, &shadow_name_cursor, 2); - struct aws_byte_cursor desired_state_cursor; - aws_array_list_get_at(arguments, &desired_state_cursor, 3); - desired_state_cursor.len = (size_t)(line_cursor.ptr + line_cursor.len - desired_state_cursor.ptr); - - char subscription_topic_filter[128]; - snprintf(subscription_topic_filter, AWS_ARRAY_SIZE(subscription_topic_filter), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/+", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + char subscription_topic_filter_accepted[128]; + snprintf( + subscription_topic_filter_accepted, + AWS_ARRAY_SIZE(subscription_topic_filter_accepted), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/accepted", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + struct aws_byte_cursor subscription_topic_filter_accepted_cursor = + aws_byte_cursor_from_c_str(subscription_topic_filter_accepted); + + char subscription_topic_filter_rejected[128]; + snprintf( + subscription_topic_filter_rejected, + AWS_ARRAY_SIZE(subscription_topic_filter_rejected), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/rejected", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + struct aws_byte_cursor subscription_topic_filter_rejected_cursor = + aws_byte_cursor_from_c_str(subscription_topic_filter_rejected); + + struct aws_byte_cursor subscription_topic_filters[] = { + subscription_topic_filter_accepted_cursor, + subscription_topic_filter_rejected_cursor, + }; char accepted_path[128]; - snprintf(accepted_path, AWS_ARRAY_SIZE(accepted_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/accepted", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + accepted_path, + AWS_ARRAY_SIZE(accepted_path), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/accepted", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); char rejected_path[128]; - snprintf(rejected_path, AWS_ARRAY_SIZE(rejected_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/rejected", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + rejected_path, + AWS_ARRAY_SIZE(rejected_path), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/rejected", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str("clientToken"); struct aws_mqtt_request_operation_response_path response_paths[] = { @@ -331,35 +446,110 @@ static void s_handle_update_desired( }; char publish_topic[128]; - snprintf(publish_topic, AWS_ARRAY_SIZE(publish_topic), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); - - char correlation_token[128]; - struct aws_byte_buf correlation_token_buf = aws_byte_buf_from_empty_array(correlation_token, AWS_ARRAY_SIZE(correlation_token)); - - struct aws_uuid uuid; - aws_uuid_init(&uuid); - aws_uuid_to_str(&uuid, &correlation_token_buf); - - char request[256]; - snprintf(request, AWS_ARRAY_SIZE(request), "{\"clientToken\":\"%s\",\"state\":{\"desired\":" PRInSTR "}}", correlation_token, AWS_BYTE_CURSOR_PRI(desired_state_cursor)); + snprintf( + publish_topic, + AWS_ARRAY_SIZE(publish_topic), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); struct aws_mqtt_request_operation_options get_options = { - .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .subscription_topic_filters = subscription_topic_filters, + .subscription_topic_filter_count = 2, .response_paths = response_paths, .response_path_count = 2, .publish_topic = aws_byte_cursor_from_c_str(publish_topic), - .serialized_request = aws_byte_cursor_from_c_str(request), - .correlation_token = aws_byte_cursor_from_c_str(correlation_token), - .completion_callback = s_on_update_shadow_desired_complete, - .user_data = aws_string_new_from_c_str(allocator, correlation_token), + .serialized_request = desired_state, + .correlation_token = correlation_token, + .completion_callback = s_on_update_shadow_complete, + .user_data = aws_string_new_from_cursor(allocator, &correlation_token), }; - printf("Submitting UpdateNameShadowDesired request for shadow '" PRInSTR "' of thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); + printf( + "Submitting UpdateNamedShadow request for shadow '" PRInSTR "' of thing '" PRInSTR + "' using correlation token '" PRInSTR "'...\n", + AWS_BYTE_CURSOR_PRI(shadow_name_cursor), + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(correlation_token)); if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { int error_code = aws_last_error(); - printf("UpdateNameShadowDesired synchronous failure: %d(%s)", error_code, aws_error_debug_str(error_code)); + printf("UpdateNamedShadow synchronous failure: %d(%s)\n", error_code, aws_error_debug_str(error_code)); } + + printf("\n"); +} + +static void s_handle_update_desired( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments, + struct aws_byte_cursor line_cursor) { + + size_t argument_count = aws_array_list_length(arguments) - 1; + if (argument_count < 3) { + printf("invalid update-named-shadow-desired options:\n"); + printf(" update-named-shadow-desired \n\n"); + return; + } + + struct aws_byte_cursor desired_state_cursor; + aws_array_list_get_at(arguments, &desired_state_cursor, 3); + desired_state_cursor.len = (size_t)(line_cursor.ptr + line_cursor.len - desired_state_cursor.ptr); + + char correlation_token[128]; + s_write_correlation_token_string(aws_byte_cursor_from_array(correlation_token, AWS_ARRAY_SIZE(correlation_token))); + + char request[256]; + snprintf( + request, + AWS_ARRAY_SIZE(request), + "{\"clientToken\":\"%s\",\"state\":{\"desired\":" PRInSTR "}}", + correlation_token, + AWS_BYTE_CURSOR_PRI(desired_state_cursor)); + + s_handle_update( + context, + allocator, + arguments, + aws_byte_cursor_from_c_str(request), + aws_byte_cursor_from_c_str(correlation_token)); +} + +static void s_handle_update_reported( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments, + struct aws_byte_cursor line_cursor) { + + size_t argument_count = aws_array_list_length(arguments) - 1; + if (argument_count < 3) { + printf("invalid update-named-shadow-reported options:\n"); + printf(" update-named-shadow-reported \n\n"); + return; + } + + struct aws_byte_cursor reported_state_cursor; + aws_array_list_get_at(arguments, &reported_state_cursor, 3); + reported_state_cursor.len = (size_t)(line_cursor.ptr + line_cursor.len - reported_state_cursor.ptr); + + char correlation_token[128]; + s_write_correlation_token_string(aws_byte_cursor_from_array(correlation_token, AWS_ARRAY_SIZE(correlation_token))); + + char request[256]; + snprintf( + request, + AWS_ARRAY_SIZE(request), + "{\"clientToken\":\"%s\",\"state\":{\"reported\":" PRInSTR "}}", + correlation_token, + AWS_BYTE_CURSOR_PRI(reported_state_cursor)); + + s_handle_update( + context, + allocator, + arguments, + aws_byte_cursor_from_c_str(request), + aws_byte_cursor_from_c_str(correlation_token)); } static void s_on_delete_shadow_complete( @@ -371,9 +561,17 @@ static void s_on_delete_shadow_complete( struct aws_string *correlation_token = user_data; if (payload != NULL) { - printf("DeleteNamedShadow request '%s' response received on topic '" PRInSTR "' with body:\n " PRInSTR "\n", correlation_token->bytes, AWS_BYTE_CURSOR_PRI(*response_topic), AWS_BYTE_CURSOR_PRI(*payload)); + printf( + "DeleteNamedShadow request '%s' response received on topic '" PRInSTR "' with body:\n " PRInSTR "\n\n", + correlation_token->bytes, + AWS_BYTE_CURSOR_PRI(*response_topic), + AWS_BYTE_CURSOR_PRI(*payload)); } else { - printf("DeleteNamedShadow request '%s' failed with error code %d(%s)\n", correlation_token->bytes, error_code, aws_error_debug_str(error_code)); + printf( + "DeleteNamedShadow request '%s' failed with error code %d(%s)\n\n", + correlation_token->bytes, + error_code, + aws_error_debug_str(error_code)); } aws_string_destroy(correlation_token); @@ -387,7 +585,7 @@ static void s_handle_delete( size_t argument_count = aws_array_list_length(arguments) - 1; if (argument_count != 2) { printf("invalid delete-named-shadow options:\n"); - printf(" delete-named-shadow \n"); + printf(" delete-named-shadow \n\n"); return; } @@ -400,13 +598,29 @@ static void s_handle_delete( aws_array_list_get_at(arguments, &shadow_name_cursor, 2); char subscription_topic_filter[128]; - snprintf(subscription_topic_filter, AWS_ARRAY_SIZE(subscription_topic_filter), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/+", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + subscription_topic_filter, + AWS_ARRAY_SIZE(subscription_topic_filter), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/+", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + struct aws_byte_cursor subscription_topic_filter_cursor = aws_byte_cursor_from_c_str(subscription_topic_filter); char accepted_path[128]; - snprintf(accepted_path, AWS_ARRAY_SIZE(accepted_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/accepted", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + accepted_path, + AWS_ARRAY_SIZE(accepted_path), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/accepted", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); char rejected_path[128]; - snprintf(rejected_path, AWS_ARRAY_SIZE(rejected_path), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/rejected", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + rejected_path, + AWS_ARRAY_SIZE(rejected_path), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete/rejected", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); struct aws_byte_cursor correlation_token_path = aws_byte_cursor_from_c_str("clientToken"); struct aws_mqtt_request_operation_response_path response_paths[] = { @@ -421,10 +635,16 @@ static void s_handle_delete( }; char publish_topic[128]; - snprintf(publish_topic, AWS_ARRAY_SIZE(publish_topic), "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete", AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + snprintf( + publish_topic, + AWS_ARRAY_SIZE(publish_topic), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/delete", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); char correlation_token[128]; - struct aws_byte_buf correlation_token_buf = aws_byte_buf_from_empty_array(correlation_token, AWS_ARRAY_SIZE(correlation_token)); + struct aws_byte_buf correlation_token_buf = + aws_byte_buf_from_empty_array(correlation_token, AWS_ARRAY_SIZE(correlation_token)); struct aws_uuid uuid; aws_uuid_init(&uuid); @@ -434,7 +654,8 @@ static void s_handle_delete( snprintf(request, AWS_ARRAY_SIZE(request), "{\"clientToken\":\"%s\"}", correlation_token); struct aws_mqtt_request_operation_options get_options = { - .subscription_topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .subscription_topic_filters = &subscription_topic_filter_cursor, + .subscription_topic_filter_count = 1, .response_paths = response_paths, .response_path_count = 2, .publish_topic = aws_byte_cursor_from_c_str(publish_topic), @@ -444,11 +665,270 @@ static void s_handle_delete( .user_data = aws_string_new_from_c_str(allocator, correlation_token), }; - printf("Submitting DeleteNamedShadow request for shadow '" PRInSTR "' of thing '" PRInSTR "' using correlation token %s...\n", AWS_BYTE_CURSOR_PRI(shadow_name_cursor), AWS_BYTE_CURSOR_PRI(thing_name_cursor), correlation_token); + printf( + "Submitting DeleteNamedShadow request for shadow '" PRInSTR "' of thing '" PRInSTR + "' using correlation token %s...\n", + AWS_BYTE_CURSOR_PRI(shadow_name_cursor), + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + correlation_token); if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { int error_code = aws_last_error(); - printf("DeleteNamedShadow synchronous failure: %d(%s)", error_code, aws_error_debug_str(error_code)); + printf("DeleteNamedShadow synchronous failure: %d(%s)\n", error_code, aws_error_debug_str(error_code)); + } + + printf("\n"); +} + +static int s_print_streaming_operation(void *context, struct aws_hash_element *elem) { + (void)context; + struct aws_shadow_streaming_operation *operation = elem->value; + + struct aws_byte_cursor thing_cursor = aws_byte_cursor_from_buf(&operation->thing); + struct aws_byte_cursor shadow_cursor = aws_byte_cursor_from_buf(&operation->shadow); + struct aws_byte_cursor topic_filter_cursor = aws_byte_cursor_from_buf(&operation->topic_filter); + + printf( + " %" PRIu64 " Thing:'" PRInSTR "', Shadow: '" PRInSTR "', TopicFilter:'" PRInSTR "'\n", + operation->id, + AWS_BYTE_CURSOR_PRI(thing_cursor), + AWS_BYTE_CURSOR_PRI(shadow_cursor), + AWS_BYTE_CURSOR_PRI(topic_filter_cursor)); + + return AWS_COMMON_HASH_TABLE_ITER_CONTINUE; +} + +static void s_handle_list_streams( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments) { + (void)allocator; + (void)arguments; + + printf("Open Streams:\n"); + aws_hash_table_foreach(&context->streaming_operations, s_print_streaming_operation, NULL); + printf("\n"); +} + +static const char *s_rr_streaming_subscription_event_type_to_c_str( + enum aws_rr_streaming_subscription_event_type status) { + switch (status) { + case ARRSSET_SUBSCRIPTION_ESTABLISHED: + return "SubscriptionEstablished"; + + case ARRSSET_SUBSCRIPTION_LOST: + return "SubscriptionLost"; + + case ARRSSET_SUBSCRIPTION_HALTED: + return "SubscriptionHalted"; + + default: + return "Unknown"; + } +} + +static void s_stream_subscription_status_fn( + enum aws_rr_streaming_subscription_event_type status, + int error_code, + void *user_data) { + + struct aws_shadow_streaming_operation *operation = user_data; + + struct aws_byte_cursor topic_filter_cursor = aws_byte_cursor_from_buf(&operation->topic_filter); + + printf( + "Streaming operation %" PRIu64 " received subscription status event on topic filter '" PRInSTR "':\n", + operation->id, + AWS_BYTE_CURSOR_PRI(topic_filter_cursor)); + printf( + " Status: %d(%s), ErrorCode: %d(%s)\n\n", + status, + s_rr_streaming_subscription_event_type_to_c_str(status), + error_code, + aws_error_debug_str(error_code)); +} + +static void s_stream_incoming_publish_fn(struct aws_byte_cursor payload, void *user_data) { + struct aws_shadow_streaming_operation *operation = user_data; + + struct aws_byte_cursor thing_cursor = aws_byte_cursor_from_buf(&operation->thing); + struct aws_byte_cursor shadow_cursor = aws_byte_cursor_from_buf(&operation->shadow); + struct aws_byte_cursor topic_filter_cursor = aws_byte_cursor_from_buf(&operation->topic_filter); + + printf( + "Streaming operation %" PRIu64 " ('" PRInSTR "', ' " PRInSTR "') received publish on topic filter '" PRInSTR + "':\n", + operation->id, + AWS_BYTE_CURSOR_PRI(thing_cursor), + AWS_BYTE_CURSOR_PRI(shadow_cursor), + AWS_BYTE_CURSOR_PRI(topic_filter_cursor)); + printf(" " PRInSTR "\n\n", AWS_BYTE_CURSOR_PRI(payload)); +} + +static void s_stream_terminated_fn(void *user_data) { + struct aws_shadow_streaming_operation *operation = user_data; + + printf("Stream %" PRIu64 "terminated\n\n", operation->id); + + s_aws_shadow_streaming_operation_destroy(operation); +} + +static void s_handle_open_delta_stream( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments) { + + size_t argument_count = aws_array_list_length(arguments) - 1; + if (argument_count != 2) { + printf("invalid open-named-shadow-delta-stream options:\n"); + printf(" open-named-shadow-delta-stream \n\n"); + return; + } + + struct aws_byte_cursor thing_name_cursor; + AWS_ZERO_STRUCT(thing_name_cursor); + aws_array_list_get_at(arguments, &thing_name_cursor, 1); + + struct aws_byte_cursor shadow_name_cursor; + AWS_ZERO_STRUCT(shadow_name_cursor); + aws_array_list_get_at(arguments, &shadow_name_cursor, 2); + + char subscription_topic_filter[128]; + snprintf( + subscription_topic_filter, + AWS_ARRAY_SIZE(subscription_topic_filter), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/delta", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + struct aws_shadow_streaming_operation *operation = s_aws_shadow_streaming_operation_new( + allocator, + context, + thing_name_cursor, + shadow_name_cursor, + aws_byte_cursor_from_c_str(subscription_topic_filter)); + aws_hash_table_put(&context->streaming_operations, &operation->id, operation, NULL); + + struct aws_mqtt_streaming_operation_options open_stream_options = { + .topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .subscription_status_callback = s_stream_subscription_status_fn, + .incoming_publish_callback = s_stream_incoming_publish_fn, + .terminated_callback = s_stream_terminated_fn, + .user_data = operation, + }; + + printf( + "Opening NamedShadow delta stream with id %" PRIu64 " for shadow '" PRInSTR "' of thing '" PRInSTR "'...\n", + operation->id, + AWS_BYTE_CURSOR_PRI(shadow_name_cursor), + AWS_BYTE_CURSOR_PRI(thing_name_cursor)); + + operation->streaming_operation = + aws_mqtt_request_response_client_create_streaming_operation(context->rr_client, &open_stream_options); + if (operation->streaming_operation == NULL) { + int error_code = aws_last_error(); + printf( + "NamedShadow delta stream synchronous open failure: %d(%s)\n", error_code, aws_error_debug_str(error_code)); + + aws_hash_table_remove(&context->streaming_operations, &operation->id, NULL, NULL); + } + + printf("\n"); +} + +static void s_handle_open_document_stream( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments) { + + size_t argument_count = aws_array_list_length(arguments) - 1; + if (argument_count != 2) { + printf("invalid open-named-shadow-document-stream options:\n"); + printf(" open-named-shadow-document-stream \n\n"); + return; + } + + struct aws_byte_cursor thing_name_cursor; + AWS_ZERO_STRUCT(thing_name_cursor); + aws_array_list_get_at(arguments, &thing_name_cursor, 1); + + struct aws_byte_cursor shadow_name_cursor; + AWS_ZERO_STRUCT(shadow_name_cursor); + aws_array_list_get_at(arguments, &shadow_name_cursor, 2); + + char subscription_topic_filter[128]; + snprintf( + subscription_topic_filter, + AWS_ARRAY_SIZE(subscription_topic_filter), + "$aws/things/" PRInSTR "/shadow/name/" PRInSTR "/update/document", + AWS_BYTE_CURSOR_PRI(thing_name_cursor), + AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + + struct aws_shadow_streaming_operation *operation = s_aws_shadow_streaming_operation_new( + allocator, + context, + thing_name_cursor, + shadow_name_cursor, + aws_byte_cursor_from_c_str(subscription_topic_filter)); + aws_hash_table_put(&context->streaming_operations, &operation->id, operation, NULL); + + struct aws_mqtt_streaming_operation_options open_stream_options = { + .topic_filter = aws_byte_cursor_from_c_str(subscription_topic_filter), + .subscription_status_callback = s_stream_subscription_status_fn, + .incoming_publish_callback = s_stream_incoming_publish_fn, + .terminated_callback = s_stream_terminated_fn, + .user_data = operation, + }; + + printf( + "Opening NamedShadow document stream with id %" PRIu64 " for shadow '" PRInSTR "' of thing '" PRInSTR "'...\n", + operation->id, + AWS_BYTE_CURSOR_PRI(shadow_name_cursor), + AWS_BYTE_CURSOR_PRI(thing_name_cursor)); + + operation->streaming_operation = + aws_mqtt_request_response_client_create_streaming_operation(context->rr_client, &open_stream_options); + if (operation->streaming_operation == NULL) { + int error_code = aws_last_error(); + printf( + "NamedShadow document stream synchronous open failure: %d(%s)\n", + error_code, + aws_error_debug_str(error_code)); + + aws_hash_table_remove(&context->streaming_operations, &operation->id, NULL, NULL); + } + + printf("\n"); +} + +static void s_handle_close_stream( + struct app_ctx *context, + struct aws_allocator *allocator, + struct aws_array_list *arguments) { + (void)allocator; + + size_t argument_count = aws_array_list_length(arguments) - 1; + if (argument_count != 1) { + printf("invalid close-stream options:\n"); + printf(" close-stream \n\n"); + return; + } + + struct aws_byte_cursor id_cursor; + AWS_ZERO_STRUCT(id_cursor); + aws_array_list_get_at(arguments, &id_cursor, 1); + + char id_buffer[32]; + snprintf(id_buffer, AWS_ARRAY_SIZE(id_buffer), PRInSTR, AWS_BYTE_CURSOR_PRI(id_cursor)); + uint64_t id = atoi(id_buffer); + + int was_present = 0; + aws_hash_table_remove(&context->streaming_operations, &id, NULL, &was_present); + + if (was_present == 0) { + printf("Stream %" PRIu64 " does not exist\n\n", id); + } else { + printf("Closing stream %" PRIu64 "\n\n", id); } } @@ -463,6 +943,11 @@ static bool s_handle_input(struct app_ctx *context, struct aws_allocator *alloca struct aws_byte_cursor update_reported_cursor = aws_byte_cursor_from_c_str("update-named-shadow-reported"); struct aws_byte_cursor update_desired_cursor = aws_byte_cursor_from_c_str("update-named-shadow-desired"); struct aws_byte_cursor delete_cursor = aws_byte_cursor_from_c_str("delete-named-shadow"); + struct aws_byte_cursor list_streams_cursor = aws_byte_cursor_from_c_str("list-streams"); + struct aws_byte_cursor open_delta_stream_cursor = aws_byte_cursor_from_c_str("open-named-shadow-delta-stream"); + struct aws_byte_cursor open_document_stream_cursor = + aws_byte_cursor_from_c_str("open-named-shadow-document-stream"); + struct aws_byte_cursor close_stream_cursor = aws_byte_cursor_from_c_str("close-stream"); struct aws_array_list words; aws_array_list_init_dynamic(&words, allocator, 10, sizeof(struct aws_byte_cursor)); @@ -498,8 +983,26 @@ static bool s_handle_input(struct app_ctx *context, struct aws_allocator *alloca s_handle_update_desired(context, allocator, &words, line_cursor); } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &delete_cursor)) { s_handle_delete(context, allocator, &words); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &list_streams_cursor)) { + s_handle_list_streams(context, allocator, &words); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &open_delta_stream_cursor)) { + s_handle_open_delta_stream(context, allocator, &words); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &open_document_stream_cursor)) { + s_handle_open_document_stream(context, allocator, &words); + } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &close_stream_cursor)) { + s_handle_close_stream(context, allocator, &words); } else { printf("Unknown command: " PRInSTR "\n", AWS_BYTE_CURSOR_PRI(command_cursor)); + printf("\nValid commands:\n"); + printf(" get-named-shadow - gets the full state of a named shadow\n"); + printf(" delete-named-shadow - deletes a named shadow\n"); + printf(" update-named-shadow-reported - updates the reported state of a named shadow\n"); + printf(" update-named-shadow-desired - updates the desired state of a named shadow\n"); + printf("\n"); + printf(" start - starts the mqtt5 client underlying the request-response client\n"); + printf(" stop - starts the mqtt5 client underlying the request-response client\n"); + printf(" quit - quits the program\n"); + printf("\n"); } done: @@ -542,10 +1045,6 @@ static void s_lifecycle_event_callback(const struct aws_mqtt5_client_lifecycle_e fflush(stdout); } -static void s_release_streaming_operations(struct app_ctx *ctx) { - (void)ctx; -} - AWS_STATIC_STRING_FROM_LITERAL(s_client_id, "HelloWorld"); int main(int argc, char **argv) { @@ -560,6 +1059,15 @@ int main(int argc, char **argv) { aws_mutex_init(&app_ctx.lock); app_ctx.port = 1883; + aws_hash_table_init( + &app_ctx.streaming_operations, + allocator, + 10, + aws_mqtt_hash_uint64_t, + aws_mqtt_compare_uint64_t_eq, + NULL, + s_release_streaming_operation); + s_parse_options(argc, argv, &app_ctx); if (app_ctx.uri.port) { app_ctx.port = app_ctx.uri.port; @@ -676,11 +1184,13 @@ int main(int argc, char **argv) { app_ctx.client = aws_mqtt5_client_new(allocator, &client_options); struct aws_mqtt_request_response_client_options rr_client_options = { - .max_subscriptions = 30, + .max_request_response_subscriptions = 10, + .max_streaming_subscriptions = 5, .operation_timeout_seconds = 60, }; - app_ctx.rr_client = aws_mqtt_request_response_client_new_from_mqtt5_client(allocator, app_ctx.client, &rr_client_options); + app_ctx.rr_client = + aws_mqtt_request_response_client_new_from_mqtt5_client(allocator, app_ctx.client, &rr_client_options); bool done = false; while (!done) { @@ -695,7 +1205,7 @@ int main(int argc, char **argv) { done = s_handle_input(&app_ctx, allocator, line); } - s_release_streaming_operations(&app_ctx); + aws_hash_table_clean_up(&app_ctx.streaming_operations); aws_mqtt_request_response_client_release(app_ctx.rr_client); aws_mqtt5_client_release(app_ctx.client); diff --git a/include/aws/mqtt/private/shared_constants.h b/include/aws/mqtt/private/shared.h similarity index 69% rename from include/aws/mqtt/private/shared_constants.h rename to include/aws/mqtt/private/shared.h index 0a835942..5d36d7f5 100644 --- a/include/aws/mqtt/private/shared_constants.h +++ b/include/aws/mqtt/private/shared.h @@ -8,11 +8,16 @@ #include +#include + AWS_EXTERN_C_BEGIN AWS_MQTT_API extern const struct aws_byte_cursor *g_websocket_handshake_default_path; AWS_MQTT_API extern const struct aws_http_header *g_websocket_handshake_default_protocol_header; +AWS_MQTT_API uint64_t aws_mqtt_hash_uint64_t(const void *item); +AWS_MQTT_API bool aws_mqtt_compare_uint64_t_eq(const void *a, const void *b); + AWS_EXTERN_C_END #endif /* AWS_MQTT_SHARED_CONSTANTS_H */ diff --git a/source/client.c b/source/client.c index 36c6d134..8102ea24 100644 --- a/source/client.c +++ b/source/client.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include diff --git a/source/shared_constants.c b/source/shared.c similarity index 95% rename from source/shared_constants.c rename to source/shared.c index 8d260799..e84c4afe 100644 --- a/source/shared_constants.c +++ b/source/shared.c @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0. */ -#include +#include #include diff --git a/source/v5/mqtt5_client.c b/source/v5/mqtt5_client.c index 8f9ca3dc..7474e1bd 100644 --- a/source/v5/mqtt5_client.c +++ b/source/v5/mqtt5_client.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include From aa7488fc50e73dbbaef57f84d679df32a5336004 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 15 Apr 2024 08:35:09 -0700 Subject: [PATCH 109/124] Unused params --- tests/request-response/request_response_client_tests.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index dd28c65e..9360f01f 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -2621,6 +2621,7 @@ AWS_TEST_CASE( static void s_fail_all_subscribes_config_modifier_fn( struct aws_mqtt_request_response_client_options *fixture_options, struct mqtt5_client_test_options *client_test_options) { + (void)fixture_options; client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = s_handle_subscribe_with_terminal_failure; @@ -2692,6 +2693,7 @@ int s_handle_second_subscribe_with_failure( static void s_fail_second_subscribe_config_modifier_fn( struct aws_mqtt_request_response_client_options *fixture_options, struct mqtt5_client_test_options *client_test_options) { + (void)fixture_options; client_test_options->server_function_table.packet_handlers[AWS_MQTT5_PT_SUBSCRIBE] = s_handle_second_subscribe_with_failure; From 3d99a4709e3121c0ac50d83ff2c42bb3ced20580 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 15 Apr 2024 09:46:18 -0700 Subject: [PATCH 110/124] fixup --- bin/elastishadow/main.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/bin/elastishadow/main.c b/bin/elastishadow/main.c index 729e4133..f6d37e96 100644 --- a/bin/elastishadow/main.c +++ b/bin/elastishadow/main.c @@ -3,28 +3,22 @@ * SPDX-License-Identifier: Apache-2.0. */ -#include #include #include #include #include -#include -#include #include #include #include #include #include -#include #include -#include #include #include #include -#include -#include +#include #include #include @@ -768,7 +762,7 @@ static void s_stream_incoming_publish_fn(struct aws_byte_cursor payload, void *u static void s_stream_terminated_fn(void *user_data) { struct aws_shadow_streaming_operation *operation = user_data; - printf("Stream %" PRIu64 "terminated\n\n", operation->id); + printf("Stream %" PRIu64 " terminated\n\n", operation->id); s_aws_shadow_streaming_operation_destroy(operation); } @@ -994,14 +988,19 @@ static bool s_handle_input(struct app_ctx *context, struct aws_allocator *alloca } else { printf("Unknown command: " PRInSTR "\n", AWS_BYTE_CURSOR_PRI(command_cursor)); printf("\nValid commands:\n"); - printf(" get-named-shadow - gets the full state of a named shadow\n"); - printf(" delete-named-shadow - deletes a named shadow\n"); - printf(" update-named-shadow-reported - updates the reported state of a named shadow\n"); - printf(" update-named-shadow-desired - updates the desired state of a named shadow\n"); + printf(" get-named-shadow - gets the full state of a named shadow\n"); + printf(" delete-named-shadow - deletes a named shadow\n"); + printf(" update-named-shadow-reported - updates the reported state of a named shadow\n"); + printf(" update-named-shadow-desired - updates the desired state of a named shadow\n"); printf("\n"); - printf(" start - starts the mqtt5 client underlying the request-response client\n"); - printf(" stop - starts the mqtt5 client underlying the request-response client\n"); - printf(" quit - quits the program\n"); + printf(" list-streams - lists all open shadow streams\n"); + printf(" open-named-shadow-delta-stream - creates and opens a new shadow delta stream\n"); + printf(" open-named-shadow-document-stream - creates and opens a new shadow document stream\n"); + printf(" close-stream - closes a specific shadow stream\n"); + printf("\n"); + printf(" start - starts the mqtt5 client underlying the request-response client\n"); + printf(" stop - starts the mqtt5 client underlying the request-response client\n"); + printf(" quit - quits the program\n"); printf("\n"); } From cbb958edeaba9755de635590b59716712b758e57 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 15 Apr 2024 12:09:55 -0700 Subject: [PATCH 111/124] Updates for common helper functionality --- bin/elastishadow/main.c | 4 ++-- include/aws/mqtt/private/shared.h | 3 --- source/request-response/request_response_client.c | 12 ++---------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/bin/elastishadow/main.c b/bin/elastishadow/main.c index f6d37e96..c357bf94 100644 --- a/bin/elastishadow/main.c +++ b/bin/elastishadow/main.c @@ -1062,8 +1062,8 @@ int main(int argc, char **argv) { &app_ctx.streaming_operations, allocator, 10, - aws_mqtt_hash_uint64_t, - aws_mqtt_compare_uint64_t_eq, + aws_hash_uint64_t, + aws_hash_compare_uint64_t_eq, NULL, s_release_streaming_operation); diff --git a/include/aws/mqtt/private/shared.h b/include/aws/mqtt/private/shared.h index 5d36d7f5..a1165d8e 100644 --- a/include/aws/mqtt/private/shared.h +++ b/include/aws/mqtt/private/shared.h @@ -15,9 +15,6 @@ AWS_EXTERN_C_BEGIN AWS_MQTT_API extern const struct aws_byte_cursor *g_websocket_handshake_default_path; AWS_MQTT_API extern const struct aws_http_header *g_websocket_handshake_default_protocol_header; -AWS_MQTT_API uint64_t aws_mqtt_hash_uint64_t(const void *item); -AWS_MQTT_API bool aws_mqtt_compare_uint64_t_eq(const void *a, const void *b); - AWS_EXTERN_C_END #endif /* AWS_MQTT_SHARED_CONSTANTS_H */ diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index bc86347d..542bfd79 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -1108,14 +1108,6 @@ static void s_aws_rr_client_protocol_adapter_connection_event_callback( aws_rr_subscription_manager_on_protocol_adapter_connection_event(&rr_client->subscription_manager, event); } -uint64_t aws_mqtt_hash_uint64_t(const void *item) { - return *(uint64_t *)item; -} - -bool aws_mqtt_compare_uint64_t_eq(const void *a, const void *b) { - return *(uint64_t *)a == *(uint64_t *)b; -} - static int s_compare_rr_operation_timeouts(const void *a, const void *b) { const struct aws_mqtt_rr_client_operation **operation_a_ptr = (void *)a; const struct aws_mqtt_rr_client_operation *operation_a = *operation_a_ptr; @@ -1165,8 +1157,8 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie &rr_client->operations, allocator, MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE, - aws_mqtt_hash_uint64_t, - aws_mqtt_compare_uint64_t_eq, + aws_hash_uint64_t, + aws_hash_compare_uint64_t_eq, NULL, NULL); From f8248f81d30e591eae2b89c573a15510fd0d9ba4 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 15 Apr 2024 12:54:31 -0700 Subject: [PATCH 112/124] Checkpoint --- .../request_response_client.h | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 1abd108a..fb123972 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -12,19 +12,41 @@ struct aws_mqtt_request_response_client; struct aws_mqtt_client_connection; struct aws_mqtt5_client; +/* + * A response path is a pair of values - MQTT topic and a JSON path - that describe where a response to + * an MQTT-based request may arrive. For a given request type, there may be multiple response paths and each + * one is associated with a separate JSON schema for the response body. + */ struct aws_mqtt_request_operation_response_path { + + /* + * MQTT topic that a response may arrive on. + */ struct aws_byte_cursor topic; - /* potential point of expansion into an abstract "extractor" if we ever need to support non-JSON payloads */ + /* + * JSON path for finding correlation tokens within payloads that arrive on this path's topic. + */ struct aws_byte_cursor correlation_token_json_path; }; +/* + * Callback signature for request-response completion. + * + * Invariants: + * If error_code is non-zero then response_topic and payload will be NULL. + * If response_topic and payload are not NULL then error_code will be 0. + * response_topic and payload are either both set or both not set. + */ typedef void(aws_mqtt_request_operation_completion_fn)( const struct aws_byte_cursor *response_topic, const struct aws_byte_cursor *payload, int error_code, void *user_data); +/* + * Configuration options for a request-response operation. + */ struct aws_mqtt_request_operation_options { struct aws_byte_cursor *subscription_topic_filters; size_t subscription_topic_filter_count; From 9dfbb86f99727d70abec3c7048c2f1bbe8a1e53c Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 15 Apr 2024 16:25:52 -0700 Subject: [PATCH 113/124] Checkpoint --- .../request_response_client.h | 105 +++++++++++++++++- .../request_response_client.c | 16 +++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index fb123972..4c906281 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -48,16 +48,41 @@ typedef void(aws_mqtt_request_operation_completion_fn)( * Configuration options for a request-response operation. */ struct aws_mqtt_request_operation_options { + + /* + * Set of topic filters that should be subscribed to in order to cover all possible response paths. Sometimes + * we can use wildcards to cut down on the subscriptions needed; sometimes we can't. + */ struct aws_byte_cursor *subscription_topic_filters; size_t subscription_topic_filter_count; + /* + * Set of all possible response paths associated with this request type + */ struct aws_mqtt_request_operation_response_path *response_paths; size_t response_path_count; + /* + * Topic to publish the request to once response subscriptions have been established. + */ struct aws_byte_cursor publish_topic; + + /* + * Payload to publish in order to initiate the request + */ struct aws_byte_cursor serialized_request; + + /* + * Correlation token embedded in the request that must be found in a response message. This can be the + * empty cursor to support certain services which don't use correlation tokens. In this case, the client + * only allows one request at a time to use the associated subscriptions; no concurrency is possible. There + * are some optimizations we could make here but for now, they're not worth the complexity. + */ struct aws_byte_cursor correlation_token; + /* + * Callback (and associated user data) to invoke when the request is completed. + */ aws_mqtt_request_operation_completion_fn *completion_callback; void *user_data; }; @@ -84,36 +109,94 @@ enum aws_rr_streaming_subscription_event_type { ARRSSET_SUBSCRIPTION_HALTED, }; +/* + * Callback signature for when the subscription status of a streaming operation changes. + */ typedef void(aws_mqtt_streaming_operation_subscription_status_fn)( enum aws_rr_streaming_subscription_event_type status, int error_code, void *user_data); + +/* + * Callback signature for when a publish arrives that matches a streaming operation's subscription + */ typedef void(aws_mqtt_streaming_operation_incoming_publish_fn)(struct aws_byte_cursor payload, void *user_data); + +/* + * Callback signature for when a streaming operation is fully destroyed and no more events will be emitted. + */ typedef void(aws_mqtt_streaming_operation_terminated_fn)(void *user_data); +/* + * Configuration options for a streaming operation. + */ struct aws_mqtt_streaming_operation_options { + + /* + * Topic filter that the streaming operation should listen on + */ struct aws_byte_cursor topic_filter; + /* + * Callback for subscription status events + */ aws_mqtt_streaming_operation_subscription_status_fn *subscription_status_callback; + + /* + * Callback for publish messages that match the operation's topic filter + */ aws_mqtt_streaming_operation_incoming_publish_fn *incoming_publish_callback; + + /* + * Callback for streaming operation final shutdown + */ aws_mqtt_streaming_operation_terminated_fn *terminated_callback; + /* + * Data passed to all streaming operation callbacks + */ void *user_data; }; typedef void(aws_mqtt_request_response_client_initialized_callback_fn)(void *user_data); typedef void(aws_mqtt_request_response_client_terminated_callback_fn)(void *user_data); +/* + * Request-response client configuration options + */ struct aws_mqtt_request_response_client_options { + + /* + * Maximum number of subscriptions that the client will concurrently use for request-response operations + */ size_t max_request_response_subscriptions; + + /* + * Maximum number of subscriptions that the client will concurrently use for streaming operations + */ size_t max_streaming_subscriptions; + /* + * Duration, in seconds, that a request-response operation will wait for completion before giving up + */ uint32_t operation_timeout_seconds; - /* Do not bind the initialized callback; it exists mostly for tests and should not be exposed */ + /* + * Request-response client initialization is asynchronous. This callback is invoked when the client is fully + * initialized. + * + * Do not bind the initialized callback; it exists mostly for tests and should not be exposed + */ aws_mqtt_request_response_client_initialized_callback_fn *initialized_callback; + + /* + * Callback invoked when the client's asynchronous destruction process has fully completed. + */ aws_mqtt_request_response_client_terminated_callback_fn *terminated_callback; + /* + * Arbitrary data to pass to the client callbacks + */ void *user_data; }; @@ -142,22 +225,40 @@ AWS_MQTT_API struct aws_mqtt_request_response_client *aws_mqtt_request_response_ struct aws_mqtt_request_response_client *client); /* - * Remove a reference to a request-response client + * Remove a reference from a request-response client */ AWS_MQTT_API struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release( struct aws_mqtt_request_response_client *client); +/* + * Submits a request operation to the client + */ AWS_MQTT_API int aws_mqtt_request_response_client_submit_request( struct aws_mqtt_request_response_client *client, const struct aws_mqtt_request_operation_options *request_options); +/* + * Creates a new streaming operation. Streaming operations start in an inactive state and must be + * activated before its subscription can be established. + */ AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_streaming_operation( struct aws_mqtt_request_response_client *client, const struct aws_mqtt_streaming_operation_options *streaming_options); +/* + * Initiates a streaming operation's subscription process. + */ +AWS_MQTT_API int aws_mqtt_rr_client_operation_activate(struct aws_mqtt_rr_client_operation *operation); + +/* + * Add a reference to a streaming operation. + */ AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_acquire( struct aws_mqtt_rr_client_operation *operation); +/* + * Remove a reference from a streaming operation + */ AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_release( struct aws_mqtt_rr_client_operation *operation); diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 542bfd79..2b7b863e 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -33,6 +33,8 @@ struct aws_mqtt_streaming_operation_storage { struct aws_mqtt_streaming_operation_options options; struct aws_byte_buf operation_data; + + struct aws_atomic_var activated; }; enum aws_mqtt_request_response_operation_type { @@ -2166,6 +2168,8 @@ void s_aws_mqtt_streaming_operation_storage_init_from_options( AWS_FATAL_ASSERT( aws_byte_buf_append_and_update(&storage->operation_data, &storage->options.topic_filter) == AWS_OP_SUCCESS); + + aws_atomic_init_int(&storage->activated, 0); } static void s_log_streaming_operation( @@ -2228,6 +2232,18 @@ struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_str return operation; } +int aws_mqtt_rr_client_operation_activate(struct aws_mqtt_rr_client_operation *operation) { + struct aws_atomic_var *activated = &operation->storage.streaming_storage.activated; + size_t unactivated = 0; + if (!aws_atomic_compare_exchange_int(activated, &unactivated, 1)) { + return aws_raise_error(??); + } + + aws_event_loop_schedule_task_now(operation->client_internal_ref->loop, &operation->submit_task); + + return AWS_OP_SUCCESS; +} + struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_acquire( struct aws_mqtt_rr_client_operation *operation) { if (operation != NULL) { From db285e39f4f0d6f9903644f08e4cd7aceddadca6 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 16 Apr 2024 10:45:45 -0700 Subject: [PATCH 114/124] Activate API, public API comments --- bin/elastishadow/main.c | 8 ++- include/aws/mqtt/mqtt.h | 1 + source/mqtt.c | 3 + .../request_response_client.c | 34 ++++++---- tests/CMakeLists.txt | 3 +- .../request_response_client_tests.c | 65 ++++++++++++------- 6 files changed, 76 insertions(+), 38 deletions(-) diff --git a/bin/elastishadow/main.c b/bin/elastishadow/main.c index c357bf94..f7c6ddaa 100644 --- a/bin/elastishadow/main.c +++ b/bin/elastishadow/main.c @@ -819,7 +819,8 @@ static void s_handle_open_delta_stream( operation->streaming_operation = aws_mqtt_request_response_client_create_streaming_operation(context->rr_client, &open_stream_options); - if (operation->streaming_operation == NULL) { + if (operation->streaming_operation == NULL || + aws_mqtt_rr_client_operation_activate(operation->streaming_operation)) { int error_code = aws_last_error(); printf( "NamedShadow delta stream synchronous open failure: %d(%s)\n", error_code, aws_error_debug_str(error_code)); @@ -882,7 +883,8 @@ static void s_handle_open_document_stream( operation->streaming_operation = aws_mqtt_request_response_client_create_streaming_operation(context->rr_client, &open_stream_options); - if (operation->streaming_operation == NULL) { + if (operation->streaming_operation == NULL || + aws_mqtt_rr_client_operation_activate(operation->streaming_operation)) { int error_code = aws_last_error(); printf( "NamedShadow document stream synchronous open failure: %d(%s)\n", @@ -1062,7 +1064,7 @@ int main(int argc, char **argv) { &app_ctx.streaming_operations, allocator, 10, - aws_hash_uint64_t, + aws_hash_uint64_t_by_identity, aws_hash_compare_uint64_t_eq, NULL, s_release_streaming_operation); diff --git a/include/aws/mqtt/mqtt.h b/include/aws/mqtt/mqtt.h index bfdd73ff..902500de 100644 --- a/include/aws/mqtt/mqtt.h +++ b/include/aws/mqtt/mqtt.h @@ -88,6 +88,7 @@ enum aws_mqtt_error { AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE, AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR, AWS_ERROR_MQTT_REQUEST_RESPONSE_PUBLISH_FAILURE, + AWS_ERROR_MQTT_REUQEST_RESPONSE_STREAM_ALREADY_ACTIVATED, AWS_ERROR_END_MQTT_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_MQTT_PACKAGE_ID), }; diff --git a/source/mqtt.c b/source/mqtt.c index 9a7d7662..b3f6ddd0 100644 --- a/source/mqtt.c +++ b/source/mqtt.c @@ -254,6 +254,9 @@ bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter) AWS_DEFINE_ERROR_INFO_MQTT( AWS_ERROR_MQTT_REQUEST_RESPONSE_PUBLISH_FAILURE, "Request-response operation failed because the associated publish failed synchronously."), + AWS_DEFINE_ERROR_INFO_MQTT( + AWS_ERROR_MQTT_REUQEST_RESPONSE_STREAM_ALREADY_ACTIVATED, + "Streaming operation activation failed because the operaation had already been activated."), }; /* clang-format on */ diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 2b7b863e..a3f81000 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -1159,7 +1159,7 @@ static struct aws_mqtt_request_response_client *s_aws_mqtt_request_response_clie &rr_client->operations, allocator, MQTT_RR_CLIENT_OPERATION_TABLE_DEFAULT_SIZE, - aws_hash_uint64_t, + aws_hash_uint64_t_by_identity, aws_hash_compare_uint64_t_eq, NULL, NULL); @@ -1941,12 +1941,6 @@ static void s_aws_mqtt_rr_client_operation_init_shared( operation->allocator = client->allocator; aws_ref_count_init(&operation->ref_count, operation, s_on_mqtt_rr_client_operation_zero_ref_count); - /* - * We hold a second reference to the operation during submission. This ensures that even if a streaming operation - * is immediately dec-refed by the creator (before submission runs), the operation will not get destroyed. - */ - aws_mqtt_rr_client_operation_acquire(operation); - operation->client_internal_ref = aws_mqtt_request_response_client_acquire_internal(client); operation->id = s_aws_mqtt_request_response_client_allocate_operation_id(client); s_change_operation_state(operation, AWS_MRROS_NONE); @@ -2152,6 +2146,12 @@ int aws_mqtt_request_response_client_submit_request( s_log_request_response_operation(operation, client); + /* + * We hold a second reference to the operation during submission. This ensures that even if a streaming operation + * is immediately dec-refed by the creator (before submission runs), the operation will not get destroyed. + */ + aws_mqtt_rr_client_operation_acquire(operation); + aws_event_loop_schedule_task_now(client->loop, &operation->submit_task); return AWS_OP_SUCCESS; @@ -2227,8 +2227,6 @@ struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_client_create_str s_log_streaming_operation(operation, client); - aws_event_loop_schedule_task_now(client->loop, &operation->submit_task); - return operation; } @@ -2236,10 +2234,24 @@ int aws_mqtt_rr_client_operation_activate(struct aws_mqtt_rr_client_operation *o struct aws_atomic_var *activated = &operation->storage.streaming_storage.activated; size_t unactivated = 0; if (!aws_atomic_compare_exchange_int(activated, &unactivated, 1)) { - return aws_raise_error(??); + return aws_raise_error(AWS_ERROR_MQTT_REUQEST_RESPONSE_STREAM_ALREADY_ACTIVATED); } - aws_event_loop_schedule_task_now(operation->client_internal_ref->loop, &operation->submit_task); + struct aws_mqtt_request_response_client *rr_client = operation->client_internal_ref; + + AWS_LOGF_DEBUG( + AWS_LS_MQTT_REQUEST_RESPONSE, + "id=%p: request-response client - activating streaming operation with id %" PRIu64, + (void *)rr_client, + operation->id); + + /* + * We hold a second reference to the operation during submission. This ensures that even if a streaming operation + * is immediately dec-refed by the creator (before submission runs), the operation will not get destroyed. + */ + aws_mqtt_rr_client_operation_acquire(operation); + + aws_event_loop_schedule_task_now(rr_client->loop, &operation->submit_task); return AWS_OP_SUCCESS; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3fe1c582..f4836b7e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -543,7 +543,8 @@ add_test_case(rrc_submit_request_operation_failure_empty_request) add_test_case(rrc_submit_streaming_operation_failure_invalid_subscription_topic_filter) add_test_case(rrc_submit_request_operation_failure_by_shutdown) -add_test_case(rrc_submit_streaming_operation_and_shutdown) +add_test_case(rrc_create_streaming_operation_and_shutdown) +add_test_case(rrc_activate_streaming_operation_and_shutdown) add_test_case(rrc_submit_request_operation_failure_by_timeout) add_test_case(rrc_streaming_operation_success_single) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index 9360f01f..fa315bc2 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -888,6 +888,7 @@ static int s_rrc_submit_streaming_operation_failure_invalid_subscription_topic_f struct aws_mqtt_rr_client_operation *good_operation = aws_mqtt_request_response_client_create_streaming_operation(fixture.rr_client, &good_options); ASSERT_NOT_NULL(good_operation); + ASSERT_SUCCESS(aws_mqtt_rr_client_operation_activate(good_operation)); aws_mqtt_rr_client_operation_release(good_operation); @@ -988,7 +989,7 @@ static int s_do_rrc_single_streaming_operation_test_fn( struct aws_mqtt_streaming_operation_options *streaming_options, size_t expected_subscription_event_count, struct aws_rr_client_fixture_streaming_record_subscription_event *expected_subscription_events, - bool shutdown_after_submit) { + bool should_activate) { aws_mqtt_library_init(allocator); struct mqtt5_client_test_options client_test_options; @@ -1015,27 +1016,29 @@ static int s_do_rrc_single_streaming_operation_test_fn( aws_mqtt_request_response_client_create_streaming_operation(fixture.rr_client, streaming_options); ASSERT_NOT_NULL(streaming_operation); - if (shutdown_after_submit) { - aws_mqtt_request_response_client_release(fixture.rr_client); - fixture.rr_client = NULL; - - /* - * Extremely awkward sleep: - * - * We've submitted the operation and we've decref'd the client to zero. When the operation submit task - * is processed, if the release in the succeeding line has happened-before the client external destroy task - * has run, then the operation's destory will be scheduled in-thread and run ahead of the client external - * destroy. This doesn't break correctness, but it does prevent the client from emitting a HALTED event - * on the subscription because the subscription/operation will be gone before the client external destroy - * task runs. - * - * So we add a nice, fat sleep to guarantee that the client external destroy task runs before the operation - * destroy task. - */ - aws_thread_current_sleep(aws_timestamp_convert(1, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); - aws_mqtt_rr_client_operation_release(streaming_operation); + if (should_activate) { + ASSERT_SUCCESS(aws_mqtt_rr_client_operation_activate(streaming_operation)); } + aws_mqtt_request_response_client_release(fixture.rr_client); + fixture.rr_client = NULL; + + /* + * Extremely awkward sleep: + * + * We've submitted the operation and we've decref'd the client to zero. When the operation submit task + * is processed, if the release in the succeeding line has happened-before the client external destroy task + * has run, then the operation's destory will be scheduled in-thread and run ahead of the client external + * destroy. This doesn't break correctness, but it does prevent the client from emitting a HALTED event + * on the subscription because the subscription/operation will be gone before the client external destroy + * task runs. + * + * So we add a nice, fat sleep to guarantee that the client external destroy task runs before the operation + * destroy task. + */ + aws_thread_current_sleep(aws_timestamp_convert(1, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); + aws_mqtt_rr_client_operation_release(streaming_operation); + s_rrc_wait_on_streaming_termination(&fixture, streaming_id); ASSERT_SUCCESS(s_rrc_verify_streaming_record_subscription_events( @@ -1048,7 +1051,7 @@ static int s_do_rrc_single_streaming_operation_test_fn( return AWS_OP_SUCCESS; } -static int s_rrc_submit_streaming_operation_and_shutdown_fn(struct aws_allocator *allocator, void *ctx) { +static int s_rrc_activate_streaming_operation_and_shutdown_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; struct aws_rr_client_fixture_streaming_record_subscription_event expected_events[] = { @@ -1066,7 +1069,19 @@ static int s_rrc_submit_streaming_operation_and_shutdown_fn(struct aws_allocator allocator, NULL, &streaming_options, AWS_ARRAY_SIZE(expected_events), expected_events, true); } -AWS_TEST_CASE(rrc_submit_streaming_operation_and_shutdown, s_rrc_submit_streaming_operation_and_shutdown_fn) +AWS_TEST_CASE(rrc_activate_streaming_operation_and_shutdown, s_rrc_activate_streaming_operation_and_shutdown_fn) + +static int s_rrc_create_streaming_operation_and_shutdown_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_mqtt_streaming_operation_options streaming_options = { + .topic_filter = aws_byte_cursor_from_c_str("derp/filter"), + }; + + return s_do_rrc_single_streaming_operation_test_fn(allocator, NULL, &streaming_options, 0, NULL, false); +} + +AWS_TEST_CASE(rrc_create_streaming_operation_and_shutdown, s_rrc_create_streaming_operation_and_shutdown_fn) static int s_rrc_submit_request_operation_failure_by_timeout_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -1114,7 +1129,11 @@ static struct aws_mqtt_rr_client_operation *s_create_streaming_operation( streaming_options.terminated_callback = s_rrc_fixture_streaming_operation_terminated_callback; streaming_options.user_data = record; - return aws_mqtt_request_response_client_create_streaming_operation(fixture->rr_client, &streaming_options); + struct aws_mqtt_rr_client_operation *streaming_operation = + aws_mqtt_request_response_client_create_streaming_operation(fixture->rr_client, &streaming_options); + aws_mqtt_rr_client_operation_activate(streaming_operation); + + return streaming_operation; } static int s_rrc_publish_5( From 13faf2f963b2010a6f1bd34447896e359e81a4a8 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 19 Jul 2024 09:26:04 -0700 Subject: [PATCH 115/124] Bad error handling in acquire subscription --- source/request-response/subscription_manager.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 522f9738..3928ca30 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -445,7 +445,12 @@ enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_su const struct aws_rr_acquire_subscription_options *options) { if (options->topic_filter_count == 0) { - return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + AWS_LOGF_ERROR( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - acquire_subscription for operation %" PRIu64 + " with no topic filters", + options->operation_id); + return AASRT_FAILURE; } /* From 840332a3051ad049060505b766f41d77c859a8bc Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 25 Jul 2024 08:59:26 -0700 Subject: [PATCH 116/124] Comment updates --- .../aws/mqtt/request-response/request_response_client.h | 2 +- source/request-response/request_response_client.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 4c906281..55c8ae6c 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -88,7 +88,7 @@ struct aws_mqtt_request_operation_options { }; /* - * Describes a change to the state of a request operation subscription + * Describes a change to the state of a streaming operation subscription */ enum aws_rr_streaming_subscription_event_type { diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index a3f81000..35a6cce2 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -119,18 +119,18 @@ const char *s_aws_acquire_subscription_result_type(enum aws_acquire_subscription Client Tables/Lookups - (Authoritative operation container) + (operations: authoritative operation container) 1. &operation.id -> &operation // added on in-thread enqueue, removed on operation completion/destruction - (Response path topic -> Correlation token extraction info) + (request_response_paths: response path topic -> Correlation token extraction info) 2. &topic -> &{topic, topic_buffer, correlation token json path buffer} // ref-counted, per-message-path add on request dequeue into subscribing/subscribed state, decref/removed on operation completion/destruction - (CorrelationToken -> request operation) + (operations_by_correlation_tokens: correlationToken -> request operation) 3. &operation.correlation token -> (request) &operation // added on request dequeue into subscribing/subscribed state, removed on operation completion/destruction - (Streaming subscription filter -> list of all operations using that filter) + (streaming_operation_subscription_lists: streaming subscription filter -> list of all operations using that filter) 4. &topic_filter -> &{topic_filter, linked_list} // added on request dequeue into subscribing/subscribed state, removed from list on operation completion/destruction also checked for empty and removed from table From 9b9ed5f63480e391352d89cc636531034c4c0450 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 30 Jul 2024 09:12:12 -0700 Subject: [PATCH 117/124] Comment correction --- tests/request-response/subscription_manager_tests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/request-response/subscription_manager_tests.c b/tests/request-response/subscription_manager_tests.c index 443d1b0a..cf55fe2f 100644 --- a/tests/request-response/subscription_manager_tests.c +++ b/tests/request-response/subscription_manager_tests.c @@ -1171,7 +1171,7 @@ static int s_do_acquire_no_capacity_test(struct aws_subscription_manager_test_fi } /* - * Verify: Acquiring a new eventstream subscription when the budget is 1 returns NO_CAPACITY + * Verify: Acquiring a new eventstream subscription when the budget is 0 returns NO_CAPACITY */ static int s_rrsm_acquire_no_capacity_max1_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; From ed47f72aada97e9722cca98a5f2da1f0b9223421 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 13 Aug 2024 10:18:51 -0700 Subject: [PATCH 118/124] Use common API --- source/request-response/request_response_client.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 35a6cce2..95458af6 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -1282,11 +1282,6 @@ static uint64_t s_mqtt_request_response_client_get_next_service_time(struct aws_ return UINT64_MAX; } -/* TODO: add aws-c-common API? */ -static bool s_is_operation_in_list(const struct aws_mqtt_rr_client_operation *operation) { - return aws_linked_list_node_prev_is_valid(&operation->node) && aws_linked_list_node_next_is_valid(&operation->node); -} - static int s_add_streaming_operation_to_subscription_topic_filter_table( struct aws_mqtt_request_response_client *client, struct aws_mqtt_rr_client_operation *operation) { @@ -1313,7 +1308,7 @@ static int s_add_streaming_operation_to_subscription_topic_filter_table( AWS_FATAL_ASSERT(entry != NULL); - if (s_is_operation_in_list(operation)) { + if (aws_linked_list_node_is_in_list(&operation->node)) { aws_linked_list_remove(&operation->node); } @@ -1892,7 +1887,7 @@ static void s_mqtt_rr_client_destroy_operation(struct aws_task *task, void *arg, aws_hash_table_remove(&client->operations, &operation->id, NULL, NULL); s_remove_operation_from_timeout_queue(operation); - if (s_is_operation_in_list(operation)) { + if (aws_linked_list_node_is_in_list(&operation->node)) { aws_linked_list_remove(&operation->node); } From ccdaeac6e72e96fc7cd8c46a630f42b8aa51aea4 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 9 Sep 2024 09:37:34 -0700 Subject: [PATCH 119/124] Typo --- tests/request-response/request_response_client_tests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/request-response/request_response_client_tests.c b/tests/request-response/request_response_client_tests.c index fa315bc2..a1b55f1d 100644 --- a/tests/request-response/request_response_client_tests.c +++ b/tests/request-response/request_response_client_tests.c @@ -1028,7 +1028,7 @@ static int s_do_rrc_single_streaming_operation_test_fn( * * We've submitted the operation and we've decref'd the client to zero. When the operation submit task * is processed, if the release in the succeeding line has happened-before the client external destroy task - * has run, then the operation's destory will be scheduled in-thread and run ahead of the client external + * has run, then the operation's destroy will be scheduled in-thread and run ahead of the client external * destroy. This doesn't break correctness, but it does prevent the client from emitting a HALTED event * on the subscription because the subscription/operation will be gone before the client external destroy * task runs. From b50ec89d2a590876d40831dc5c9f822e1f73c6a2 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 9 Sep 2024 09:41:34 -0700 Subject: [PATCH 120/124] Format --- tests/v3/connection_state_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/v3/connection_state_test.c b/tests/v3/connection_state_test.c index 2f20b848..d895b44a 100644 --- a/tests/v3/connection_state_test.c +++ b/tests/v3/connection_state_test.c @@ -2436,8 +2436,8 @@ static int s_test_mqtt_connection_publish_QoS1_timeout_with_ping_fn(struct aws_a /* publish should complete after the shutdown */ aws_test311_wait_for_ops_completed(state_test_data); - ASSERT_SUCCESS( - aws_mqtt_client_connection_disconnect(state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); + ASSERT_SUCCESS(aws_mqtt_client_connection_disconnect( + state_test_data->mqtt_connection, aws_test311_on_disconnect_fn, state_test_data)); aws_test311_wait_for_disconnect_to_complete(state_test_data); return AWS_OP_SUCCESS; From 9614d92c5980164f3f90c255d36e1f3a7d4d307c Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 9 Sep 2024 10:18:25 -0700 Subject: [PATCH 121/124] Update builder --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd2e3cf4..76bf97d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: - 'main' env: - BUILDER_VERSION: v0.9.62 + BUILDER_VERSION: v0.9.64 BUILDER_SOURCE: releases BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net PACKAGE_NAME: aws-c-mqtt From 64758251cdf7c8d9b7a3a637cae5c6f6a85b752b Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 12 Sep 2024 09:31:43 -0700 Subject: [PATCH 122/124] Event loop accessor for request response client --- .../request-response/protocol_adapter.h | 8 ++++++++ .../request_response_client.h | 7 +++++++ source/request-response/protocol_adapter.c | 19 ++++++++++++++++++- .../request_response_client.c | 6 ++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/include/aws/mqtt/private/request-response/protocol_adapter.h b/include/aws/mqtt/private/request-response/protocol_adapter.h index a4896c68..7d71e805 100644 --- a/include/aws/mqtt/private/request-response/protocol_adapter.h +++ b/include/aws/mqtt/private/request-response/protocol_adapter.h @@ -12,6 +12,7 @@ #include struct aws_allocator; +struct aws_event_loop; struct aws_mqtt_client_connection; struct aws_mqtt5_client; @@ -143,6 +144,8 @@ struct aws_mqtt_protocol_adapter_vtable { int (*aws_mqtt_protocol_adapter_publish_fn)(void *, struct aws_protocol_adapter_publish_options *); bool (*aws_mqtt_protocol_adapter_is_connected_fn)(void *); + + struct aws_event_loop *(*aws_mqtt_protocol_adapter_get_event_loop_fn)(void *); }; struct aws_mqtt_protocol_adapter { @@ -201,6 +204,11 @@ AWS_MQTT_API int aws_mqtt_protocol_adapter_publish( */ AWS_MQTT_API bool aws_mqtt_protocol_adapter_is_connected(struct aws_mqtt_protocol_adapter *adapter); +/* + * Returns the event loop that the protocol client is bound to. + */ +AWS_MQTT_API struct aws_event_loop *aws_mqtt_protocol_adapter_get_event_loop(struct aws_mqtt_protocol_adapter *adapter); + AWS_MQTT_API const char *aws_protocol_adapter_subscription_event_type_to_c_str( enum aws_protocol_adapter_subscription_event_type type); diff --git a/include/aws/mqtt/request-response/request_response_client.h b/include/aws/mqtt/request-response/request_response_client.h index 55c8ae6c..3edff934 100644 --- a/include/aws/mqtt/request-response/request_response_client.h +++ b/include/aws/mqtt/request-response/request_response_client.h @@ -8,6 +8,7 @@ #include +struct aws_event_loop; struct aws_mqtt_request_response_client; struct aws_mqtt_client_connection; struct aws_mqtt5_client; @@ -245,6 +246,12 @@ AWS_MQTT_API struct aws_mqtt_rr_client_operation *aws_mqtt_request_response_clie struct aws_mqtt_request_response_client *client, const struct aws_mqtt_streaming_operation_options *streaming_options); +/* + * Returns the event loop used by the request-response client's protocol client + */ +AWS_MQTT_API struct aws_event_loop *aws_mqtt_request_response_client_get_event_loop( + struct aws_mqtt_request_response_client *client); + /* * Initiates a streaming operation's subscription process. */ diff --git a/source/request-response/protocol_adapter.c b/source/request-response/protocol_adapter.c index 8aa244cb..76aea9c4 100644 --- a/source/request-response/protocol_adapter.c +++ b/source/request-response/protocol_adapter.c @@ -457,12 +457,19 @@ static void s_protocol_adapter_mqtt311_listener_termination_callback(void *user_ } } +static struct aws_event_loop *s_aws_mqtt_protocol_adapter_311_get_event_loop(void *impl) { + struct aws_mqtt_protocol_adapter_311_impl *adapter = impl; + + return adapter->loop; +} + static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt311_vtable = { .aws_mqtt_protocol_adapter_destroy_fn = s_aws_mqtt_protocol_adapter_311_destroy, .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_311_subscribe, .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_311_unsubscribe, .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_311_publish, .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_311_is_connected, + .aws_mqtt_protocol_adapter_get_event_loop_fn = s_aws_mqtt_protocol_adapter_311_get_event_loop, }; struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( @@ -848,13 +855,19 @@ static void s_protocol_adapter_mqtt5_listener_termination_callback(void *user_da } } +static struct aws_event_loop *s_aws_mqtt_protocol_adapter_5_get_event_loop(void *impl) { + struct aws_mqtt_protocol_adapter_5_impl *adapter = impl; + + return adapter->loop; +} + static struct aws_mqtt_protocol_adapter_vtable s_protocol_adapter_mqtt5_vtable = { .aws_mqtt_protocol_adapter_destroy_fn = s_aws_mqtt_protocol_adapter_5_destroy, .aws_mqtt_protocol_adapter_subscribe_fn = s_aws_mqtt_protocol_adapter_5_subscribe, .aws_mqtt_protocol_adapter_unsubscribe_fn = s_aws_mqtt_protocol_adapter_5_unsubscribe, .aws_mqtt_protocol_adapter_publish_fn = s_aws_mqtt_protocol_adapter_5_publish, .aws_mqtt_protocol_adapter_is_connected_fn = s_aws_mqtt_protocol_adapter_5_is_connected, -}; + .aws_mqtt_protocol_adapter_get_event_loop_fn = s_aws_mqtt_protocol_adapter_5_get_event_loop}; struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( struct aws_allocator *allocator, @@ -921,6 +934,10 @@ bool aws_mqtt_protocol_adapter_is_connected(struct aws_mqtt_protocol_adapter *ad return (*adapter->vtable->aws_mqtt_protocol_adapter_is_connected_fn)(adapter->impl); } +struct aws_event_loop *aws_mqtt_protocol_adapter_get_event_loop(struct aws_mqtt_protocol_adapter *adapter) { + return (*adapter->vtable->aws_mqtt_protocol_adapter_get_event_loop_fn)(adapter->impl); +} + const char *aws_protocol_adapter_subscription_event_type_to_c_str( enum aws_protocol_adapter_subscription_event_type type) { switch (type) { diff --git a/source/request-response/request_response_client.c b/source/request-response/request_response_client.c index 95458af6..eeb4ffc8 100644 --- a/source/request-response/request_response_client.c +++ b/source/request-response/request_response_client.c @@ -2268,3 +2268,9 @@ struct aws_mqtt_rr_client_operation *aws_mqtt_rr_client_operation_release( return NULL; } + +struct aws_event_loop *aws_mqtt_request_response_client_get_event_loop( + struct aws_mqtt_request_response_client *client) { + + return aws_mqtt_protocol_adapter_get_event_loop(client->client_adapter); +} From bc2375f4576ae00db9c0bd4974f8099b0762ff2b Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 12 Sep 2024 12:44:12 -0700 Subject: [PATCH 123/124] Misc PR feedback --- bin/elastishadow/main.c | 15 ++++++++++++--- source/request-response/subscription_manager.c | 5 +++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/bin/elastishadow/main.c b/bin/elastishadow/main.c index f7c6ddaa..b9d15e87 100644 --- a/bin/elastishadow/main.c +++ b/bin/elastishadow/main.c @@ -98,6 +98,7 @@ void s_aws_shadow_streaming_operation_destroy(struct aws_shadow_streaming_operat aws_byte_buf_clean_up(&operation->thing); aws_byte_buf_clean_up(&operation->shadow); + aws_byte_buf_clean_up(&operation->topic_filter); aws_mem_release(operation->allocator, operation); } @@ -315,6 +316,7 @@ static void s_handle_get(struct app_ctx *context, struct aws_allocator *allocato char correlation_token[128]; s_write_correlation_token_string(aws_byte_cursor_from_array(correlation_token, AWS_ARRAY_SIZE(correlation_token))); + struct aws_string *correlation_token_string = aws_string_new_from_c_str(allocator, correlation_token); char request[256]; snprintf(request, AWS_ARRAY_SIZE(request), "{\"clientToken\":\"%s\"}", correlation_token); @@ -328,7 +330,7 @@ static void s_handle_get(struct app_ctx *context, struct aws_allocator *allocato .serialized_request = aws_byte_cursor_from_c_str(request), .correlation_token = aws_byte_cursor_from_c_str(correlation_token), .completion_callback = s_on_get_shadow_complete, - .user_data = aws_string_new_from_c_str(allocator, correlation_token), + .user_data = correlation_token_string, }; printf( @@ -341,6 +343,7 @@ static void s_handle_get(struct app_ctx *context, struct aws_allocator *allocato if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { int error_code = aws_last_error(); printf("GetNamedShadow synchronous failure: %d(%s)\n", error_code, aws_error_debug_str(error_code)); + aws_string_destroy(correlation_token_string); } printf("\n"); @@ -447,6 +450,8 @@ static void s_handle_update( AWS_BYTE_CURSOR_PRI(thing_name_cursor), AWS_BYTE_CURSOR_PRI(shadow_name_cursor)); + struct aws_string *correlation_token_string = aws_string_new_from_cursor(allocator, &correlation_token); + struct aws_mqtt_request_operation_options get_options = { .subscription_topic_filters = subscription_topic_filters, .subscription_topic_filter_count = 2, @@ -456,7 +461,7 @@ static void s_handle_update( .serialized_request = desired_state, .correlation_token = correlation_token, .completion_callback = s_on_update_shadow_complete, - .user_data = aws_string_new_from_cursor(allocator, &correlation_token), + .user_data = correlation_token_string, }; printf( @@ -469,6 +474,7 @@ static void s_handle_update( if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { int error_code = aws_last_error(); printf("UpdateNamedShadow synchronous failure: %d(%s)\n", error_code, aws_error_debug_str(error_code)); + aws_string_destroy(correlation_token_string); } printf("\n"); @@ -646,6 +652,7 @@ static void s_handle_delete( char request[256]; snprintf(request, AWS_ARRAY_SIZE(request), "{\"clientToken\":\"%s\"}", correlation_token); + struct aws_string *correlation_token_string = aws_string_new_from_c_str(allocator, correlation_token); struct aws_mqtt_request_operation_options get_options = { .subscription_topic_filters = &subscription_topic_filter_cursor, @@ -656,7 +663,7 @@ static void s_handle_delete( .serialized_request = aws_byte_cursor_from_c_str(request), .correlation_token = aws_byte_cursor_from_c_str(correlation_token), .completion_callback = s_on_delete_shadow_complete, - .user_data = aws_string_new_from_c_str(allocator, correlation_token), + .user_data = correlation_token_string, }; printf( @@ -669,6 +676,7 @@ static void s_handle_delete( if (aws_mqtt_request_response_client_submit_request(context->rr_client, &get_options) == AWS_OP_ERR) { int error_code = aws_last_error(); printf("DeleteNamedShadow synchronous failure: %d(%s)\n", error_code, aws_error_debug_str(error_code)); + aws_string_destroy(correlation_token_string); } printf("\n"); @@ -970,6 +978,7 @@ static bool s_handle_input(struct app_ctx *context, struct aws_allocator *alloca printf("Starting client!\n"); aws_mqtt5_client_start(client); } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &stop_cursor)) { + printf("Stopping client!\n"); aws_mqtt5_client_stop(client, NULL, NULL); } else if (aws_byte_cursor_eq_ignore_case(&command_cursor, &get_cursor)) { s_handle_get(context, allocator, &words); diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 3928ca30..15a88562 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -400,6 +400,11 @@ static int s_rr_activate_idle_subscription( int result = AWS_OP_SUCCESS; if (record->poisoned) { + AWS_LOGF_WARN( + AWS_LS_MQTT_REQUEST_RESPONSE, + "request-response subscription manager - attempt to activate subscription for ('" PRInSTR + "') failed - existing subscription is poisoned and has not been released", + AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); return AWS_OP_SUCCESS; } From 08ef51d459a047e703cdf09bff1fc1b4bb6a33bd Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Thu, 12 Sep 2024 12:54:58 -0700 Subject: [PATCH 124/124] Explanatory comment --- source/request-response/subscription_manager.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/request-response/subscription_manager.c b/source/request-response/subscription_manager.c index 15a88562..88cc5428 100644 --- a/source/request-response/subscription_manager.c +++ b/source/request-response/subscription_manager.c @@ -400,11 +400,10 @@ static int s_rr_activate_idle_subscription( int result = AWS_OP_SUCCESS; if (record->poisoned) { - AWS_LOGF_WARN( - AWS_LS_MQTT_REQUEST_RESPONSE, - "request-response subscription manager - attempt to activate subscription for ('" PRInSTR - "') failed - existing subscription is poisoned and has not been released", - AWS_BYTE_CURSOR_PRI(record->topic_filter_cursor)); + /* + * Don't try and establish poisoned subscriptions. This is not an error or a loggable event, it just means + * we hit a "try and make subscriptions" event when a poisoned subscription still hadn't been fully released. + */ return AWS_OP_SUCCESS; }