Skip to content

Commit 1d512d9

Browse files
bretambroseBret Ambrose
andauthored
Auto-generate client ids in MQTT5 (#404)
Co-authored-by: Bret Ambrose <bambrose@amazon.com>
1 parent 4329910 commit 1d512d9

File tree

3 files changed

+118
-3
lines changed

3 files changed

+118
-3
lines changed

source/v5/mqtt5_options_storage.c

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
#include <aws/common/clock.h>
99
#include <aws/common/encoding.h>
1010
#include <aws/common/string.h>
11+
#include <aws/common/uuid.h>
1112
#include <aws/io/channel_bootstrap.h>
1213
#include <aws/io/event_loop.h>
13-
#include <aws/io/stream.h>
1414
#include <aws/mqtt/private/v5/mqtt5_client_impl.h>
1515
#include <aws/mqtt/private/v5/mqtt5_utils.h>
1616
#include <aws/mqtt/v5/mqtt5_client.h>
@@ -3870,8 +3870,70 @@ struct aws_mqtt5_client_options_storage *aws_mqtt5_client_options_storage_new(
38703870
options_storage->topic_aliasing_options = *options->topic_aliasing_options;
38713871
}
38723872

3873+
struct aws_byte_buf auto_assign_id_buf;
3874+
AWS_ZERO_STRUCT(auto_assign_id_buf);
3875+
3876+
struct aws_mqtt5_packet_connect_view connect_options = *options->connect_options;
3877+
if (connect_options.client_id.len == 0) {
3878+
if (options->extended_validation_and_flow_control_options == AWS_MQTT5_EVAFCO_AWS_IOT_CORE_DEFAULTS) {
3879+
/*
3880+
* We're (probably) using an SDK builder to create the client and there's no client id set. Assume this is
3881+
* targeting IoT Core. Iot Core is not compliant to the MQTT311/5 spec with regard to auto-assigned client
3882+
* ids.
3883+
*
3884+
* In particular, IoT Core makes a client id of the pattern "$GEN/[uuid]" but it forbids a client id of that
3885+
* pattern to be specified by the user. This is contrary to the spec that requires an auto-assigned client
3886+
* id to be able to reconnect using the same client id:
3887+
*
3888+
* "It MUST then process the CONNECT packet as if the Client had provided that unique ClientID, and MUST
3889+
* return the Assigned Client Identifier in the CONNACK packet."
3890+
*
3891+
* See (https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901059) for details.
3892+
*
3893+
* The result is that using auto assigned client ids with IoT Core leads to permanent reconnect failures
3894+
* because we use the client id value returned in the CONNACK for all subsequent connection attempts. This
3895+
* behavioral choice varies across MQTT clients, but I am firmly convinced it is proper.
3896+
*
3897+
* To work around this issue, when we think we're connecting to IoT Core (ie the condition above) and we
3898+
* don't have a client id, we generate one of the form "gen[uuid]" which will be able to successfully
3899+
* reconnect since it does not use the reserved prefix "$GEN/"
3900+
*/
3901+
aws_byte_buf_init(&auto_assign_id_buf, allocator, 64);
3902+
struct aws_byte_cursor auto_assign_prefix_cursor = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("gen");
3903+
3904+
aws_byte_buf_append(&auto_assign_id_buf, &auto_assign_prefix_cursor);
3905+
3906+
struct aws_uuid uuid;
3907+
AWS_FATAL_ASSERT(aws_uuid_init(&uuid) == AWS_OP_SUCCESS);
3908+
3909+
aws_uuid_to_str_compact(&uuid, &auto_assign_id_buf);
3910+
3911+
connect_options.client_id = aws_byte_cursor_from_buf(&auto_assign_id_buf);
3912+
3913+
AWS_LOGF_INFO(
3914+
AWS_LS_MQTT5_GENERAL,
3915+
"Inferring the server is IoT Core and no client id has been set. IoT Core's auto-assigned client ids "
3916+
"cannot be reconnected with. Avoiding this issue by locally setting the client id to \"" PRInSTR "\"",
3917+
AWS_BYTE_CURSOR_PRI(connect_options.client_id));
3918+
} else {
3919+
/*
3920+
* Log the fact that we're not manually generating the client id in case someone turns off iot core
3921+
* validation and then sees reconnect failures.
3922+
*/
3923+
AWS_LOGF_WARN(
3924+
AWS_LS_MQTT5_GENERAL,
3925+
"Letting server assign the client id. If reconnects fail with a connect reason code of "
3926+
"AWS_MQTT5_CRC_CLIENT_IDENTIFIER_NOT_VALID (133), then consider manually setting the client id to a "
3927+
"UUID.");
3928+
}
3929+
}
3930+
38733931
options_storage->connect = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_packet_connect_storage));
3874-
if (aws_mqtt5_packet_connect_storage_init(options_storage->connect, allocator, options->connect_options)) {
3932+
int connect_storage_result =
3933+
aws_mqtt5_packet_connect_storage_init(options_storage->connect, allocator, &connect_options);
3934+
3935+
aws_byte_buf_clean_up(&auto_assign_id_buf);
3936+
if (connect_storage_result != AWS_OP_SUCCESS) {
38753937
goto error;
38763938
}
38773939

tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ add_test_case(mqtt5_first_byte_reserved_header_check_subscribe)
323323
add_test_case(mqtt5_first_byte_reserved_header_check_unsubscribe)
324324
add_test_case(mqtt5_first_byte_reserved_header_check_disconnect)
325325

326+
add_test_case(mqtt5_client_auto_assigned_client_id)
327+
add_test_case(mqtt5_client_auto_assigned_client_id_iot_core)
326328
add_test_case(mqtt5_client_direct_connect_success)
327329
add_test_case(mqtt5_client_direct_connect_sync_channel_failure)
328330
add_test_case(mqtt5_client_direct_connect_async_channel_failure)

tests/v5/mqtt5_client_tests.c

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <aws/testing/aws_test_harness.h>
1919

2020
#include <math.h>
21+
2122
/* The delay margin accounts for system-level latencies, particularly when scheduling tasks on dispatch queues. In
2223
* typical test environments, we observe delays of around 150–170ms before the task begins execution. To ensure
2324
* stability, we define a margin of 200ms (AWS_MQTT5_TESTING_DELAY_NS). If related tests fail, it may indicate this
@@ -6600,4 +6601,54 @@ static int s_mqtt5_client_dynamic_operation_timeout_default_fn(struct aws_alloca
66006601
return AWS_OP_SUCCESS;
66016602
}
66026603

6603-
AWS_TEST_CASE(mqtt5_client_dynamic_operation_timeout_default, s_mqtt5_client_dynamic_operation_timeout_default_fn)
6604+
AWS_TEST_CASE(mqtt5_client_dynamic_operation_timeout_default, s_mqtt5_client_dynamic_operation_timeout_default_fn)
6605+
6606+
static int s_mqtt5_do_auto_assigned_client_id_test(struct aws_allocator *allocator, bool use_iot_core_validation) {
6607+
aws_mqtt_library_init(allocator);
6608+
6609+
struct mqtt5_client_test_options test_options;
6610+
aws_mqtt5_client_test_init_default_options(&test_options);
6611+
AWS_ZERO_STRUCT(((struct aws_mqtt5_packet_connect_view *)test_options.client_options.connect_options)->client_id);
6612+
if (use_iot_core_validation) {
6613+
test_options.client_options.extended_validation_and_flow_control_options =
6614+
AWS_MQTT5_EVAFCO_AWS_IOT_CORE_DEFAULTS;
6615+
}
6616+
6617+
struct aws_mqtt5_client_mqtt5_mock_test_fixture_options test_fixture_options = {
6618+
.client_options = &test_options.client_options,
6619+
.server_function_table = &test_options.server_function_table,
6620+
};
6621+
6622+
struct aws_mqtt5_client_mock_test_fixture test_context;
6623+
ASSERT_SUCCESS(aws_mqtt5_client_mock_test_fixture_init(&test_context, allocator, &test_fixture_options));
6624+
6625+
struct aws_mqtt5_client *client = test_context.client;
6626+
6627+
const struct aws_byte_cursor auto_assigned_client_id = client->config->connect->storage_view.client_id;
6628+
if (use_iot_core_validation) {
6629+
ASSERT_INT_EQUALS(35, auto_assigned_client_id.len); /* "gen" + uuid as string no dashes length */
6630+
} else {
6631+
ASSERT_INT_EQUALS(0, auto_assigned_client_id.len);
6632+
}
6633+
6634+
aws_mqtt5_client_mock_test_fixture_clean_up(&test_context);
6635+
aws_mqtt_library_clean_up();
6636+
6637+
return AWS_OP_SUCCESS;
6638+
}
6639+
6640+
static int s_mqtt5_client_auto_assigned_client_id_fn(struct aws_allocator *allocator, void *ctx) {
6641+
(void)ctx;
6642+
6643+
return s_mqtt5_do_auto_assigned_client_id_test(allocator, false);
6644+
}
6645+
6646+
AWS_TEST_CASE(mqtt5_client_auto_assigned_client_id, s_mqtt5_client_auto_assigned_client_id_fn)
6647+
6648+
static int s_mqtt5_client_auto_assigned_client_id_iot_core_fn(struct aws_allocator *allocator, void *ctx) {
6649+
(void)ctx;
6650+
6651+
return s_mqtt5_do_auto_assigned_client_id_test(allocator, true);
6652+
}
6653+
6654+
AWS_TEST_CASE(mqtt5_client_auto_assigned_client_id_iot_core, s_mqtt5_client_auto_assigned_client_id_iot_core_fn)

0 commit comments

Comments
 (0)