Skip to content

Commit 53d0e6e

Browse files
authored
Metrics metadata (#422)
1 parent 3c2ceee commit 53d0e6e

13 files changed

Lines changed: 541 additions & 50 deletions

include/aws/mqtt/client.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,8 +697,6 @@ int aws_mqtt_client_connection_get_stats(
697697
* Sets IoT SDK metrics configuration for the connection.
698698
* These metrics will be appended to the username field during connection.
699699
*
700-
* NOTE: DO NOT USE METADATA. Metadata will not be set.
701-
*
702700
* \param connection The connection object
703701
* \param metrics The IoT SDK metrics configuration (pass NULL to disable metrics)
704702
* \returns AWS_OP_SUCCESS if successful, AWS_OP_ERR otherwise

include/aws/mqtt/mqtt.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ struct aws_mqtt_iot_metrics {
127127
* Library name string (SDK attribute)
128128
*/
129129
struct aws_byte_cursor library_name;
130+
131+
/**
132+
* Metadata entries, key value pair to set in metrics "Metadata" field
133+
*/
134+
size_t metadata_count;
135+
const struct aws_mqtt_metadata_entry *metadata_entries;
130136
};
131137

132138
AWS_EXTERN_C_BEGIN

include/aws/mqtt/private/mqtt_iot_metrics.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ struct aws_mqtt_iot_metrics_storage {
1616

1717
struct aws_byte_cursor library_name;
1818

19+
struct aws_array_list metadata_entries;
20+
1921
struct aws_byte_buf storage;
2022
};
2123

@@ -54,4 +56,17 @@ int aws_mqtt_append_sdk_metrics_to_username(
5456
AWS_MQTT_API
5557
int aws_mqtt_validate_iot_metrics(const struct aws_mqtt_iot_metrics *metrics);
5658

59+
/**
60+
* Checks if username should be included in the CONNECT packet.
61+
* Returns true if either username is provided or metrics are configured.
62+
*
63+
* @param username The username cursor (can be NULL)
64+
* @param metrics_storage The metrics storage (can be NULL)
65+
* @return true if username should be included, false otherwise
66+
*/
67+
AWS_MQTT_API
68+
bool aws_mqtt_has_non_empty_username(
69+
const struct aws_byte_cursor *username,
70+
const struct aws_mqtt_iot_metrics_storage *metrics_storage);
71+
5772
#endif /* AWS_MQTT_IOT_METRICS_H */

source/client.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -635,12 +635,13 @@ static void s_mqtt_client_init(
635635
&connect, topic_cur, connection->will.qos, connection->will.retain, payload_cur);
636636
}
637637

638-
if (connection->username || connection->metrics_storage) {
639-
struct aws_byte_cursor username_cur;
640-
AWS_ZERO_STRUCT(username_cur);
641-
if (connection->username) {
642-
username_cur = aws_byte_cursor_from_string(connection->username);
643-
}
638+
struct aws_byte_cursor username_cur;
639+
AWS_ZERO_STRUCT(username_cur);
640+
if (connection->username) {
641+
username_cur = aws_byte_cursor_from_string(connection->username);
642+
}
643+
644+
if (aws_mqtt_has_non_empty_username(&username_cur, connection->metrics_storage)) {
644645

645646
/* Apply metrics to username if configured */
646647
if (connection->metrics_storage) {

source/mqtt_iot_metrics.c

Lines changed: 137 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ int s_build_username_query(
9494
return AWS_OP_SUCCESS;
9595
}
9696

97-
// TODO Future Work: we ignored the metadata field for now, will add them in future support
9897
int aws_mqtt_append_sdk_metrics_to_username(
9998
struct aws_allocator *allocator,
10099
const struct aws_byte_cursor *original_username,
@@ -133,10 +132,15 @@ int aws_mqtt_append_sdk_metrics_to_username(
133132
struct aws_byte_cursor question_mark_str = aws_byte_cursor_from_c_str("?");
134133
struct aws_byte_cursor sdk_str = aws_byte_cursor_from_c_str("SDK");
135134
struct aws_byte_cursor platform_str = aws_byte_cursor_from_c_str("Platform");
135+
struct aws_byte_cursor metadata_str = aws_byte_cursor_from_c_str("Metadata");
136136

137137
struct aws_array_list params_list;
138138
aws_array_list_init_dynamic(&params_list, allocator, DEFAULT_QUERY_PARAM_COUNT, sizeof(struct aws_uri_param));
139139

140+
/* Buffer to hold the metadata value string: (key1=value1;key2=value2;...) */
141+
struct aws_byte_buf metadata_value_buf;
142+
AWS_ZERO_STRUCT(metadata_value_buf);
143+
140144
// Looking for any existing query in the original username
141145
if (local_original_username.len > 0) {
142146
struct aws_byte_cursor question_mark_find = local_original_username;
@@ -193,6 +197,66 @@ int aws_mqtt_append_sdk_metrics_to_username(
193197
aws_array_list_push_back(&params_list, &platform_params);
194198
}
195199

200+
/* Build metadata value string and add to params_list if metadata entries exist */
201+
if (metrics->metadata_entries != NULL && metrics->metadata_count > 0) {
202+
struct aws_byte_cursor key_value_delim = aws_byte_cursor_from_c_str("=");
203+
struct aws_byte_cursor entry_delim = aws_byte_cursor_from_c_str(";");
204+
struct aws_byte_cursor open_paren = aws_byte_cursor_from_c_str("(");
205+
struct aws_byte_cursor close_paren = aws_byte_cursor_from_c_str(")");
206+
207+
/* Calculate size needed for metadata value: (key1=value1;key2=value2;...) */
208+
size_t metadata_value_size = open_paren.len + close_paren.len;
209+
for (size_t i = 0; i < metrics->metadata_count; ++i) {
210+
const struct aws_mqtt_metadata_entry *entry = &metrics->metadata_entries[i];
211+
metadata_value_size += entry->key.len + key_value_delim.len + entry->value.len;
212+
if (i < metrics->metadata_count - 1) {
213+
metadata_value_size += entry_delim.len; /* semicolon separator between entries */
214+
}
215+
}
216+
217+
/* Initialize buffer for metadata value */
218+
if (aws_byte_buf_init(&metadata_value_buf, allocator, metadata_value_size)) {
219+
goto cleanup;
220+
}
221+
222+
/* Build metadata value: (key1=value1;key2=value2;...) */
223+
if (aws_byte_buf_append(&metadata_value_buf, &open_paren)) {
224+
goto cleanup;
225+
}
226+
227+
for (size_t i = 0; i < metrics->metadata_count; ++i) {
228+
const struct aws_mqtt_metadata_entry *entry = &metrics->metadata_entries[i];
229+
230+
if (aws_byte_buf_append(&metadata_value_buf, &entry->key)) {
231+
goto cleanup;
232+
}
233+
if (aws_byte_buf_append(&metadata_value_buf, &key_value_delim)) {
234+
goto cleanup;
235+
}
236+
if (aws_byte_buf_append(&metadata_value_buf, &entry->value)) {
237+
goto cleanup;
238+
}
239+
240+
/* Add semicolon separator between entries (not after the last one) */
241+
if (i < metrics->metadata_count - 1) {
242+
if (aws_byte_buf_append(&metadata_value_buf, &entry_delim)) {
243+
goto cleanup;
244+
}
245+
}
246+
}
247+
248+
if (aws_byte_buf_append(&metadata_value_buf, &close_paren)) {
249+
goto cleanup;
250+
}
251+
252+
/* Add Metadata parameter to params_list */
253+
struct aws_uri_param metadata_params = {
254+
.key = metadata_str,
255+
.value = aws_byte_cursor_from_buf(&metadata_value_buf),
256+
};
257+
aws_array_list_push_back(&params_list, &metadata_params);
258+
}
259+
196260
// Rebuild metrics string from params_list
197261
// First parse to get final username size
198262
size_t total_size = 0;
@@ -218,6 +282,7 @@ int aws_mqtt_append_sdk_metrics_to_username(
218282
result = AWS_OP_SUCCESS;
219283

220284
cleanup:
285+
aws_byte_buf_clean_up(&metadata_value_buf);
221286
aws_array_list_clean_up(&params_list);
222287

223288
if (result == AWS_OP_ERR) {
@@ -238,6 +303,15 @@ size_t aws_mqtt_iot_metrics_compute_storage_size(const struct aws_mqtt_iot_metri
238303
size_t storage_size = 0;
239304
storage_size += metrics->library_name.len;
240305

306+
/* Add storage for metadata entries */
307+
if (metrics->metadata_entries != NULL && metrics->metadata_count > 0) {
308+
for (size_t i = 0; i < metrics->metadata_count; ++i) {
309+
const struct aws_mqtt_metadata_entry *entry = &metrics->metadata_entries[i];
310+
storage_size += entry->key.len;
311+
storage_size += entry->value.len;
312+
}
313+
}
314+
241315
return storage_size;
242316
}
243317

@@ -270,13 +344,49 @@ struct aws_mqtt_iot_metrics_storage *aws_mqtt_iot_metrics_storage_new(
270344
storage_view->library_name = metrics_storage->library_name;
271345
}
272346

347+
/* Copy metadata entries */
348+
if (metrics_options->metadata_entries != NULL && metrics_options->metadata_count > 0) {
349+
if (aws_array_list_init_dynamic(
350+
&metrics_storage->metadata_entries,
351+
allocator,
352+
metrics_options->metadata_count,
353+
sizeof(struct aws_mqtt_metadata_entry))) {
354+
goto cleanup_storage;
355+
}
356+
357+
for (size_t i = 0; i < metrics_options->metadata_count; ++i) {
358+
struct aws_mqtt_metadata_entry entry;
359+
entry.key = metrics_options->metadata_entries[i].key;
360+
entry.value = metrics_options->metadata_entries[i].value;
361+
362+
/* Copy key into storage buffer and update cursor */
363+
if (entry.key.len > 0) {
364+
if (aws_byte_buf_append_and_update(&metrics_storage->storage, &entry.key)) {
365+
goto cleanup_storage;
366+
}
367+
}
368+
369+
/* Copy value into storage buffer and update cursor */
370+
if (entry.value.len > 0) {
371+
if (aws_byte_buf_append_and_update(&metrics_storage->storage, &entry.value)) {
372+
goto cleanup_storage;
373+
}
374+
}
375+
376+
aws_array_list_push_back(&metrics_storage->metadata_entries, &entry);
377+
}
378+
379+
/* Set storage_view to point to the metadata entries array */
380+
storage_view->metadata_count = aws_array_list_length(&metrics_storage->metadata_entries);
381+
storage_view->metadata_entries = metrics_storage->metadata_entries.data;
382+
}
383+
273384
return metrics_storage;
274385

275386
cleanup_storage:
276-
// TODO Future Work: add metadata entries once we implemented the metadata feature
277-
// if (aws_array_list_is_valid(&metrics_storage->metadata_entries)) {
278-
// aws_array_list_clean_up(&metrics_storage->metadata_entries);
279-
// }
387+
if (aws_array_list_is_valid(&metrics_storage->metadata_entries)) {
388+
aws_array_list_clean_up(&metrics_storage->metadata_entries);
389+
}
280390

281391
aws_byte_buf_clean_up(&metrics_storage->storage);
282392
aws_mem_release(allocator, metrics_storage);
@@ -288,6 +398,10 @@ void aws_mqtt_iot_metrics_storage_destroy(struct aws_mqtt_iot_metrics_storage *m
288398
return;
289399
}
290400

401+
if (aws_array_list_is_valid(&metrics_storage->metadata_entries)) {
402+
aws_array_list_clean_up(&metrics_storage->metadata_entries);
403+
}
404+
291405
aws_byte_buf_clean_up(&metrics_storage->storage);
292406

293407
aws_mem_release(metrics_storage->allocator, metrics_storage);
@@ -302,5 +416,23 @@ int aws_mqtt_validate_iot_metrics(const struct aws_mqtt_iot_metrics *metrics) {
302416
return AWS_OP_ERR;
303417
}
304418

419+
/* Validate metadata entries */
420+
if (metrics->metadata_entries != NULL && metrics->metadata_count > 0) {
421+
for (size_t i = 0; i < metrics->metadata_count; ++i) {
422+
if (aws_mqtt_validate_utf8_text(metrics->metadata_entries[i].key)) {
423+
return AWS_OP_ERR;
424+
}
425+
if (aws_mqtt_validate_utf8_text(metrics->metadata_entries[i].value)) {
426+
return AWS_OP_ERR;
427+
}
428+
}
429+
}
430+
305431
return AWS_OP_SUCCESS;
306432
}
433+
434+
bool aws_mqtt_has_non_empty_username(
435+
const struct aws_byte_cursor *username,
436+
const struct aws_mqtt_iot_metrics_storage *metrics_storage) {
437+
return (username != NULL && username->len > 0) || metrics_storage != NULL;
438+
}

source/v5/mqtt5_options_storage.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,9 @@ static size_t s_aws_mqtt5_packet_connect_compute_storage_size(
645645
size_t storage_size = 0;
646646

647647
storage_size += view->client_id.len;
648-
if (view->username != NULL) {
648+
649+
if (aws_mqtt_has_non_empty_username(view->username, options ? options->metrics_storage : NULL)) {
650+
649651
if (options) {
650652
size_t username_size = 0;
651653
aws_mqtt_append_sdk_metrics_to_username(
@@ -699,17 +701,19 @@ int aws_mqtt5_packet_connect_storage_init(
699701
return AWS_OP_ERR;
700702
}
701703

702-
if (view->username != NULL) {
703-
storage->username = *view->username;
704+
if (aws_mqtt_has_non_empty_username(
705+
view->username, client_options_storage ? client_options_storage->metrics_storage : NULL)) {
706+
if (view->username) {
707+
storage->username = *view->username;
708+
}
704709
struct aws_byte_buf metrics_username_buf;
705710
AWS_ZERO_STRUCT(metrics_username_buf);
706711

707712
/* Apply metrics to username if configured */
708713
if (client_options_storage) {
709-
struct aws_byte_cursor username_cur = storage->username;
710714
if (aws_mqtt_append_sdk_metrics_to_username(
711715
allocator,
712-
&username_cur,
716+
&storage->username,
713717
client_options_storage->metrics_storage ? &client_options_storage->metrics_storage->storage_view
714718
: NULL,
715719
&metrics_username_buf,

tests/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ add_test_case(mqtt_websocket_failed_transform)
115115
# Connection state tests
116116
add_test_case(mqtt_connection_set_metrics_valid)
117117
add_test_case(mqtt_connection_set_metrics_null)
118+
add_test_case(mqtt_connection_set_metrics_with_null_username)
118119
add_test_case(mqtt_connection_set_metrics_invalid_utf8_library)
119120
add_test_case(mqtt_connection_set_metrics_modify_on_reconnect)
120121

@@ -149,6 +150,11 @@ add_test_case(mqtt_append_sdk_metrics_special_chars)
149150
add_test_case(mqtt_append_sdk_metrics_long_strings)
150151
add_test_case(mqtt_append_sdk_metrics_invalid_utf8)
151152
add_test_case(mqtt_append_sdk_metrics_multiple_question_mark)
153+
add_test_case(mqtt_append_sdk_metrics_with_metadata)
154+
add_test_case(mqtt_append_sdk_metrics_with_metadata_invalid_utf8)
155+
add_test_case(mqtt_append_sdk_metrics_with_metadata_invalid_utf8_value)
156+
add_test_case(mqtt_iot_metrics_storage_with_metadata)
157+
add_test_case(mqtt_iot_metrics_storage_empty_metadata)
152158

153159
# topic aliasing
154160
add_test_case(mqtt5_inbound_topic_alias_register_failure)
@@ -414,6 +420,7 @@ add_test_case(mqtt5_client_manual_pub_ack_disconnect_cancellation)
414420
# Mqtt5 Metrics tests
415421
add_test_case(mqtt5_client_set_metrics_valid)
416422
add_test_case(mqtt5_client_set_metrics_null)
423+
add_test_case(mqtt5_client_set_metrics_with_null_username)
417424

418425
add_test_case(rate_limiter_token_bucket_init_invalid)
419426
add_test_case(rate_limiter_token_bucket_regeneration_integral)

0 commit comments

Comments
 (0)