mqtt5_utils.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. /**
  2. * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. * SPDX-License-Identifier: Apache-2.0.
  4. */
  5. #include <aws/mqtt/private/v5/mqtt5_utils.h>
  6. #include <aws/common/byte_buf.h>
  7. #include <aws/common/device_random.h>
  8. #include <aws/common/encoding.h>
  9. #include <inttypes.h>
  10. uint8_t aws_mqtt5_compute_fixed_header_byte1(enum aws_mqtt5_packet_type packet_type, uint8_t flags) {
  11. return flags | ((uint8_t)packet_type << 4);
  12. }
  13. /* encodes a utf8-string (2 byte length + "MQTT") + the version value (5) */
  14. static uint8_t s_connect_variable_length_header_prefix[7] = {0x00, 0x04, 0x4D, 0x51, 0x54, 0x54, 0x05};
  15. struct aws_byte_cursor g_aws_mqtt5_connect_protocol_cursor = {
  16. .ptr = &s_connect_variable_length_header_prefix[0],
  17. .len = AWS_ARRAY_SIZE(s_connect_variable_length_header_prefix),
  18. };
  19. void aws_mqtt5_negotiated_settings_log(
  20. struct aws_mqtt5_negotiated_settings *negotiated_settings,
  21. enum aws_log_level level) {
  22. struct aws_logger *temp_logger = aws_logger_get();
  23. if (temp_logger == NULL || temp_logger->vtable->get_log_level(temp_logger, AWS_LS_MQTT5_GENERAL) < level) {
  24. return;
  25. }
  26. AWS_LOGF(
  27. level,
  28. AWS_LS_MQTT5_GENERAL,
  29. "id=%p: aws_mqtt5_negotiated_settings maxiumum qos set to %d",
  30. (void *)negotiated_settings,
  31. negotiated_settings->maximum_qos);
  32. AWS_LOGF(
  33. level,
  34. AWS_LS_MQTT5_GENERAL,
  35. "id=%p: aws_mqtt5_negotiated_settings session expiry interval set to %" PRIu32,
  36. (void *)negotiated_settings,
  37. negotiated_settings->session_expiry_interval);
  38. AWS_LOGF(
  39. level,
  40. AWS_LS_MQTT5_GENERAL,
  41. "id=%p: aws_mqtt5_negotiated_settings receive maximum from server set to %" PRIu16,
  42. (void *)negotiated_settings,
  43. negotiated_settings->receive_maximum_from_server);
  44. AWS_LOGF(
  45. level,
  46. AWS_LS_MQTT5_GENERAL,
  47. "id=%p: aws_mqtt5_negotiated_settings maximum packet size to server set to %" PRIu32,
  48. (void *)negotiated_settings,
  49. negotiated_settings->maximum_packet_size_to_server);
  50. AWS_LOGF(
  51. level,
  52. AWS_LS_MQTT5_GENERAL,
  53. "id=%p: aws_mqtt5_negotiated_settings topic alias maximum to server set to %" PRIu16,
  54. (void *)negotiated_settings,
  55. negotiated_settings->topic_alias_maximum_to_server);
  56. AWS_LOGF(
  57. level,
  58. AWS_LS_MQTT5_GENERAL,
  59. "id=%p: aws_mqtt5_negotiated_settings topic alias maximum to client set to %" PRIu16,
  60. (void *)negotiated_settings,
  61. negotiated_settings->topic_alias_maximum_to_client);
  62. AWS_LOGF(
  63. level,
  64. AWS_LS_MQTT5_GENERAL,
  65. "id=%p: aws_mqtt5_negotiated_settings server keep alive set to %" PRIu16,
  66. (void *)negotiated_settings,
  67. negotiated_settings->server_keep_alive);
  68. AWS_LOGF(
  69. level,
  70. AWS_LS_MQTT5_GENERAL,
  71. "id=%p: aws_mqtt5_negotiated_settings retain available set to %s",
  72. (void *)negotiated_settings,
  73. negotiated_settings->retain_available ? "true" : "false");
  74. AWS_LOGF(
  75. level,
  76. AWS_LS_MQTT5_GENERAL,
  77. "id=%p: aws_mqtt5_negotiated_settings wildcard subscriptions available set to %s",
  78. (void *)negotiated_settings,
  79. negotiated_settings->wildcard_subscriptions_available ? "true" : "false");
  80. AWS_LOGF(
  81. level,
  82. AWS_LS_MQTT5_GENERAL,
  83. "id=%p: aws_mqtt5_negotiated_settings subscription identifiers available set to %s",
  84. (void *)negotiated_settings,
  85. negotiated_settings->subscription_identifiers_available ? "true" : "false");
  86. AWS_LOGF(
  87. level,
  88. AWS_LS_MQTT5_GENERAL,
  89. "id=%p: aws_mqtt5_negotiated_settings shared subscriptions available set to %s",
  90. (void *)negotiated_settings,
  91. negotiated_settings->shared_subscriptions_available ? "true" : "false");
  92. }
  93. int aws_mqtt5_negotiated_settings_init(
  94. struct aws_allocator *allocator,
  95. struct aws_mqtt5_negotiated_settings *negotiated_settings,
  96. const struct aws_byte_cursor *client_id) {
  97. if (aws_byte_buf_init(&negotiated_settings->client_id_storage, allocator, client_id->len)) {
  98. return AWS_OP_ERR;
  99. }
  100. if (aws_byte_buf_append_dynamic(&negotiated_settings->client_id_storage, client_id)) {
  101. return AWS_OP_ERR;
  102. }
  103. return AWS_OP_SUCCESS;
  104. }
  105. int aws_mqtt5_negotiated_settings_copy(
  106. const struct aws_mqtt5_negotiated_settings *source,
  107. struct aws_mqtt5_negotiated_settings *dest) {
  108. aws_mqtt5_negotiated_settings_clean_up(dest);
  109. *dest = *source;
  110. AWS_ZERO_STRUCT(dest->client_id_storage);
  111. if (source->client_id_storage.allocator != NULL) {
  112. return aws_byte_buf_init_copy_from_cursor(
  113. &dest->client_id_storage,
  114. source->client_id_storage.allocator,
  115. aws_byte_cursor_from_buf(&source->client_id_storage));
  116. }
  117. return AWS_OP_SUCCESS;
  118. }
  119. int aws_mqtt5_negotiated_settings_apply_client_id(
  120. struct aws_mqtt5_negotiated_settings *negotiated_settings,
  121. const struct aws_byte_cursor *client_id) {
  122. if (negotiated_settings->client_id_storage.len == 0) {
  123. if (aws_byte_buf_append_dynamic(&negotiated_settings->client_id_storage, client_id)) {
  124. return AWS_OP_ERR;
  125. }
  126. }
  127. return AWS_OP_SUCCESS;
  128. }
  129. void aws_mqtt5_negotiated_settings_clean_up(struct aws_mqtt5_negotiated_settings *negotiated_settings) {
  130. aws_byte_buf_clean_up(&negotiated_settings->client_id_storage);
  131. }
  132. /** Assign defaults values to negotiated_settings */
  133. void aws_mqtt5_negotiated_settings_reset(
  134. struct aws_mqtt5_negotiated_settings *negotiated_settings,
  135. const struct aws_mqtt5_packet_connect_view *packet_connect_view) {
  136. AWS_PRECONDITION(negotiated_settings != NULL);
  137. AWS_PRECONDITION(packet_connect_view != NULL);
  138. /* Properties that may be sent in CONNECT to Server. These should only be sent if Client
  139. changes them from their default values.
  140. */
  141. negotiated_settings->server_keep_alive = packet_connect_view->keep_alive_interval_seconds;
  142. negotiated_settings->session_expiry_interval = 0;
  143. negotiated_settings->receive_maximum_from_server = AWS_MQTT5_RECEIVE_MAXIMUM;
  144. negotiated_settings->maximum_packet_size_to_server = AWS_MQTT5_MAXIMUM_PACKET_SIZE;
  145. negotiated_settings->topic_alias_maximum_to_client = 0;
  146. // Default for Client is QoS 1. Server default is 2.
  147. // This should only be changed if server returns a 0 in the CONNACK
  148. negotiated_settings->maximum_qos = AWS_MQTT5_QOS_AT_LEAST_ONCE;
  149. negotiated_settings->topic_alias_maximum_to_server = 0;
  150. // Default is true for following settings but can be changed by Server on CONNACK
  151. negotiated_settings->retain_available = true;
  152. negotiated_settings->wildcard_subscriptions_available = true;
  153. negotiated_settings->subscription_identifiers_available = true;
  154. negotiated_settings->shared_subscriptions_available = true;
  155. negotiated_settings->rejoined_session = false;
  156. /**
  157. * Apply user set properties to negotiated_settings
  158. * NULL pointers indicate user has not set a property and it should remain the default value.
  159. */
  160. if (packet_connect_view->session_expiry_interval_seconds != NULL) {
  161. negotiated_settings->session_expiry_interval = *packet_connect_view->session_expiry_interval_seconds;
  162. }
  163. if (packet_connect_view->topic_alias_maximum != NULL) {
  164. negotiated_settings->topic_alias_maximum_to_client = *packet_connect_view->topic_alias_maximum;
  165. }
  166. }
  167. void aws_mqtt5_negotiated_settings_apply_connack(
  168. struct aws_mqtt5_negotiated_settings *negotiated_settings,
  169. const struct aws_mqtt5_packet_connack_view *connack_data) {
  170. AWS_PRECONDITION(negotiated_settings != NULL);
  171. AWS_PRECONDITION(connack_data != NULL);
  172. /**
  173. * Reconcile CONNACK set properties with current negotiated_settings values
  174. * NULL pointers indicate Server has not set a property
  175. */
  176. if (connack_data->session_expiry_interval != NULL) {
  177. negotiated_settings->session_expiry_interval = *connack_data->session_expiry_interval;
  178. }
  179. if (connack_data->receive_maximum != NULL) {
  180. negotiated_settings->receive_maximum_from_server = *connack_data->receive_maximum;
  181. }
  182. // NULL = Maximum QoS of 2.
  183. if (connack_data->maximum_qos != NULL) {
  184. if (*connack_data->maximum_qos < negotiated_settings->maximum_qos) {
  185. negotiated_settings->maximum_qos = *connack_data->maximum_qos;
  186. }
  187. }
  188. if (connack_data->retain_available != NULL) {
  189. negotiated_settings->retain_available = *connack_data->retain_available;
  190. }
  191. if (connack_data->maximum_packet_size != NULL) {
  192. negotiated_settings->maximum_packet_size_to_server = *connack_data->maximum_packet_size;
  193. }
  194. // If a value is not sent by Server, the Client must not send any Topic Aliases to the Server.
  195. if (connack_data->topic_alias_maximum != NULL) {
  196. negotiated_settings->topic_alias_maximum_to_server = *connack_data->topic_alias_maximum;
  197. }
  198. if (connack_data->wildcard_subscriptions_available != NULL) {
  199. negotiated_settings->wildcard_subscriptions_available = *connack_data->wildcard_subscriptions_available;
  200. }
  201. if (connack_data->subscription_identifiers_available != NULL) {
  202. negotiated_settings->subscription_identifiers_available = *connack_data->subscription_identifiers_available;
  203. }
  204. if (connack_data->shared_subscriptions_available != NULL) {
  205. negotiated_settings->shared_subscriptions_available = *connack_data->shared_subscriptions_available;
  206. }
  207. if (connack_data->server_keep_alive != NULL) {
  208. negotiated_settings->server_keep_alive = *connack_data->server_keep_alive;
  209. }
  210. if (connack_data->assigned_client_identifier != NULL) {
  211. aws_mqtt5_negotiated_settings_apply_client_id(negotiated_settings, connack_data->assigned_client_identifier);
  212. }
  213. negotiated_settings->rejoined_session = connack_data->session_present;
  214. }
  215. const char *aws_mqtt5_client_session_behavior_type_to_c_string(
  216. enum aws_mqtt5_client_session_behavior_type session_behavior) {
  217. switch (aws_mqtt5_client_session_behavior_type_to_non_default(session_behavior)) {
  218. case AWS_MQTT5_CSBT_CLEAN:
  219. return "Clean session always";
  220. case AWS_MQTT5_CSBT_REJOIN_POST_SUCCESS:
  221. return "Attempt to resume a session after initial connection success";
  222. case AWS_MQTT5_CSBT_REJOIN_ALWAYS:
  223. return "Always attempt to resume a session";
  224. default:
  225. return "Unknown session behavior";
  226. }
  227. }
  228. enum aws_mqtt5_client_session_behavior_type aws_mqtt5_client_session_behavior_type_to_non_default(
  229. enum aws_mqtt5_client_session_behavior_type session_behavior) {
  230. if (session_behavior == AWS_MQTT5_CSBT_DEFAULT) {
  231. return AWS_MQTT5_CSBT_CLEAN;
  232. }
  233. return session_behavior;
  234. }
  235. const char *aws_mqtt5_outbound_topic_alias_behavior_type_to_c_string(
  236. enum aws_mqtt5_client_outbound_topic_alias_behavior_type outbound_aliasing_behavior) {
  237. switch (aws_mqtt5_outbound_topic_alias_behavior_type_to_non_default(outbound_aliasing_behavior)) {
  238. case AWS_MQTT5_COTABT_USER:
  239. return "User-controlled outbound topic aliasing behavior";
  240. case AWS_MQTT5_COTABT_LRU:
  241. return "LRU caching outbound topic aliasing behavior";
  242. case AWS_MQTT5_COTABT_DISABLED:
  243. return "Outbound topic aliasing disabled";
  244. default:
  245. return "Unknown outbound topic aliasing behavior";
  246. }
  247. }
  248. enum aws_mqtt5_client_outbound_topic_alias_behavior_type aws_mqtt5_outbound_topic_alias_behavior_type_to_non_default(
  249. enum aws_mqtt5_client_outbound_topic_alias_behavior_type outbound_aliasing_behavior) {
  250. if (outbound_aliasing_behavior == AWS_MQTT5_COTABT_DEFAULT) {
  251. return AWS_MQTT5_COTABT_DISABLED;
  252. }
  253. return outbound_aliasing_behavior;
  254. }
  255. const char *aws_mqtt5_inbound_topic_alias_behavior_type_to_c_string(
  256. enum aws_mqtt5_client_inbound_topic_alias_behavior_type inbound_aliasing_behavior) {
  257. switch (aws_mqtt5_inbound_topic_alias_behavior_type_to_non_default(inbound_aliasing_behavior)) {
  258. case AWS_MQTT5_CITABT_ENABLED:
  259. return "Inbound topic aliasing behavior enabled";
  260. case AWS_MQTT5_CITABT_DISABLED:
  261. return "Inbound topic aliasing behavior disabled";
  262. default:
  263. return "Unknown inbound topic aliasing behavior";
  264. }
  265. }
  266. enum aws_mqtt5_client_inbound_topic_alias_behavior_type aws_mqtt5_inbound_topic_alias_behavior_type_to_non_default(
  267. enum aws_mqtt5_client_inbound_topic_alias_behavior_type inbound_aliasing_behavior) {
  268. if (inbound_aliasing_behavior == AWS_MQTT5_CITABT_DEFAULT) {
  269. return AWS_MQTT5_CITABT_DISABLED;
  270. }
  271. return inbound_aliasing_behavior;
  272. }
  273. const char *aws_mqtt5_extended_validation_and_flow_control_options_to_c_string(
  274. enum aws_mqtt5_extended_validation_and_flow_control_options extended_validation_behavior) {
  275. switch (extended_validation_behavior) {
  276. case AWS_MQTT5_EVAFCO_NONE:
  277. return "No additional flow control or packet validation";
  278. case AWS_MQTT5_EVAFCO_AWS_IOT_CORE_DEFAULTS:
  279. return "AWS IoT Core flow control and packet validation";
  280. default:
  281. return "Unknown extended validation behavior";
  282. }
  283. }
  284. const char *aws_mqtt5_client_operation_queue_behavior_type_to_c_string(
  285. enum aws_mqtt5_client_operation_queue_behavior_type offline_queue_behavior) {
  286. switch (aws_mqtt5_client_operation_queue_behavior_type_to_non_default(offline_queue_behavior)) {
  287. case AWS_MQTT5_COQBT_FAIL_NON_QOS1_PUBLISH_ON_DISCONNECT:
  288. return "Fail all incomplete operations except QoS 1 publishes";
  289. case AWS_MQTT5_COQBT_FAIL_QOS0_PUBLISH_ON_DISCONNECT:
  290. return "Fail incomplete QoS 0 publishes";
  291. case AWS_MQTT5_COQBT_FAIL_ALL_ON_DISCONNECT:
  292. return "Fail all incomplete operations";
  293. default:
  294. return "Unknown operation queue behavior type";
  295. }
  296. }
  297. enum aws_mqtt5_client_operation_queue_behavior_type aws_mqtt5_client_operation_queue_behavior_type_to_non_default(
  298. enum aws_mqtt5_client_operation_queue_behavior_type offline_queue_behavior) {
  299. if (offline_queue_behavior == AWS_MQTT5_COQBT_DEFAULT) {
  300. return AWS_MQTT5_COQBT_FAIL_QOS0_PUBLISH_ON_DISCONNECT;
  301. }
  302. return offline_queue_behavior;
  303. }
  304. const char *aws_mqtt5_client_lifecycle_event_type_to_c_string(
  305. enum aws_mqtt5_client_lifecycle_event_type lifecycle_event) {
  306. switch (lifecycle_event) {
  307. case AWS_MQTT5_CLET_ATTEMPTING_CONNECT:
  308. return "Connection establishment attempt";
  309. case AWS_MQTT5_CLET_CONNECTION_SUCCESS:
  310. return "Connection establishment success";
  311. case AWS_MQTT5_CLET_CONNECTION_FAILURE:
  312. return "Connection establishment failure";
  313. case AWS_MQTT5_CLET_DISCONNECTION:
  314. return "Disconnection";
  315. case AWS_MQTT5_CLET_STOPPED:
  316. return "Client stopped";
  317. }
  318. return "Unknown lifecycle event";
  319. }
  320. uint64_t aws_mqtt5_client_random_in_range(uint64_t from, uint64_t to) {
  321. uint64_t max = aws_max_u64(from, to);
  322. uint64_t min = aws_min_u64(from, to);
  323. /* Note: this contains several changes to the corresponding function in aws-c-io. Don't throw them away.
  324. *
  325. * 1. random range is now inclusive/closed: [from, to] rather than half-open [from, to)
  326. * 2. as a corollary, diff == 0 => return min, not 0
  327. */
  328. uint64_t diff = max - min;
  329. if (!diff) {
  330. return min;
  331. }
  332. uint64_t random_value = 0;
  333. if (aws_device_random_u64(&random_value)) {
  334. return min;
  335. }
  336. if (diff == UINT64_MAX) {
  337. return random_value;
  338. }
  339. return min + random_value % (diff + 1); /* + 1 is safe due to previous check */
  340. }
  341. static uint8_t s_aws_iot_core_rules_prefix[] = "$aws/rules/";
  342. struct aws_byte_cursor aws_mqtt5_topic_skip_aws_iot_rules_prefix(struct aws_byte_cursor topic_cursor) {
  343. size_t prefix_length = AWS_ARRAY_SIZE(s_aws_iot_core_rules_prefix) - 1; /* skip 0-terminator */
  344. struct aws_byte_cursor rules_prefix = {
  345. .ptr = s_aws_iot_core_rules_prefix,
  346. .len = prefix_length,
  347. };
  348. if (topic_cursor.len < rules_prefix.len) {
  349. return topic_cursor;
  350. }
  351. struct aws_byte_cursor topic_cursor_copy = topic_cursor;
  352. struct aws_byte_cursor topic_prefix = topic_cursor;
  353. topic_prefix.len = rules_prefix.len;
  354. if (!aws_byte_cursor_eq_ignore_case(&rules_prefix, &topic_prefix)) {
  355. return topic_cursor;
  356. }
  357. aws_byte_cursor_advance(&topic_cursor_copy, prefix_length);
  358. if (topic_cursor_copy.len == 0) {
  359. return topic_cursor;
  360. }
  361. struct aws_byte_cursor rule_name_segment_cursor;
  362. AWS_ZERO_STRUCT(rule_name_segment_cursor);
  363. if (!aws_byte_cursor_next_split(&topic_cursor_copy, '/', &rule_name_segment_cursor)) {
  364. return topic_cursor;
  365. }
  366. if (topic_cursor_copy.len < rule_name_segment_cursor.len + 1) {
  367. return topic_cursor;
  368. }
  369. aws_byte_cursor_advance(&topic_cursor_copy, rule_name_segment_cursor.len + 1);
  370. return topic_cursor_copy;
  371. }
  372. size_t aws_mqtt5_topic_get_segment_count(struct aws_byte_cursor topic_cursor) {
  373. size_t segment_count = 0;
  374. struct aws_byte_cursor segment_cursor;
  375. AWS_ZERO_STRUCT(segment_cursor);
  376. while (aws_byte_cursor_next_split(&topic_cursor, '/', &segment_cursor)) {
  377. ++segment_count;
  378. }
  379. return segment_count;
  380. }
  381. bool aws_mqtt_is_valid_topic_filter_for_iot_core(struct aws_byte_cursor topic_filter_cursor) {
  382. struct aws_byte_cursor post_rule_suffix = aws_mqtt5_topic_skip_aws_iot_rules_prefix(topic_filter_cursor);
  383. return aws_mqtt5_topic_get_segment_count(post_rule_suffix) <= AWS_IOT_CORE_MAXIMUM_TOPIC_SEGMENTS;
  384. }
  385. bool aws_mqtt_is_valid_topic_for_iot_core(struct aws_byte_cursor topic_cursor) {
  386. struct aws_byte_cursor post_rule_suffix = aws_mqtt5_topic_skip_aws_iot_rules_prefix(topic_cursor);
  387. if (aws_mqtt5_topic_get_segment_count(post_rule_suffix) > AWS_IOT_CORE_MAXIMUM_TOPIC_SEGMENTS) {
  388. return false;
  389. }
  390. return post_rule_suffix.len <= AWS_IOT_CORE_MAXIMUM_TOPIC_LENGTH;
  391. }
  392. static uint8_t s_shared_subscription_prefix[] = "$share";
  393. static bool s_is_not_hash_or_plus(uint8_t byte) {
  394. return byte != '+' && byte != '#';
  395. }
  396. /* $share/{ShareName}/{filter} */
  397. bool aws_mqtt_is_topic_filter_shared_subscription(struct aws_byte_cursor topic_cursor) {
  398. /* shared subscription filters must have an initial segment of "$share" */
  399. struct aws_byte_cursor first_segment_cursor;
  400. AWS_ZERO_STRUCT(first_segment_cursor);
  401. if (!aws_byte_cursor_next_split(&topic_cursor, '/', &first_segment_cursor)) {
  402. return false;
  403. }
  404. struct aws_byte_cursor share_prefix_cursor = {
  405. .ptr = s_shared_subscription_prefix,
  406. .len = AWS_ARRAY_SIZE(s_shared_subscription_prefix) - 1, /* skip null terminator */
  407. };
  408. if (!aws_byte_cursor_eq_ignore_case(&share_prefix_cursor, &first_segment_cursor)) {
  409. return false;
  410. }
  411. /*
  412. * The next segment must be non-empty and cannot include '#', '/', or '+'. In this case we know it already
  413. * does not include '/'
  414. */
  415. struct aws_byte_cursor second_segment_cursor = first_segment_cursor;
  416. if (!aws_byte_cursor_next_split(&topic_cursor, '/', &second_segment_cursor)) {
  417. return false;
  418. }
  419. if (second_segment_cursor.len == 0 ||
  420. !aws_byte_cursor_satisfies_pred(&second_segment_cursor, s_is_not_hash_or_plus)) {
  421. return false;
  422. }
  423. /*
  424. * Everything afterwards must form a normal, valid topic filter.
  425. */
  426. struct aws_byte_cursor remaining_cursor = topic_cursor;
  427. size_t remaining_length =
  428. topic_cursor.ptr + topic_cursor.len - (second_segment_cursor.len + second_segment_cursor.ptr);
  429. if (remaining_length == 0) {
  430. return false;
  431. }
  432. aws_byte_cursor_advance(&remaining_cursor, topic_cursor.len - remaining_length + 1);
  433. if (!aws_mqtt_is_valid_topic_filter(&remaining_cursor)) {
  434. return false;
  435. }
  436. return true;
  437. }
  438. /* UTF-8 encoded string validation respect to [MQTT-1.5.3-2]. */
  439. static int aws_mqtt5_utf8_decoder(uint32_t codepoint, void *user_data) {
  440. (void)user_data;
  441. /* U+0000 - A UTF-8 Encoded String MUST NOT include an encoding of the null character U+0000. [MQTT-1.5.4-2]
  442. * U+0001..U+001F control characters are not valid
  443. */
  444. if (AWS_UNLIKELY(codepoint <= 0x001F)) {
  445. return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING);
  446. }
  447. /* U+007F..U+009F control characters are not valid */
  448. if (AWS_UNLIKELY((codepoint >= 0x007F) && (codepoint <= 0x009F))) {
  449. return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING);
  450. }
  451. /* Unicode non-characters are not valid: https://www.unicode.org/faq/private_use.html#nonchar1 */
  452. if (AWS_UNLIKELY((codepoint & 0x00FFFF) >= 0x00FFFE)) {
  453. return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING);
  454. }
  455. if (AWS_UNLIKELY(codepoint >= 0xFDD0 && codepoint <= 0xFDEF)) {
  456. return aws_raise_error(AWS_ERROR_MQTT5_INVALID_UTF8_STRING);
  457. }
  458. return AWS_ERROR_SUCCESS;
  459. }
  460. struct aws_utf8_decoder_options g_aws_mqtt5_utf8_decoder_options = {
  461. .on_codepoint = aws_mqtt5_utf8_decoder,
  462. };
  463. int aws_mqtt5_validate_utf8_text(struct aws_byte_cursor text) {
  464. return aws_decode_utf8(text, &g_aws_mqtt5_utf8_decoder_options);
  465. }