From b7de17b8f3cceea759d4e7525aeb71166be08912 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 29 Nov 2023 11:55:33 -0800 Subject: [PATCH 01/20] 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 02/20] 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 03/20] 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 04/20] 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 05/20] 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 13/20] 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 b1fe1f93c0e1fd46c00fbef8463c854fecd18faf Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 24 Jan 2024 11:02:22 -0800 Subject: [PATCH 14/20] 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 e5f93e1e3cfa81e67c8bc85fbec86d0145bb6bc4 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 24 Jan 2024 15:24:15 -0800 Subject: [PATCH 15/20] 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 16/20] 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 abbcdc1688f4f3af72003218433959b63178887a Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 26 Jan 2024 07:26:48 -0800 Subject: [PATCH 17/20] 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 18/20] 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 4783ea1cfed65a8c2ab57e325db5ad32a5c25071 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 30 Jan 2024 11:06:17 -0800 Subject: [PATCH 19/20] 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 38532f0b8a5aec5a8988cea2cbab88912688794e Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Tue, 30 Jan 2024 12:17:17 -0800 Subject: [PATCH 20/20] 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;